04.HTTP

一、HTTP

HTTP是HyperText Transfer Protocol的缩写,即超文本传输协议(对比,HTML,超文本标记语言)。

它是一种请求/响应式的协议(双方通讯的格式),客户端在与服务器端建立连接后,就可以向服务器端发送请求,这种请求被称作HTTP请求,服务器端接收到请求后会做出响应,称为HTTP响应,客户端与服务器端在HTTP下的交互过程如图所示。

从图中可以清楚地看到客户端与服务器端使用HTTP通信的过程,接下来总结一下HTTP协议的特点,具体如下:

(1)支持客户端(浏览器就是一种Web客户端)/服务器模式。

(2)简单快速:客户端向服务器请求服务时,只需传送请求方式和路径。常用的请求方式有GET、POST等,每种方式规定了客户端与服务器联系的类型不同。由于HTTP简单,使得HTTP服务器的程序规模小,因而通信速度很快。

(3)灵活:HTTP允许传输任意类型的数据,正在传输的数据类型由Content-Type加以标记。

(4)无状态:HTTP是无状态协议。无状态是指协议对于事务处理没有记忆能力,如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。

二、HTTP 1.0和HTTP 1.1区别

1、HTTP1.0

基于HTTP 1.0协议的客户端与服务器在交互过程中需要经过建立连接、发送请求信息、回送响应信息、关闭连接4个步骤,具体交互过程如图所示。

从图中可以看出,客户端与服务器建立TCP(传输控制协议)连接后,每次只能处理一个HTTP请求。

对于内容丰富的网页来说,这样的通信方式明显有缺陷。例如,下面的一段HTML代码:

<html>
    <body>
        <img src="/image01.jpg"/>
        <img src="/image02.jpg"/>
        <img src="/image03.jpg"/>
    </body>
</html>

上面的HTML文档中包含三个<img>标记,由于<img>标记的src属性指明的是图片的URL地址,因此,当客户端访问这些图片时,需要发送三次请求,并且每次请求都需要与服务器重新建立连接。如此一来,必然导致客户端与服务器端交互耗时,影响网页的访问速度。

2、HTTP1.1

为了克服上述HTTP1.0的缺陷,HTTP1.1版本支持持久连接,也就是说在一个TCP连接上可以传送多个HTTP请求和响应,从而减少了建立和关闭连接的消耗和延时,如图所示。

从图中可以看出,当客户端与服务器端建立连接后,客户端可以向服务器端发送多个请求,并且在发送下个请求时,无需等待上次请求的返回结果但服务器必须按照接受客户端请求的先后顺序依次返回响应结果,以保证客户端能够区分出每次请求的响应内容。

由此可见,HTTP1.1不仅继承了HTTP1.0的优点,而且有效解决了HTTP1.0的性能问题,显著地减少浏览器与服务器交互所需要的时间。

三、HTTP消息

当用户在浏览器中访问某个URL地址、单击网页的某个超链接或者提交网页上的form表单时,浏览器都会向服务器发送请求数据,即HTTP请求消息。服务器接收到请求数据后,会将处理后的数据回送给客户端,即HTTP响应消息

HTTP请求消息和HTTP响应消息统称为HTTP消息。

在HTTP消息中,除了服务器端的响应实体内容(HTML网页、图片等)以外,其他信息对用户都是不可见的,要想观察这些“隐藏”的信息,需要借助一些网络查看工具。例如:HttpWatch、Fiddler、Firefox浏览器的Firebug这类抓包工具。

另外IE浏览器的“F12开发人员工具”功能也能够查看网络数据情况。

浏览器和服务器通信的HTTP消息可以通过单击“网络”按钮进行查看。

为了更好地理解HTTP消息,我们启动配置好的Tomcat,浏览上节课编写的HelloTomcat项目,来详细学习HTTP消息。

 

我们选中第一条URL,点击“详细信息”按钮,切换到详细界面。

 

先来看一下“请求标头”,这里显示的是请求头信息,“F12开发人员工具”以键值对的形式展示出来,第一行是请求行,后面其余几行是请求消息头

切换到“响应标头”,这里显示的是响应头信息。第一行是响应状态行,后面其余是响应消息头。 

切换到“请求正文”,这里是请求体,但是由于我们的请求方式是GET方式,所以,请求体是空的。因为,表单提交的内容作为URL地址的参数提交了,并没有放到请求体中。

如果请求方式是POST,这里就会显示表单提交的参数。我们找到之前注册信息的那个html文件,将其发布到Tomcat中,运行注册一下查看消息,form.html代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta charset="UTF-8">
<title>注册</title>
</head>
<body>
    <fieldset>
        <legend>注册新用户</legend>
        <form action="form.html" name="注册申请表" method="POST">
            <table cellpadding="2" align="center">
                <tr>
                    <td align="right">用户名:</td>
                    <td><input type="text" name="username" /></td>
                </tr>
                <tr>
                    <td align="right">密码:</td>
                    <td><input type="password" name="password" /></td>
                </tr>
                <tr>
                    <td align="right">性别:</td>
                    <td>
                        <input type="radio" name="sex" value="male" /><input type="radio" name="sex" value="female" /></td>
                </tr>
                <tr>
                    <td align="right">兴趣:</td>
                    <td><input type="checkbox" name="interest" value="film" />看电影
                        <input type="checkbox" name="interest" value="code" />敲代码 
                        <input type="checkbox" name="interest" value="game" />玩游戏
                    </td>
                </tr>
                <tr>
                    <td colspan="2" align="center">
                        <input type="submit" value="注册" />
                        <input type="reset" value="重填" />
                    </td>
                </tr>
            </table>
        </form>
    </fieldset>
</body>
</html>

切换到“响应正文”,这里是响应体。响应体实际上就是服务器返回的html文件内容。

下面我们来详细看看消息的具体内容。

四、HTTP请求消息

一个完整的请求消息是由请求行请求消息头实体内容(请求正文)三部分组成。

1、HTTP请求行

 

 

 

HTTP请求行位于请求消息的第一行,它包括三个部分,分别是请求方式、资源路径以及所使用的HTTP版本,具体示例如下:

GET /HelloTomcat/index.jsp HTTP/1.1   或    POST /HelloTomcat/form.html HTTP/1.1

其中,GET/POST是请求方式,/HelloTomcat/或/HelloTomcat/form.html是请求资源路径,HTTP/1.1是通信使用的协议版本。需要注意的是,请求行中的每个部分需要用空格分隔,最后要以回车换行结束。

在HTTP的请求消息中,请求方式有GET、POST、HEAD、OPTIONS、DELETE、TRACE、PUT和CONNECT共8种,每种方式都指明了操作服务器中指定URI资源的方式,它们表示的含义如表所示。

请求方式

含义

GET

向特定资源发出请求(请求指定页面信息,并返回实体主体)

POST

向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。

HEAD

向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。

PUT

向指定资源位置上传其最新内容

DELETE

请求服务器删除Request-URI所标识的资源(请求服务器删除页面)

TRACE

回显服务器收到的请求,主要用于测试或诊断

CONNECT

HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器,保留将来使用。

OPTIONS

返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送'*'的请求来测试服务器的功能性。

其中最常用的就是GET和POST方式

(1)GET方式

当用户在浏览器地址栏中直接输入某个URL地址或者单击网页上的一个超链接时,浏览器将使用GET方式发送请求。如果将网页上的form表单的method属性设置为“GET”或者不设置method属性(默认值是GET),当用户提交表单时,浏览器也将使用GET方式发送请求。

如果浏览器请求的URL中有参数部分,在浏览器生成的请求消息中,参数部分将附加在请求行中的资源路径后面。先来看一个URL地址,具体如下:

http://localhost:8080/HelloTomcat/form.html?username=lihuawei&password=123456

 

 

在上述URL中,“?”后面的内容为参数信息。参数是由参数名和参数值组成的,并且中间使用等号(=)进行连接。需要注意的是,如果URL地址中有多个参数,参数之间需要用“&”分隔。

当浏览器向服务器发送请求消息时,上述URL中的参数部分会附加在要访问的URI资源后面,具体如下所示:

GET /HelloTomcat/form.html?username=lihuawei&password=123456 HTTP/1.1

需要注意的是,使用GET方式传送的数据量有限,最多不能超过1KB(1024字节)。

(2)POST方式

如果网页上form表单的method属性设置为“POST”,当用户提交表单时,浏览器将使用POST方式提交表单内容,并把各个表单元素及数据作为HTTP消息的实体内容发送给服务器,而不是作为URI地址的参数传递。另外,在使用POST方式向服务器传递数据时,Content-Type消息头会自动设置为“application/x-www-form-urlencoded”,Content-Length消息头会自动设置为实体内容的长度,具体示例如下:

 

 

 

对于使用POST方式传递的请求信息,服务器端程序会采用与获取URI后面参数相同的方式来获取表单各个字段的数据。

需要注意的是,在实际开发中,通常都会使用POST方式发送请求,其原因主要有两个:

  • POST传输数据大小无限制

  由于GET请求方式是通过请求参数传递数据的,因此最多可传递1KB(1024字节)的数据。而POST请求方式是通过实体内容传递数据的,因此可以传递数据的大小没有限制。

  • POST比GET请求方式更安全

  由于GET请求方式的参数信息都会在URL地址栏明文显示,而POST请求方式传递的参数隐藏在实体内容中,用户是看不到的,因此,POST比GET请求方式更安全。

2、HTTP请求消息头

在HTTP请求消息中,请求行之后,便是若干请求消息头。请求消息头主要用于向服务器端传递附加消息,例如,客户端可以接收的数据类型(Accept)、压缩方法、语言(Accept-Language)以及发送请求的超链接所属页面的URL地址等信息,具体示例如下所示:

 

Accept:text/html, application/xhtml+xml, */*,能够处理的MIME(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展)类型;

Accept-Encoding:gzip, deflate,用于指定客户端能够进行解码的数据编码方式,这里的编码方式通常指的是某种压缩方式;

Accept-Language:zh-CN,头字段用于指定客户端期望服务器返回哪个国家语言的文档,如果指定多个国家的语言,用逗号分隔;

 

3、HTTP请求实体内容(请求正文)

如果使用的请求方式是GET,HTTP是没有实体内容的,参数都在URL中了。

如果使用的请求方式是POST,HTTP实体内容就是表单提交的参数。

五、HTTP响应消息

一个完整的响应消息主要包括响应状态行响应消息头实体内容(响应正文)

1、HTTP响应状态行

HTTP响应状态行位于响应消息的第一行,它包括三个部分,分别是HTTP版本、一个表示成功或错误的整数代码(状态码)和对状态码进行描述的文本信息,具体示例如下:

HTTP/1.1 200 OK

上面的示例就是一个HTTP响应消息的状态行,其中HTTP 1.1是通信使用的协议版本(200是状态码),OK是状态描述,说明客户端请求成功。需要注意的是,请求行中的每个部分需要用空格分隔,最后要以回车换行结束。

关于协议版本和文本信息,都比较容易理解,而HTTP的状态码对读者来说则比较陌生,接下来就针对HTTP的状态码进行具体分析。

状态代码由三位数字组成,表示请求是否被理解或被满足。HTTP响应状态码的第一个数字定义了响应的类别,后面两位没有具体的分类,第一个数字有5种可能的取值,状态码列表如下:

分类

分类描述

1**

信息,服务器收到请求,需要请求者继续执行操作

2**

成功,操作被成功接收并处理

3**

重定向,需要进一步的操作以完成请求

4**

客户端错误,请求包含语法错误或无法完成请求

5**

服务器错误,服务器在处理请求的过程中发生了错误

其中比较常见的的状态码如下:

200:OK,请求成功。一般用于GET与POST请求;

404:Not Found,请求的资源没有找到。说明客户端错误的请求了不存在的资源;

500:Internal Server Error,请求的资源找到了,但是服务器内部出现了错误,无法完成请求;

302:Found,找到,但是重定向。表示服务器要求浏览器重新再发一个请求,服务器会发送一个响应头location,指定新请求的URL地址;

304:Not Modified,未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,客户端在请求一个页面后,服务器第一次响应200,客户端浏览器会在本地建立一个缓存文件,并且有Last-Modified时间,第二次请求该资源时,会在请求中包含If-Modified-Since还给服务器(这个时间就是缓存文件的Last-Modified,就是浏览器缓存文件的最后修改时间),服务器端会比较If-Modified-Since与文件真实的Last-Modified时间是否相同。如果相同,说明服务器文件和客户端浏览器缓存的文件一致,没有修改,服务器会返回响应码304,并且不会返回响应实体内容(没有响应正文),节省响应时间,节省传输成本,表示客户端浏览器中缓存的就是最新版的响应实体内容。如果不相同,服务器的文件发生了改变,服务器会返回响应码200,并且重新返回响应实体内容。这个一般只对HTML静态页面有效,对于JSP动态页面一般不这样使用。当我们反复请求服务器上的一个HTML页面时,就会见到304。 

2、HTTP响应消息头

在HTTP响应消息中,第一行为响应状态行,紧接着的是若干响应消息头,服务器端通过响应消息头向客户端传递附加信息,包括服务器软件名称(Server)、被请求资源需要的认证方式、客户端请求资源的最后修改时间(Last-Modified)、重定向地址等信息。HTTP响应消息头的具体示例如下所示:

Server:Apache-Coyote/1.1,服务器软件名称和版本信息;

Accept-Ranges:bytes,服务器接受客户端使用以bytes为单位的Range请求;

Last-Modified:Mon, 25 Mar 2019 05:57:55 GMT,文档的最后修改时间,使用的是GMT时间,和中国标准时会有8个小时的时区差。

Content-Type:text/html,响应实体内容的MIME类型(服务器端返回的响应实体内容);

Content-Length:1246,响应实体内容的长度(字节数);

Date :Mon, 25 Mar 2019 06:45:35 GMT 响应的时间,使用的是GMT时间,和中国标准时会有8个小时的时区差。

 

3、HTTP响应实体内容

HTTP响应实体内容就是服务器返回给客户端浏览器的HTML代码,用于显示服务器返回的数据。

 

六、HTTP其他头字段

设置网页不缓存,以下三个字段一起使用:

Pragma:no-cache    对于HTTP1.0有效

Cache-Control:no-cache   对于HTTP1.1有效

Expires:0   设置为0,表示当前网页过期,不用缓存。 

Refresh:3;url=http://www.baidu.com   表示在3秒钟之后,自动跳转到另一个页面。 

html中的<meta>标签可以用来模拟响应消息头:

<meta http-equiv="Refresh" content="5; url=http://www.sdbi.edu.cn">

 

七、Java随机生成验证码图片

我们学习了HTTP的相关知识后,我们可以利用HTTP去反复测试,来暴力破解Web系统的用户名和密码。为此,人们发明了验证码来防止计算机的暴力破解。我们下面来学习一下Java如何生成随机验证码。

定义一个类,VerifyCode.java,定义方法createImage(),用于创建一张验证码的图片,我们使用BufferedImage类来作为该方法的返回值;定义方法output(),用于生成一张JPEG类型的图片文件;定义方法getText(),用于得到验证码的文本,用来验证用户输入的验证码。代码如下:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random; 
import javax.imageio.ImageIO;
public class VerifyCode {
    private int w = 150; // 宽度
    private int h = 70; // 高度
    private Random r = new Random(); // 获得一个随机数对象
    // 定义有哪些字体
    private String[] fontNames = { "宋体", "华文楷体", "黑体", "微软雅黑", "楷体_GB2312" };
    // 定义有哪些验证码的随机字符
    private String codes = "23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
    // 生成背景色
    private Color bgColor = new Color(250, 250, 250);
    // 生成的验证码文本
    private String text;

    // 生成随机颜色
    private Color randomColor() {
        int red = r.nextInt(200);
        int green = r.nextInt(200);
        int blue = r.nextInt(200);
        return new Color(red, green, blue);
    }

    // 生成随机字体
    private Font randomFont() {
        int index = r.nextInt(fontNames.length);
        String fontName = fontNames[index]; // 字体名称
        int style = r.nextInt(4); // 字体样式(PLAIN=0,BOLD=1,ITALIC=2,BOLD|ITALIC=3)
        int size = r.nextInt(5) + 50; // 字体大小(50-54之间)
        return new Font(fontName, style, size);
    }

    // 画干扰线
    private void drawLine(BufferedImage image) {
        int num = 3; // 干扰线数量
        Graphics2D g2 = (Graphics2D) image.getGraphics(); // 得到图片画笔
        for (int i = 0; i < num; i++) {
            int x1 = r.nextInt(w / 5); // 起点x坐标,起点尽量靠左,0到五分之一处
            int y1 = r.nextInt(r.nextInt(h - 20) + 10); // 起点y坐标,中间,10到60之间
            int x2 = r.nextInt(w / 5) + w / 5 * 4; // 终点x坐标,靠右,右五分之一
            int y2 = r.nextInt(h); // 终点y坐标,中间,10到60之间
            g2.setStroke(new BasicStroke(1.5F));// 设置线条特征,1.5F为线的宽度
            g2.setColor(randomColor()); // 干扰线颜色随机
            g2.drawLine(x1, y1, x2, y2); // 画线
        }
    }

    // 得到codes的长度内的随机数,并使用charAt取得随机数位置上的codes中的字符
    private char randomChar() {
        int index = r.nextInt(codes.length());
        return codes.charAt(index);
    }

    // 创建一张验证码的图片
    public BufferedImage createImage() {
        // 得到一个图片缓冲区
        BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = (Graphics2D) image.getGraphics(); // 得到图片画笔
        g2.setColor(bgColor); // 给画笔设置颜色
        g2.fillRect(0, 0, w, h); // 使用画笔填充指定的矩形
        g2.setColor(Color.RED); // 给画笔设置颜色
        g2.drawRect(0, 0, w - 1, h - 1); // 使用画笔绘制指定的矩形
        StringBuilder stringBuilder = new StringBuilder();
        // 向图中画四个字符
        for (int i = 0; i < 4; i++) {
            String s = randomChar() + "";
            stringBuilder.append(s);
            int x = i * w / 4; // 计算字符x坐标位置
            g2.setFont(randomFont()); // 设置画笔字体
            g2.setColor(randomColor()); // 设置画笔颜色
            g2.drawString(s, x, h - 20); // 在图片上写字符
        }
        text = stringBuilder.toString();
        drawLine(image); // 绘制干扰线
        return image;// 返回图片
    }

    // 得到验证码的文本,用来验证用户输入的验证码
    public String getText() {
        return text;
    }

    // 定义输出的对象和输出文件流
    // 注意第2个参数类型指定为OutputStream,以后可以用于向response中输出图片
    public void output(BufferedImage bi, OutputStream output)    throws IOException {
        ImageIO.write(bi, "JPEG", output);
    }
}

以上代码中,随机字符数组中只有55个字符,没有0,1,i,l,I,o,O这7个字符,因为这7个字符容易出现不好识别的情况。

另外,我们在写一个类VerifyCodeTest.java,用于测试验证码是否能够正确生成。代码如下:

import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
public class VerifyCodeTest {
    public static void main(String[] args) throws IOException {
        VerifyCode code = new VerifyCode();
        BufferedImage image = code.createImage(); // 获得验证码图片对象
        code.output(image, new FileOutputStream("D:/project_javaweb/image.jpg"));
        System.out.println("验证码的真实文本是:" + code.getText()); // 输出验证码文本
    }
}

如果我们不是需要画干扰线,而是在图片上加上一些噪点,我们可以这样修改drawLine()方法。

private void drawLine(BufferedImage image) {
    int num = 500; // 噪点个数
    Graphics2D g2 = (Graphics2D) image.getGraphics();
    for (int i = 0; i < num; i++) {
        int x = r.nextInt(width);
        int y = r.nextInt(heigth);
        g2.setColor(randomColor());
        g2.setStroke(new BasicStroke(2.0F)); // 线的粗细
        g2.drawLine(x, y, x, y); // 起点终点横纵坐标一样,就是画点
    }
}

 

posted @ 2022-09-03 11:34  熊猫Panda先生  阅读(739)  评论(0编辑  收藏  举报