关于HTTP的Content-Length与Chunk编码方式_YotWei-CSDN博客_content

length chunk · · 28 次点击 · · 开始浏览    

最近在学习netty,里面提到了一个TCP拆包粘包的问题,查阅资料了解到了几种解决的办法,例如

  1. 使用特殊分隔符
  2. 定长数据帧以及
  3. 自定义消息头+消息体,在消息头中定义数据包的长度

关于最后一种实现手段,最典型的代表就是我们常见的HTTP协议。

关于Content-Length

Content-Length是HTTP头部的一个字段,用于表示HTTP的body数据的长度。但是在HTTP 1.0的时候,Content-Length字段可有可无,因为HTTP 1.0的每一个网络请求都是短连接,在发起TCP三次握手后,服务端返回数据,随后四次挥手,这个连接在HTTP请求结束后会立即关闭,浏览器在读到EOF(-1)的时候就可以认为数据传输完毕。下面用一个Java程序验证一下

public class HttpServer01 {

    public static void main(String[] args) throws IOException {

        ServerSocket ss = new ServerSocket(8888);

        System.out.println("http simple server start ...");

        while (true) {
            final Socket socket = ss.accept();
            new Thread(() -> {
                try {
                    String response = "HTTP/1.0 200 OK\r\n" +    //头部简单一点,没有Content-Length字段
                            "\r\n" +
                            "<h1>Hello world!</h1>";    //返回一段简单的html    
                    socket.getOutputStream().write(response.getBytes());
                } catch (IOException ex) {
                    ex.printStackTrace();
                } finally {
                    try {
                        socket.close();    //写完数据就立马关闭TCP连接
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

运行程序,然后用浏览器访问http://localhost:8888,得到结果如下

即使没有Content-Length,浏览器也能正确获取到返回的内容,这是因为在TCP连接断开时,浏览器认为服务器已经将数据传输完了。如果要加上Content-Length,后面的值就必须与Body的长度一致,如果过断数据会被截断

将上面的代码的一处做一下小改动

 String response = "HTTP/1.0 200 OK\r\n" +    //头部简单一点,没有Content-Length字段
                            "Content-Length: 10\r\n" +    //这个长度显然小于下面BODY的长度
                            "\r\n" +
                            "<h1>Hello world!</h1>";    //返回一段简单的html

然后再次访问,结果如下

服务器输出的BODY被截断了,只剩下10个字符(hello后面还有个空格),说明Content-Length起到了作用。

如果Content-Length过长似乎还不会出现问题,当然前提是HTTP版本是1.0,可以自己验证一下。

HTTP 1.1的Content-Length

谈到HTTP1.1,不得不先说一个改进——Keep-Alive。使用KeepAlive后,可以达到多个HTTP请求复用一个TCP连接的目的,TCP连接在建立之后,会根据HTTP报文的Connection字段的值来判断是否关闭TCP连接,如果是keep-alive,则保持,如果为close,关闭TCP连接。Keep-Alive还可以单独设置保持连接的最大时长,超过时长自动断开TCP连接。

前面已经说过,在HTTP1.0的时候,可以认为连接关闭的时候数据就已经传输完成了,因此不需要Content-Length字段,但是HTTP1.1就变得不一样,需要Content-Length来协助了。

同样,如果Content-Length长度过短,会导致数据截断,如果过长,则会造成浏览器等待,看下面一段代码

public class HttpServer02 {

    public static void main(String[] args) throws IOException {

        ServerSocket ss = new ServerSocket(8888);

        System.out.println("http simple server start ...");

        while (true) {
            final Socket socket = ss.accept();
            new Thread(() -> {
                try {
                    String response = "HTTP/1.1 200 OK\r\n" +
                            "Connection: keep-alive\r\n" +
                            "Content-Length: 64\r\n" +
                            "\r\n" +
                            "<h1>Hello world!</h1>";
                    socket.getOutputStream().write(response.getBytes());
                } catch (IOException ex) {
                    ex.printStackTrace();
                    // 注意,这里变成了发生异常才关闭连接了,没发生异常时连接不会关闭
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

结果如下

虽然被解析出来了,但是查看body的内容是一直为空的,并且左上角的标签会一直打转,表示“还有数据没传过来”,实际上本次请求的数据已经传输完毕了,这是content-length设置不当导致的。

Chunk编码

Content-Length需要提前知道BODY的长度,对于静态资源是没问题的,但是对于一些动态资源有时候就没有那么方便了。因此HTTP1.1还有一种Chunk编码的方式来传输数据。

使用Chunk编码的BODY会变成下面这样子(假设BODY的数据是“<h1>Hello world</h1>”)

4

<h1>

5

Hello

6

 world

5

</h1>

0

 

也就是说body被切成了一个个小的数据块,因为整个body的长度不好计算,可是取其中一部分还是容易计算的,每个数据块都形如

[size]

[data]

这样的格式,最后一个数据块大小为0,代表着本次请求的数据已经传完了。使用chunk编码需要在HTTP头部声明Transfer-Encoding: chunked。

下面用代码验证一下

public class HttpServer03 {

    public static void main(String[] args) throws IOException {

        ServerSocket ss = new ServerSocket(8888);

        System.out.println("http simple server start ...");

        while (true) {
            final Socket socket = ss.accept();
            new Thread(() -> {
                try {
                    String response = "HTTP/1.1 200 OK\r\n" +
                            "Connection: keep-alive\r\n" +
                            "Keep-Alive: timeout=10\r\n" +
                            "Transfer-Encoding: chunked\r\n" +    //使用chunk需要在请求头声明
                            "\r\n" +
                            "4\r\n" +    // size = 4
                            "<h1>\r\n" +    
                            "5\r\n" +    // size = 5
                            "hello\r\n" +
                            "6\r\n" +
                            " world\r\n" +
                            "6\r\n" +
                            "!</h1>\r\n" +
                            "0\r\n" +    // size = 0,代表结束
                            "\r\n";
                    socket.getOutputStream().write(response.getBytes());
                } catch (IOException ex) {
                    ex.printStackTrace();
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

运行结果如下

查看Header

注意

  1. chunk的每个数据块的size一定会与data对应,否则浏览器会解析错误,如Chrome下会报ERR_INVALID_CHUNKED_ENCODING
  2. 数据块的size用16进制表达
  3. 最后一个数据块的size为0,下面也会跟着空数据(即 "0\r\n\r\n" )

本文来自:length chunk

感谢作者:length chunk

查看原文:关于HTTP的Content-Length与Chunk编码方式_YotWei-CSDN博客_content

28 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传