4 协议的应答格式:
上面已经比较具体的介绍的协议的请求部分,下面轮到服务器处理结束后返回数据的应答部分了。
Response = Simple-Response | Full-Response
Simple-Response = [ Entity-Body ]
Full-Response = Status-Line ; Section 6.1
*( General-Header ; Section 4.3
| Response-Header ; Section 6.2
| Entity-Header ) ; Section 7.1
CRLF
[ Entity-Body ] ; Section 7.2
呵呵,看看http 0.9的协议时,返回内容是多么的简单,直接就将内容吐出来就行,(毕竟当时支持的文件格式不对,主要是html文件,都是看成二进制格式来一视同仁;而且是通过关闭连接的方式来确认结束,所以交互过程就会相当的简单)
http 1.0应答进行了很大的扩展,增加了 服务器处理结果状态部分,通过数值来区分服务器处理的不同状态,同时还可以附加一段有语义信息的描述(呵呵,描述较少有,不过相比我们有时自己的协议里面常常返回一些具有实际意义的字符串来返回数字要科学一些,这样的话,可以被不同的客户端给予不同的处理方式,而且可以国际化),而且还增加了很多返回数据的元描述信息。
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
这里面主要的就是状态码了,状态码有三个数字来组成:
· 1xx: 还没有启用
· 2xx: 服务器处理成功
· 3xx: 跳转
· 4xx: 客户端的错误信息
· 5xx: 服务器处理错误
具体的状态码如有如下这些:
Status-Code = "200" ; 正常(最常遇到)
| "201" ; 文档已经被服务器创建了
| "202" ; 请求已经被接受了,但是文档并没有已经创建,提供了异步访问的方式,先告诉客户端我已经接受了请求,过一会儿再来查询是否创建ok吧
| "204" ; 告诉客户端没有新的内容,你不用改变当前你的活动的内容,虽然没有包体,不过服务器可以在http的头部中携带一些有用的信息给客户端。
| "301" ; 服务器文件被永久的移走
| "302" ; 服务器文件临时移走
| "304" ; 服务器的内容同客户端的是一样的,
| "400" ; 客户端发出的请求串的格式有误
| "401" ; 客户端没有授权
| "403" ; 客户端被静止访问
| "404" ; 客户端的请求找不到
| "500" ; 服务器内部异常
| "501" ; 客户端请求的命令服务器还没有实现
| "502" ; 中间的代理服务器接收到后端的web服务器的内容的格式不对
| "503" ; 服务器正忙,没有时间来完成现在客户端的请求,这种一般在IIS的服务器上大件一些asp或者asp.net的网站常常一点并发的时候就有这个错误
| extension-code(你可以根据业务需要来扩展状态码)
常常遇到和需要关注的:200 301 302 304 500
301 服务器告诉客户端文件被永远的移走了,以后请求的时候,就用这个文件吧。服务器返回的HTTP头部,有一个Location字段用于标识移走后的文件新路径。对于POST方式,HTTP协议建议客户端最好给用户以确认。一般是整站移走,或者域名切换的时候,移动到新的地方。对于POST方式请求后,在请求跳转的时候,需要使用GET方式。
302 服务器告诉客户端文件被临时的移走了,这次暂时使用这个临时的跳转地址,以后还是要来服务器询问的。其它的处理的类似与301。
304 客户端发送往服务器时带上的If-Modified-Since时间,服务器根据这个时间判定服务器的文件没有改变,于是返回304给客户端,同时不会返回包体内容了。这样客户端就能使用本地之前的内容了。返回304头部时,服务器只能返回一些缓存相关的东东。304请求对于web应用的优化是至关重要的,如果服务器忙不过来或者服务器除了错的时候,这是返回一个304码就能使用旧的内容,内容虽然比较旧,但是好歹给用户一个交代。对于一些日志站点,如qzone这样的,旧一点的内容也是没关系的,至少不是返回一个冷冰冰的错误给用户会好一些。
If the client has performed a conditional GET request and access is allowed, but the document has not been modified since the date and time specified in the If-Modified-Since field, the server must respond with this status code and not send an Entity-Body to the client. Header fields contained in the response should only include information which is relevant to cache managers or which may have changed independently of the entity's Last-Modified date. Examples of relevant header fields include: Date, Server, and Expires. A cache should update its cached entity to reflect any new field values given in the 304 response.
HTTP返回时的头部有如下这些:
Entity-Header = Allow ; Section 10.1
| Content-Encoding ; Section 10.3
| Content-Length ; Section 10.4
| Content-Type ; Section 10.5
| Expires ; Section 10.7
| Last-Modified ; Section 10.10
| extension-header
extension-header = HTTP-header
Content-Length字段,用于标识包体的长度,对于1**,204,304不能包含包体。其它的返回都必须包含一个包体或者其Content-Length说明为0。如果返回包体,最好一定要有Content-Length字段, 仅仅是依靠客户端根据服务器关闭socket来标识包的结束是存在一定问题的。
Expire字段,用于标识内容过期的时间。是一个标准的http格式的时间,如果格式不对的话,客户端可以解析为马上过期。对于浏览器里面的前进后退的按钮,只要内容还在本地磁盘上,Expire标识的过期时间是不起作用的。例如:
Expires
Fri, 29 Oct 2010 06:30:22 GMT
Expire对于网站的优化是很重要的,有必要的话,为页面元素的每个请求都增加一个过期时间,毕竟不是所有的请求都是那么实时的。
Last-Modified字段,用于标识内容上次更新的时间,客户端可以根据这个字段来判断本地的内容是否是过期的。一般对于静态的文件,这个字段就是文件的上次修改时间;对于动态内容的话,就是动态内容生成的时间。这个字段一般需要服务器来配合做一些容错使用,客户端收到Last-Modified字段后,请求的时候会带上If-Modified-Since字段,服务器会根据这个字段来判断是否需要去重新生成内容,如果服务器觉得客户端的还是没有过期的话,就会返回客户端一个304的状态码, 告诉客户端的是最新的。从而就提高了服务器的性能。
HTTP 1.0的协议基本上就说完了,比起0.9来,考虑了更多的东西。特性可以概括如下:
1 对服务器的返回的数据有了更多的元描述信息,如文件的修改时间,文件的过期的情况
2 增加了客户端到服务器端的中间过程的描述,通过http头部的一些指令来控制这些中间节点的行为
4 有了完整的可扩张的协议格式说明。通过包头和包体相分离,客户端和服务器端交互的时候能够可以携带更多的信息,而且包头采用键值对的形式,可以很容易的得到扩展。
5 协议明确规定了相关的状态码,保证了协议双方能够更好的处理一些容错和提示。
6 协议通过无状态来保证了容灾和可扩展性,客户端可以随时切换到另外一台服务器去请求内容,而不用关系数据潜在的一致性问题。
基本上看起来,http1.0已经能够很好的满足了我们很大的一部分应用了。然而随着网络和各种应用的发展,数据越来越多,需要传输的内容也越来越大,http1.0在高效和快速传输内容方面就表现的稍微的有所欠缺。接下来才有了http1.1的协议。我们来看看http1.1协议在数据的高效和快速传输方面做了一些什么事情。
话说92年很快过去,时间如梭,大家在不停的讨论中大踏步来到了96年,这一年应该发生了不少的事情,如java就是这一年开始盛行起来的,说明web开发和应用已经开始如火如荼了,一时间,百花齐放,百家争鸣,看来http协议是需要一个标准的时候来了。
三 http 1.0
1996年的http 1.0已经是标准化的东东了,保持与上面的版本兼容,不过已经添加上了不少的东东,内容变得更加的复杂。不过我们可以提取一些比较值得注意的地方:
1 HTTP概览:HTTP协议是一个请求应答的协议.对客户端、中间代理、服务器端进行了一些详细的说明和分类。明确了各自的职责。
The HTTP protocol is based on a request/response paradigm. A client establishes a connection with a server and sends a request to
the server in the form of a request method, URI, and protocol version, followed by a MIME-like message containing request
modifiers, client information, and possible body content. The server responds with a status line, including the message's protocol
version and a success or error code, followed by a MIME-like message containing server information, entity metainformation,
and possible body content.
看看一个最简单的请求应答的流程:
request chain ------------------------>
UA(客户端) -------------------v(虚拟的链路)------------------- O(服务器)
<----------------------- response chain
如果链路中间多了一些代理、防火墙、缓存这类的东东的话:中间就会多出一些转发来,简单的一条链路就会多一些转发,多一些变数,客户端请求的包的内容、或者服务器返回的包的内容都有可能在中间的某一个点上被修改。呵呵,因此很多公司的内网透过公司的代理去打开网页的话,常常被静止,或者得到一个从缓存里取出来的比较旧的内容。
request chain -------------------------------------->
UA -----v----- A -----v----- B -----v----- C -----v----- O
<------------------------------------- response chain
HTTP协议是一个应用层的协议,并没有规定说下面的传输协议一定要用TCP/IP,它只是规定了客户端和服务器端之间如何来进行可靠传输数据的一种规范。
On the Internet, HTTP communication generally takes place over TCP/IP connections. The default port is TCP 80 [15],
but other ports can be used. This does not preclude HTTP from being implemented on top of any other protocol on the Internet,
or on other networks. HTTP only presumes a reliable transport; any protocol that provides such guarantees can be used, and the
mapping of the HTTP/1.0 request and response structures onto the transport data units of the protocol in question is outside the
scope of this specification.
2 协议中的一些常用字段格式的说明:
a) URL格式说明:
http_URL = "http:" "//" host [ ":" port ] [ abs_path文档的绝对路径 ]
host = <域名或者IP地址,遵从 RFC 1123>
port = *DIGIT(没有写时,默认就是80) 通过host和port就能从语义上来区分不同的服务器资源啦
abs_path 如果没有填呢,在HTTP请求头的Request-URI需要用缺省的'/'来代替
b) 日期和时间格式:
存在三种格式:
Sun, 06 Nov 1994 08:49:37 GMT(这种最常见) ; RFC 822, updated by RFC 1123
Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
这里使用的是GMT是格林威治时间,比起北京时间来说要晚8个小时,所以GMT的08点,就是北京时间的16点啦。
对于一些页面expire时间是1个小时的,常常会奇怪为什么页面已经过期了。其实是没有加上8个小时的时差。
Expires
Tue, 19 Oct 2010 14:31:18 GMT
其实过期时间是2010年的10月19日22:31:18
3 协议请求和应答的格式:
HTTP-message = Simple-Request ; HTTP/0.9 messages
| Simple-Response
| Full-Request ; HTTP/1.0 messages
| Full-Response
分为简单版本(兼容老协议)和1.0的复杂版本。简单版本在上面已经说了,非常的简单。协议只是查询,没有包体的概念,或者是没有包头,呵呵,都是一个意思。1.0开始将包头和包体分开来,加上包头是键值对、从而使得协议变得更加的可扩展。
Full-Request = Request-Line ; Section 5.1
*( General-Header ; Section 4.3
| Request-Header ; Section 5.2
| Entity-Header ) ; Section 7.1
CRLF
[ Entity-Body ] ; Section 7.2
Full-Response = Status-Line ; Section 6.1
*( General-Header ; Section 4.3
| Response-Header ; Section 6.2
| Entity-Header ) ; Section 7.1
CRLF
[ Entity-Body ] ; Section 7.2
注意请求和应答时,包头和包体之间的CRLF也就是\r\n来作为分割的。
Request-Line = Method SP Request-URI SP HTTP-Version CRLF
请求串也就是常常看到的这种:
GET /xsoso/css/public_compress.css?t=20100706.css HTTP/1.0\r\n
这里的Method有GET POST HEAD常用的外,你也可以扩展为你想要的任何命令,只要服务器支持。
Request-URI 需要注意缺省时是'/',也就大家说的根目录,譬如apache配置了default directory index文件为index.html、index.php时就会返回这个缺省的文件。
General-Header主要包括Date和Pragma两个键
需要强调的是对Pragma的说明:
The Pragma general-header field is used to include implementation-specific directives that may apply to any recipient along the request/response chain. All pragma directives specify optional
behavior from the viewpoint of the protocol; however, some systems may require that behavior be consistent with the directives.
Pragma = "Pragma" ":" 1#pragma-directive
pragma-directive = "no-cache" | extension-pragma
extension-pragma = token [ "=" word ]
When the "no-cache" directive is present in a request message, an application should forward the request toward the origin
server even if it has a cached copy of what is being requested. This allows a client to insist upon receiving an authoritative
response to its request. It also allows a client to refresh a cached copy which is known to be corrupted or stale.
Pragma directives must be passed through by a proxy or gateway application, regardless of their significance to that application,
since the directives may be applicable to all recipients along the request/response chain. It is not possible to specify a pragma for
a specific recipient; however, any pragma directive not relevant to a recipient should be ignored by that recipient.
这个键的威力是无比大的,在整个请求链上的节点都得为它放行,如实的将其传送到最终的服务器上。常常在f5刷新一个页面的时候会看到Pragma: no-cache这个东东,含义就是任何中间的cache不要拦住我晒,我要去取最新的数据啦。
Request-Header 主要包括Authorization、From、If-Modified-Since、Referer、User-Agent这些键的信息
Entity-Header 主要包括Allow、Content-Encoding、Content-Length、Content-Type、Expires、Last-Modified这些键,如果你要扩展一些信息就属于这里了。
当有包体内容时,Content-Length的内容即为包体的长度。这里同我们一般设计的私有的客户端服务器协议不太一样,我们私有的协议一般而言,这个长度代表整个包的长度,是因为我们私有的协议包头一般是固定大小的。对于http而言,其包头是可以变长的,包头和包体的分割是通过\r\n来分割的。所以这个理的Content-Length存储的就是包体的长度了。由于POST命令需要传递数据包,所以POST命令时都会有Content-Length头部。
来看看依次HTTP的请求
GET / HTTP/1.0
Accept: */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; QQDownload 663; SLCC2;
.NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; AskTbPTV2/5.8.0.12304)
Pragma: no-cache
(Request-Line) GET / HTTP/1.1
Host x.soso.com
User-Agent Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language zh-cn,zh;q=0.5
Accept-Encoding gzip,deflate
Accept-Charset GB2312,utf-8;q=0.7,*;q=0.7
Keep-Alive 115
Connection keep-alive
Cookie ClientId=ae827f15ef3d229488589f2f38875e5954bc4478dc9b8a06; xw_visit_load=1; xw_visit_ready=0;
__utma=103122434.1522801715.1287226431.1287234451.1287495061.3; __utmz=103122434.1287234451.2.2.utmcsr=
x.soso.com|utmccn=(referral)|utmcmd=referral|utmcct=/; ad_play_index=35; skey=; uin=; __utmb=103122434.5.10.1287495061; session_id=
3061966461657876554; __utmc=103122434
Cache-Control max-age=0
加粗的部分都是http 1.1协议提供的特性(Host、Keep-Alive等)。
最近同一些web开发人员聊起,会问到一些web开发对于http协议的理解,结果有些东东大家还是比较的模糊,有些连默认端口是80都不是很清楚。于是决定八卦一下http协议,也算是对这个协议的一点复习。对自己而言,搞web开发,对天天发生在身边的http协议还是应该或多或少的有些了解才好,这样在跟进问题或者对网站进行优化的时候也才好着手。
对http协议的八卦分作如下几个部分来说:
一 简单的历史回顾
二 http 0.9
三 http 1.0
四 http 1.1
五 现在和未来的http
列了上面这么多,还不知道能不能写下来呢,先写再说!
一 简单的历史:
说道html和http协议,不得不提及Tim Berners-Lee这位牛人,这位牛津的牛人,在1989年的时候发明了w3c,1990年时自己搞了一个web的客户端和服务器。再后来的事情,就是他搞了http、html、URI、w3c等标准的东东。一些详细的信息可以参考其在w3c上的传记:http://www.w3.org/People/Berners-Lee/
为什么需要http协议?
1 文件传输
2 查询索引
3 格式透明
4 服务器无关(客户端指向另外一台服务器而不影响服务)
5 服务器上的文件能够被广大的用户获取到
存在的邮件协议,是主动推送,互传性不是很好。ftp协议允许互传文件,但是在响应端对数据能够进行的处理很少。
HTTP协议通过向集中的服务器来查询文件和上传文件解决了互传性的问题;通过返回html文件来是数据接收端对数据能够很好的得到处理。
参考:http://www.w3.org/Protocols/WhyHTTP.html
二 http 0.9
1990~1991年的http 0.9是一个非常简单的协议,http协议是一个应用层的协议,其目的类似与telnet,不过可能更加简化一些,就是发个查询命令给服务器,服务器返回查询所需要的东东。
有几点可以简单的解释一下:
一 应用层的协议,所以在传输控制的时候可以是tcp协议,也可以是其它面向连接的协议。不过一般使用的是tcp协议
二 作为查询协议,就会规定了客户端和服务器端之间通讯的规则,规则可以简单的理解为如下几个方面:
1 连接建立阶段:客户端通过域名或者ip地址,配合上端口来连接服务器,默认端口是80
2 交互阶段:客户端发送一个GET命令加上一个空格在加上查询的文档的地址,文档地址中不能出现空格,然后再加上回车和换行发送到服务器。看起来是非常的简单:
GET /index.html/r/n
服务器返回给客户端的是一串二进制的ASCII字符,里面是一个html文件的内容。
3 收尾工作:服务器端发送完文件后就关闭当前的网络连接,客户端通过检测到连接的关闭来知道文件是否发送完毕,对于慢的客户端,服务器给客户端设置了一个15秒的超时时间
当时的协议还是比较简单,规定了双方是如何来查询和答复的,规定了服务器上是如何来查找文件的,整个过程非常简单,没有后续我们常常说的gzip、mime、chunk传输、keep alive长连接、expire等等的信息。后续我们再慢慢把这些特新描述出来。
参考:http://www.w3.org/Protocols/HTTP/AsImplemented.html
三 http 1.0
先来段过门,http0.9 人们发现在www上应用起来很实用,不过在应用中又发现了一些我问题,于是在1992年的一些协议提按被提了出来,准备大踏步的迈向标准化,纯属个人之言,诸多猜测和模仿:
1992年对之前的http进行了扩展,已经有了一些1.0标准的味道了,主要有如下几个方面:
1 增加了命令:除了GET命令,还增加了SEARCH、SINCE、BEFORE、ACCEPT 、PORT、HEAD、POST、PUT、DELETE、USER、AUTHORITY、HOST 、Client Software等。从这些个命令的名称可以看到,增加了一些客户端和服务器端之间更加细致化的交互,如查询、获取文件信息HEAD、登录信息的命令。
2 返回的内容:变得比之前丰富了一些,增加了状态码、格式。通过状态码可以知道服务器出现的状况、知道文件的情况。返回增加了格式字段,当时已经有二进制的图像。
参考:
http://www.w3.org/Protocols/DesignIssues.html
http://www.w3.org/Protocols/HTTP/HTTP2.html
来看看更加复杂的协议吧:
1 客户端发起查询请求阶段,不再只是简单的GET命令了,有了更加规范的请求方式,如考虑到了HTTP的协议版本号问题。(协议的版本号是个好东东,能够较好的解决兼容性的问题)如果没有协议号,就按照0.9的规范来走。
Request = SimpleRequest | FullRequest
SimpleRequest = GET <uri> CrLf
FullRequest = Method URI ProtocolVersion CrLf
[*<HTRQ Header>]
[<CrLf> <data>]
<Method> = <InitialAlpha>
ProtocolVersion = HTTP/1.0
uri = <as defined in URL spec>
<HTRQ Header> = <Fieldname> : <Value> <CrLf>
<data> = MIME-conforming-message
这里需要强调一点的是,现在客户端也可以往服务器端传送数据了,呵呵,这里的命令可以有POST这些传数据的命令了,因此HTTP请求头和传输的数据之间是通过回车换行来分割的。 [*<HTRQ Header>][<CrLf> <data>]因为每个http头之间也是通过CrLf来分割,因此,最后一个http头和正文之间就会看起来是两个CrLf。
对于一些写C或者C++来些CGI程序的同学,如果遇到500错误,很大一部分原因就是程序在推出的时候,还没有来得及输出两个/r/n导致浏览器不知道http的头部是否已经结束,成为一个premature end of script。
这个时候的URI可以包含空格了,不过需要进行URI的转义(escape)。通过%加上这个特殊字符的十六进制来标识。如空格变成%20,20是空格的十六进制编码;如我就是%E6%88%91,因为我utf8编码对应的是3个字节。通过ua可以看到对应的三个字节正好是:E6 88 91。
转义说明参考:http://www.w3.org/Addressing/URL/4_Recommentations.html#z1
2 HTTP头部可以包含的一些信息:
· From
· Accept
· Accept-Encoding
· Accept-Language
· User-Agent
· Referer
· Authorization
· Charge-To
· If-Modified-Since
· Pragma
· Allowed
· Public
· Content-Length
· Content-Type
· Content-Transfer-Encoding
· Content-Encoding
· Date
· Expires
· Last-Modified
· Message-Id
· URI
· Version
· Derived-From
· Content-Language
· Cost
· Link
· Title
从中可以看到已经有了一些cache控制的头部,如Pragma、If-Modified-Since。不过再后来的复杂的缓存环境中,会根据etag和一些代理缓存增加一些头部,如Etag、Cach-Control等。
相比0.9,增加的命令如POST、PUT会传输数据到服务器进行存储。PUT命令是已经通过URI指定了文件,将客户端的传过去的data进行更新,POST是服务器存储这个文件后,返回一个URI来给客户端。传输数据是会指定Content-Length这个头部,以及使用multipart这个mime来传输。
参考:http://www.w3.org/Protocols/HTTP/Body.html
3 安全考虑方面
开始尝试使用加密的通道,增加了一些授权的头部字段如Authorization