Http协议简单解析

HTTP协议的基本格式:

Request:

第一行为Request Line,分为三个区域,第一个为请求方法,第二个为请求链接,第三个为协议类型,结尾加上\r\n。之后的N行为Request Header,基本格式为xxx: xxxxx,最后都是以\r\n结束。当请求头结束后,加上\r\n表示Request请求写入结束,调用flush方法传给Server服务器。附一段代码:

PrintStream ps = new PrintStream(out);
        ps.write("GET /zlv2Excel.do?reqCode=initDir HTTP/1.1\r\n".getBytes());
        ps.write("Host: 120.26.136.141:8080\r\n".getBytes());
        ps.write("Connection: keep-alive\r\n".getBytes());
        ps.write("Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n".getBytes());
        ps.write("User-Agent: Chrome/38.0.2125.122\r\n".getBytes());
        ps.write("\r\n".getBytes());
        ps.flush();

Response:

1.第一类为包含 Content-Length:xxxx 信息的。浏览器通过Content-Length的长度,判断响应实体已经结束。如果Content-Length比实际长度短,会造成内容被截取;如果Content-Length比实际长度长,会造成pending(服务器一直等待读取状态)。所以Content-Length必须真实反映实体长度。但实际上,如果实体是文件或者动态生成的,想要获取长度就不那么容易。真要计算,也需要开一个额外的buffer去缓存计算,这样会有额外的内存开销,也更增加了响应时间。下面附一段Content-Length:xxxx的响应体:

72 84 84 80 47 49 46 49 32 50 48 48 32 79 75 13 10 
69 120 112 105 114 101 115 58 32 84 104 117 44 32 48 49 32 74 97 110 32 49 57 55 48 32 48 48 58 48 48 58 48 48 32 71 77 84 13 10 
83 101 116 45 67 111 111 107 105 101 58 32 74 83 69 83 83 73 79 78 73 68 61 49 113 49 50 112 109 118 102 109 53 49 103 99 59 112 97 116 104 61 47 13 10 
67 111 110 116 101 110 116 45 84 121 112 101 58 32 116 101 120 116 47 104 116 109 108 59 32 99 104 97 114 115 101 116 61 85 84 70 45 56 13 10 
67 111 110 116 101 110 116 45 76 101 110 103 116 104 58 32 52 52 13 10 
67 111 110 110 101 99 116 105 111 110 58 32 107 101 101 112 45 97 108 105 118 101 13 10 
83 101 114 118 101 114 58 32 74 101 116 116 121 40 54 46 48 46 50 41 13 10 
13 10 
123 34 109 115 103 34 58 34 -25 -101 -82 -27 -67 -107 -27 -120 -101 -27 -69 -70 -26 -120 -112 -27 -118 -97 33 34 44 34 115 117 99 99 101 115 115 34 58 116 114 117 101 44 34 98 102 108 97 103 34 58 34 49 34 125 0 
nRead : 266

nRead是自己加的,13='\r',10='\n',32=' '(space),在13,10行之后,就是Response Body内容,最后一位长度值必须为0,表示实体结束。


2.为了解决上面提到的问题,Http新增了新的机制:不依赖头部长度的信息,也能知道实体边界。这就是Transfer-Encoding: chunked。在加入Transfer-Encoding: chunked后,代表报文采用了分块编码。分块编码的格式也很简单,首先是长度值独占一行,长度不包括结尾的\r\n。最后一个分块长度值必须为0,对应的分块没有数据,表示实体结束。附一段响应体:

72 84 84 80 47 49 46 49 32 50 48 48 32 79 75 13 10 
83 101 114 118 101 114 58 32 65 112 97 99 104 101 45 67 111 121 111 116 101 47 49 46 49 13 10 
83 101 116 45 67 111 111 107 105 101 58 32 74 83 69 83 83 73 79 78 73 68 61 53 55 56 50 65 70 52 53 51 56 49 70 67 51 69 54 54 56 49 65 54 69 69 65 70 48 49 51 70 52 51 54 59 32 80 97 116 104 61 47 13 10 
67 111 110 116 101 110 116 45 84 121 112 101 58 32 116 101 120 116 47 104 116 109 108 59 99 104 97 114 115 101 116 61 117 116 102 45 56 13 10 
84 114 97 110 115 102 101 114 45 69 110 99 111 100 105 110 103 58 32 99 104 117 110 107 101 100 13 10 
68 97 116 101 58 32 87 101 100 44 32 49 49 32 74 97 110 32 50 48 49 55 32 48 55 58 50 51 58 52 55 32 71 77 84 13 10 
13 10 
51 56 13 10 
123 34 98 102 108 97 103 34 58 34 49 34 44 34 109 115 103 34 58 34 -25 -101 -82 -27 -67 -107 -27 -120 -101 -27 -69 -70 -26 -120 -112 -27 -118 -97 33 34 44 34 115 117 99 99 101 115 115 34 58 116 114 117 101 125 13 10 
0 
nRead : 277

51 56 代表下面分块长度,最后分块值为0,代表实体结束。

Tomcat中默认用的是Transfer-Encoding: chunked,但是可以手动处理成Content-Length模式:response.addHeader("Content-Length", "100");

解析算法:

知道了HTTP协议的基本格式之后,就可以针对HTTP的数据结构进行解析处理。Socket流和文件流不一样,文件流很容易知道文件末尾,到了文件末尾,直接把流close就结束了。但是Socket流不一样,因为连接一直保持,你无法知道它什么时候结束,流也一直保持阻塞。HTTP协议就约定了数据传输的协议,比如用Content-Length获取实体长度,用chunked分块的第一行确定下一块读取的长度,这样服务端就可以有目的性的读取数据。

具体实现:JDK中在用HttpURLConnection处理http请求的时候,当返回类型为chunked模式的时候,JDK调用ChunkedInputStream去处理流的数据;当返回类型为Content-Length模式的时候,JDK调用KeepAliveStream处理流的数据。

顺便说下个人觉得HTTP/1.1的最大问题,一个TCP链路同时只能传输一个HTTP请求。这意味着完成相应之前,这个连接不能用于其他请求。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章