HTTP Connections
最近初涉网络编程,分析了下HTTP协议,下面为第一篇关于HTTP连接控制方面的学习日志,主要参考RFC2616,肯定有疏漏之处,还望指出。
HTTP协议是位于传输层之上的应用层协议,其网络层基础通常是TCP协议。TCP协议是面向连接和流的,因此连接的状态和控制对于HTTP协议而言相当重要。同时,HTTP是基于报文的,因此如何确定报文长度也是协议中比较重要的一点。
Persistent Connections持久连接目的 在使用持久连接前,HTTP协议规定为获取每个URL资源都需要使用单独的一个TCP连接,这增加了HTTP服务端的负载,引起互联网拥塞。例如内嵌图片以及其他类似数据的使用要求一个客户端在很短时间内向同一个服务端发起多个请求。
使用持久连接的优点:
减少TCP连接数量
在一个连接上实现HTTP请求和应答的流水,即允许客户端发出多个请求,而不必在接收到前一请求的应答后才发出下一请求,极大减少时间消耗
后续请求延迟减少,无需再在TCP握手上耗时
可以更加优雅地实现HTTP协议,由于持续连接的存在无需报告错误后无需关闭连接,因此客户端可使用最新的协议特性发出请求,如果接收到表示错误的应答,则换用更旧的语义。
总体描述HTTP/1.1和之前版本的显著区别是HTTP/1.1默认使用持久连接。即,除非服务端在应答中明确指出,客户端应当假定服务端会维持一个持久连接,即使从服务端收到的应答是报告错误。
持久连接对关闭TCP连接的行为提供信号量机制支持。这个信号量是在HTTP头中的Connection域设置,注意Client向Proxy发出请求时该域可能被Proxy-Connection域替换。一旦close信号被表明,客户端绝不能再通过该连接发送更多的请求。
协商(Negotiation)HTTP/1.1服务端可以假定HTTP/1.1客户端会维持持久连接,除非请求中Connection域的值是"close".同样的,如果服务端打算在送出应答后立即关闭连接,它应当在应答中包含同样的Connection域。(TCP连接关闭是双向的,此时TCP进入半关闭状态)
同样的,HTTP/1.1客户端可以期望连接是持久的,除非如前所述收到表示连接关闭的应答。当然,也可以主动发出一个包含Connection:close的请求以表明终止连接。
无论客户端还是服务端发出的报文包含Connection:close,则该请求均为连接上的最后一个请求(服务端发出此应答后关闭,因此不可能接收更多的请求)
报文传输长度 为保证持久性,连接上的报文都必须有一个自定义的报文传输长度(否则必须通过连接的关闭表示报文结束,因为TCP连接是面向流的),确定的规则按优先级由高到低排列如下:
报文传输长度指报文中出现的报文体的长度(即,不包括头长度,因为报文头的结束可通过连续两个CRLF确定)
1.任何绝不能包含报文体(如1xx,204,304)的应答消息总是以头域后的第一个空行结束,无视头中所有的entity类型域的设置,包括Content-Length域。
2.Transfer-Encoding域出现,其值为除"identify"以外的其他值,则用"chunked"传输编码方式确定传输长度,具体方式留待下篇分析。
3.Content-Length域出现,且Transfer-Encoding域未出现(出现则忽略Content-Length域)。Content-Length域的值为十进制数的字节序,如Content-Length:1234,则1、2、3、4是分别作为一个octet传输的,因此需要atoi转换成数值。
4.如果报文使用了"multipart/byteranges"的媒体类型,且没对传输长度做前面的指明,则这种自分割的媒体类型定义了传输长度。具体参见Range头域的说明。
5.服务端关闭连接(此方法不可用于客户端发出的请求报文,因为客户端关闭连接则使得服务端无法发送应答).
为保持和HTTP/1.0的兼容性, 包含报文体的HTTP/1.1请求必须包含合法的Content-Length头域,除非明确知道服务端是HTTP/1.1兼容的.如果请求包含消息体,而没有Content-Length域,那么如果服务端无法确定消息长度时,它会返回400(无效请求),或者坚持获取合法Content-Length而返回411(要求包含长度).
所有接收实体的HTTP/1.1应用程序必须接受"chunked"传输编码, 这样允许当报文长度无法预先确定时可以运用此机制获取报文长度.
报文不能同时包含Content-Length头域和非"identity" Transfer-Encoding.如果出现了, Content-Length域必须被忽略.
当Content-Length域在允许报文体的报文中存在时, 其域值必须严格等于消息体中的8比特字节.HTTP/1.1 user agent 必须在接收并检测到一个错误的长度时提醒用户.
以上方法中,最常见的还是使用Content-Length域表示报文体长度,Transfer-Encoding需要按格式解码才能还原出发送编码前的报文。
流水 支持持久连接的客户端可以流水发送请求,服务端必须按发送的顺序发送应答。
假定持久连接和连接后即可流水的客户端应当做好在第一次流水失败后重新尝试此连接。在这样的尝试中,在确定连接是持久的之前,客户端不能再流水。
客户端同样必须准备好在服务端送回所有相关应答前就关闭连接时重发请求。
不应流水non-idempotent方法
Proxy Servers 对于代理服务端而言,正确实现Connection头域指定的属性尤为重要。
代理服务端必须分立通告它的客户端和连接的原始服务端持久连接的属性,每个持久连接设置仅针对一个传输连接。
实践考量 超时值,服务端通常会为每个连接维护一个定时器,一旦某个连接不活跃超过一定时间值,服务端会关闭此连接。考虑到一个客户端可能通过代理服务端发出更多连接,代理服务端通常会将超时值设置得更高。
还有一些关于从异步关闭中恢复的讨论。
报文传输要求 使用TCP流控制来解决服务端临时负载过高问题,而不是简单的依赖客户端重连而关闭连接。
监视连接情况以获取错误状态消息
关于使用100(继续)状态码
100状态码用于客户端发送请求体之前测试是否可以发送该请求,对于Proxy,有以下要求:
1.如果代理服务端接收到包含Expect头域值为"100-continue"的请求, 而不明确知道下一跳服务不支持HTTP/1.1以上版本, 则它必须转发这个请求, 包括Expect头域.
2.如果代理知道下一跳服务端为HTTP/1.0或者更低版本, 则它不能转发此请求, 且必须以407应答客户端.
3.如果明确知道发出请求的客户端版本为HTTP/1.0或者更低,则代理服务端绝不能转发100应答,这条规则凌驾于转发1xx应答的一般准则.
Connection头域说明BNF文法:
Connection = "Connection" ":" 1#(connection-token)
connection-token = token
Connection头域中的token用于指定对于特定连接有意义的选项,因此proxy在转发前要扫描此域,从头中去除和token同名的域。例如Connection:Range,则要去掉Range域。
HTTP/1.1定义了close这个token,发送者用此token表示在完成这个报文所属请求/应答的收发后连接将关闭。