继续HttpClient构造的博文,第二篇:GET方法的实现。HTTP协议定义了和服务器交互的不同方法,包括GET,POST,PUT,DELETE,CONNECT等等,其中最常用的两个方法就是GET和POST。这篇先讲讲GET方法的一些细节。
HTTP协议的交互主要由请求和响应组成:客户端发起请求,服务端返回响应。而一个简单的HTTP请求又可以分成信息头和信息体。但对于GET来说,它的请求只有HTTP消息头而已。
一个最简单的HTTP GET请求可以写成: 而复杂的请求往往会加入很多的请求头域,如:
GET /logos/2011/hargreaves11-hp-15.jpg HTTP/1.1 Host: www.google.com.hk Connection: keep-alive Referer: http://www.google.com.hk/ User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.60 Safari/534.24 Accept: / Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8 Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3
每一个头域都由一个域名,冒号(:)和域值组成。域名是大小写无关,而域值前面可以添加数个空格。之所以将前面那段字特地高亮是因为碰到很多“不太规范”的HTTP Server返回的域名经常是不“正规”的:比如将Content-Type写成Content-type—-这只是从视觉上不太美观,但是绝对是符合规范的,所以客户端在解析的时候特别要注意忽略大小写的影响。
一些典型的头域有:
无论是GET方法还是POST方法,HTTP的响应都是一致的:一个HTTP消息头和一个HTTP消息体。在HTTP消息头的第一行指定了:HTTP版本号,HTTP响应码和详细消息。而接下来就是一个个头域,直到接受到两个\r\n为止。一个典型的HTTP相应的消息头如下:
HTTP/1.1 200 OK Cache-Control: private, max-age=30 Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Expires: Mon, 25 May 2009 03:20:33 GMT Last-Modified: Mon, 25 May 2009 03:20:03 GMT Vary: Accept-Encoding Server: Microsoft-IIS/7.0 X-AspNet-Version: 2.0.50727 X-Powered-By: ASP.NET Date: Mon, 25 May 2009 03:20:02 GMT Content-Length: 12173
对于客户端来说需要着重关注的头域一般只有Content-Length和Transfer-Encoding而已。如果返回的HTTP消息头中有Content-Length这个头域则说明HTTP体是定长的,客户端只需要继续接受Content-Length指定的数据长度的内容即可。而如果服务器在返回数据给客户端时是一边生成数据一边发送,那么很可能就会采用chunked的形式,将Transfer-Encoding指定为chunked。这样客户端对HTTP消息体的处理就会稍显复杂。 在HTTP协议的RFC中对chunk传输的定义如下:
Chunked-Body = *chunk
last-chunk
trailer
CRLF
chunk = chunk-size [ chunk-extension ] CRLF
chunk-data CRLF
chunk-size = 1*HEX
last-chunk = 1*("0") [ chunk-extension ] CRLF
chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
chunk-ext-name = token
chunk-ext-val = token | quoted-string
chunk-data = chunk-size(OCTET)
trailer = *(entity-header CRLF)
简单来说就是:一个chunked编码的body可以分为四个部分:一系列的chunk段,last chunk,trailer和CRLF。一个chunk又可以分为chunk-size和chunk-data两部分。通过解析chunk-size(16进制)可以获取到真正的chunk-data长度并进行拼接得到最后的HTTP体内容。RFC中关于chunk的解析伪代码如下:
length := 0
read chunk-size, chunk-ext (if any) and CRLF
while (chunk-size > 0) {
read chunk-data and CRLF
append chunk-data to entity-body
length := length + chunk-size
read chunk-size and CRLF
}
read entity-header
while (entity-header not empty) {
append entity-header to existing header fields
read entity-header
}
Content-Length := length
Remove "chunked" from Transfer-Encoding
为了构造这个HTTPClient我也相应写了C++版的处理方法,不过没有考虑chunk-extension和entity-header的处理(在我测试的几个网站来看都没有这两个值,所以即使写了也无法验证其正确性)。