Transfer-Encoding 是HTTP header 中的 entity header,所谓entity header就是专门用来描述消息体(message body)的头,像 Content-Length, Content-Language, Content-Encoding 都属于 entity header这一类,他们分别用来描述消息体的长度、语言、编码格式。那么 Transfer-Encoding 是用来干什么的呢?

简介

通常,HTTP Response 中的 message body 是整个包一起发送到客户端的,Content-Length 消息头字段就是用来表示消息体的长度, 服务端必须精确地告诉客户端这个 message body 的长度是多少, 如果Content-Length 比实际长度短,会造成内容被截断,如果比实体内容长,客户端就一直处于pendding状态,直到所有的消息体都返回了才结束请求。但是对于HTTP持久连接,服务端发送消息体之前就要提前计算出消息体的长度作为Content-Length 发送给客户端,对于动态生成的内容在完全创建好之前是无法预知内容的长度的,这就要服务器缓冲内容直到完整的生成内容后计算出Content-Length的值才能发送给客户端。然而使用分块传输编码(Transfer-Encoding),数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小,这就是Transfer-Encoding 的作用。

在消息头中指定Transfer-Encoding: chunked 就表示整个response将使用分块传输编码来传输内容,一个完整的消息体由n个块组成,并以最后一个大小为0的块为结束。每个非空的块包括两部分,分别为:块的长度(用十六进制表示)后面跟一个CRLF (回车及换行),长度并不包括结尾的回车换行符。第二部分就是数据本身,同样以CRLF (回车及换行)结束。最后一块是单行,只由块大小(0)以及CRLF组成,不包含任何数据。

现在就可以用Python实现一个简单的 HTTP Server 来指定 Response 的头 Transfer-Encoding。

# -*- coding:utf-8 -*-

import socket

if __name__ == '__main__':
    PORT = 8000
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('127.0.0.1', PORT))
    sock.listen(1)
    print 'Serving HTTP on port %s ...' % PORT

    while 1:
        conn, addr = sock.accept()
        print conn, addr
        request = conn.recv(1024)
        # HTTP响应消息
        conn.sendall("HTTP/1.1 200 OK\r\n")  # status line
        conn.sendall("Content-Type: text/plain\r\n")
        conn.sendall("Transfer-Encoding: chunked\r\n")  # 分块传输编码
        conn.sendall("\r\n")  # 空行
        conn.sendall("b\r\n")  # 11个字节的长度
        conn.sendall("hello world\r\n")  # 消息体
        conn.sendall("0\r\n")  # 最后一块0长度
        conn.sendall("\r\n")
        conn.close()

用telnet请求测试:

telnet localhost 8000
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying fe80::1...
telnet: connect to address fe80::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
get HTTP/1.1 /              # 发送GET请求
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

b
hello world
0

最后引用Stack Overflow 上一句非常精辟的话作为总结:

Transfer-Encoding: chunked is needed when the total content length is unknown before the first bytes are sent.

参考

https://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html

https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5

https://docs.python.org/2/library/socket.html#socket.socket

完毕。