深入了解HTTP协议

仅供学习交流,如有错误请指出,如要转载请加上出处,谢谢

前言

HTTP是超文本传输协议(HyperText Transfer Protocol)的缩写,是一种用于分布式、协作式和超媒体信息系统的应用层协议,也是万维网的数据通信的基础,设计最初的目的是为了统一发布和接收HTML页面的标准,由统一资源标识符(Uniform Resource Identifiers,URI)来标识

注意:统一资源标识符的英文缩写是uri,并不是统一资源定位符(uniform resource locator)url,从名称可见,uri是标识资源信息的,url是定位资源位置的,资源位置也是资源信息中的一种,所以url是uri的子集,比如http://pettyandydog.com 是一个url,也可以说是一个uri,但是http://andy:123@pettyandydog.com 就不是一个url,而是uri,因为包含了资源的认证信息

HTTP经历了多个版本的迭代,从最开始,也是最简单的,还没有版本的概念,后来统称为:HTTP/0.9版本,到后来形成可扩展的标准化协议,下面就是HTTP各个版本的变迁:

HTTP/0.9

HTTP/0.9版本很简单,一个请求由一个单行的指令组成,只接受GET一种请求方法,没有HTTP头和状态码,如果请求过程中出现了任何问题很难定位原因,很有局限性

HTTP/1.0

为了解决HTTP/0.9版本的局限性,引入了HTTP头和状态码,并且根据不同的场景增加了其他的请求方法,能够传输更多的文档类型,大大的提升了灵活性

HTTP/1.1

目前使用最广泛的HTTP版本,并且在HTTP/1.0版本的基础上做了很多的优化,比如持久化连接,单IP多域名(Host),以及增加Range和Content-Range头域和100(contiune)状态来节约带宽和完善缓存机制等等

HTTP/2.0

HTTP/2.0兼容HTTP/1.1的基础上,采用了二进制的方式来优化传输的效率,连接方式上采用了多路复用,就是单个连接同时可以处理多个请求,他消除了一些相似请求的重复操作,虽然HTTP/2.0还没有被广泛的使用,但是前途一片光明

HTTP

现在在浏览器请求http://pettyandydog.com 为例,先看一下客户端到服务端请求的方式

上述的网络通讯是基于利用TCP/IP协议族完成的,发送端的客户端在应用层发送了一个HTTP请求http://pettyandydog.com ,封装成一个请求报文,然后经过传输层,TCP协议把HTTP的报文进行分割,并在各个报文打上标记序号和端口号发送给网络层,在网络层的IP协议就负责将目的地的MAC地址转发给链路层,在目标地址的链路层接收到数据,再一层层解析,直到应用层, 这就是一个完整的HTTP请求的过程

HTTP报文


HTTP的报文分为请求报文和响应报文,如上图所示就是请求报文和相应报文的结构,都是由报文首部+空行+报文主体(不一定存在)组成,而报文首部又由请求行,状态行和四种首部以及其它(HTTP的RFC规范未定义的首部,比如cookie)构成,接下分别介绍请求报文和响应报文的首部字段

请求报文

在上述HTTP请求的过程中,客户端在应用层会对HTTP报文进行封装,而这封装的HTTP报文就是请求报文,接下来看下请求http://pettyandydog.com 的首部字段(来自chrome console )

  • Apccept:上图就是HTTP的请求首部字段,前三行以Apccept开头的表示客户端和服务端协商的内容规范, Accept表示客户端希望接收服务端的MIME格式的资源,比如text/html表示希望接收的是html类型,application/xml表示希望接收的是xml类型,Accept-Encoding表示希望接收的编码格式,在客户端和服务端通信过程中,会对文本内容进行编码来优化传输效率,也就是数据压缩,gzip和deflate就是常用的压缩算法,Accept-Language表示希望接收的语言,按先后顺序,中国当然是首选zh-CN,除了上图的三种Accept开头的首部,还有Accept-Charset表示字符编码等等

  • Cookie:指保存在客户端数据标识,当服务器接收请求之后,会在响应首部Set-Cookie设置cookie存储数据,并设置过期时间,域,路径来维护cookie的生命周期,之后发送的请求都会携带cookie首部到服务端,比如最常见的登录操作就会利用这个首部

  • Host:是HTTP/1.1新增的首部,它是解决一IP多域名的问题,由于在一台机器上可以装有多个虚拟机,所以会存在多个域名端口,而Host就是标识是哪个域名端口的请求

  • If-Modified-Since:表示是请求的资源在缓存中更改的时间,是用于缓存检验,他对应着响应首部Last-Modified(表示服务端资源最后更改的时间),当首次请求的响应首部中含有Last-Modified返回,当再次访问该资源,Last-Modified的时间会赋给If-Modified-Since首部传送给服务器进行校验,如果服务器资源修改时间与之相同表示资源未被修改,使用当前资源缓存,返回304状态码,如果服务器资源的时间在这之后,表示已经修改过了,重新获取资源,返回200状态码,其实If-Modified-Since和Last-Modified这种组合的缓存校验是一种弱校验,他们只是精确到秒级,如果多个请求在单秒内请求同一资源,一个请求对其资源进行了修改,另外一个请求获取的资源由于都是在秒内响应,所以比较时间会表示没有更改,直接获取之前资源副本,所以在很多高并发级别的场景下并不准确,这个时候就有另外一个组合请求首部If-None-Match和响应首部Etag,如果If-Modified-Since和If-None-Match同时出现在请求首部时,会优先使用If-None-Match,他是通过版本号来区分资源,比较两个资源的字节,全部相同才认为是一致的,相对于If-None-Match和Etag组合来说,这是属于一个弱比较算法,因为还有一个因素就是时间,如果该资源被修改成另一个资源,后来又修改回来,虽然内容是一致的,但是从时间上来说是不同的资源了,这类似原子操作CAS的ABA问题,所以有另外一个请求首部字段和Etag组合,就是If-Match,这个组合通常搭配请求首部Range(后面会讲到)用于范围请求资源

  • Proxy-Connection:该首部源于早期超文本传输协议版本实现中的错误。与标准的连接(Connection)字段的功能完全相同,主要是用于管理网络连接,在HTTP/1.0版本中,默认的连接是短连接(Connection:close),每一个HTTP请求时都会建立TCP连接-三次握手,到HTTP请求完成之后会关闭TCP连接-四次挥手,如果在一定时间内请求多次,这样频繁的连接关闭其实很耗时的,所以到HTTP/1.1版本出现了长连接(Connection:Keep-Alive),也是现在最常用的,他会让一个连接保持一段时间,这样可以重复的利用该连接,但是如果这段时间内没有连接也会占用资源,这样频繁的话会很浪费资源,且长连接只是解决了资源重复利用,对于HTTP的连接来说是顺序的,下一个请求必须要等待上一个执行完成,如果他们可以并行的话可以大大的提升请求的效率,这就是HTTP Pipeining

    但是大多数的浏览器并不支持HTTP Pipeining,所以在HTTP/2.0版本中,有一种更好的连接方式代替了HTTP Pipeining,那就是multiplexing-多路复用,单个进程就可以实现多个连接并行执行,在现在很多的 IO通信中都用这种模型

  • Upgrade-Insecure-Request:这是客户端向服务端发送一个信号,表示可以接受升级机制的资源,比如http过渡到https时可以访问该资源

  • User-Agent:标识客户端的身份,包含应用系统,软件开发商,以及版本号等等

除了上述用到的请求首部之外,还有比较常用的比如range首部,用于范围请求,通常会用于断点续传的下载功能,对于一些较大的文件的资源访问时,range会规范请求的字节范围,请求成功会响应206 Partial Content状态码表示处理了范围请求,如果请求首部没有声明Accept-Range或者值为none,表示不支持范围请求,返回200 ok状态吗,如果请求的字节大小大于资源的大小则会返回 416 Requested Range Not Satisfiable 无法满足范围请求,或者使用If-Range请求首部说明范围请求的条件

响应报文

在发送HTTP请求给服务端,服务端会对HTTP请求进行处理并且返回处理结果给客户端,而这个处理结果会被封装成一个HTTP报文进行传输,这就是响应报文,下面是响应http://pettyandydog.com 请求的首部字段

  • Access-Control-Allow-Origin:这个是针对CORS跨域请求设置的,指定哪些网站可参与到跨来源资源共享过程中,在一台机器上可能存在很多的域,不同的端口,它们之间的通信需要达成某种共识,*代表可以接收任何域,除了Access-Control-Allow-Origin,还有其他Access-Control开头的响应字段来控制请求参数,比如Access-Control-Allow-Methods表示可以接收请求的方法(GET,POST,PUT…),Access-Control-Allow-Headers表示可以接收的请求首部(基本的比如Accept开头的首部,Content-Type等等)

  • Cache-Control:控制服务端向客户端所有的缓存机制来缓存这个响应对象,有六种控制方式来设置缓存策略

Cache-directive 说明
public 所有内容都将被缓存(客户端和代理服务器都可缓存)
private 内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)
no-cache 必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌 (ETag),no-cache 会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载。
no-store 所有内容都不会被缓存到缓存或 Internet 临时文件中
must-revalidation/proxy-revalidation 如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证
max-age=xxx (xxx is numeric) 缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高
  • Cache-directive说明public所有内容都将被缓存(客户端和代理服务器都可缓存)private内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)no-cache必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌 (ETag),no-cache 会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载。no-store所有内容都不会被缓存到缓存或 Internet 临时文件中must-revalidation/proxy-revalidation如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证max-age=xxx (xxx is numeric)缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高

  • Content-Length:指明客户端向服务端的请求体的长度,用十进制表示八位字节数组

  • Date:此条消息被发送时的日期和时间(按照 RFC 7231 中定义的“超文本传输协议日期”格式来表示)
  • Expires:响应请求的超时时间,超过该时间表示响应已经过期了,相对于Cache-Control:max-age而言,max-age表示过期的秒数,Expires表示的是过期的时间,如果两者同时存在,max-age优先级更高,如果两者都不存在,以Date和Last-Modified来判断是否过期
  • Keep-Alive:这是一个通用的首部,表示持续连接的状态,只有在Connection设置为keep-Alive才有效,如果为timeout表示该连接保持的过期时间,如果是max表示该连接请求的最大数,在HTTP/2.0已经被忽略了
  • Last-Modified:该请求的资源最后修改的时间,用于请求资源检验的,这在请求首部已经介绍过了
  • Server:表示处理请求的服务器的软件相关信息
  • X-Github-Request-Id:github标识的请求ID,由于我的http://pettyandydog.com 这个请求是指向github资源的,所以会返回一个guthub的响应标识

在响应首部中,除了上述出现的首部之外,还有比如Set-Cookie首部字段表示向客户端设置cookie值,Location首部字段用于重定向,指访问的资源已经迁移或者换了新的地址,用Location表示新的地址重定向,Transfer-Encoding首部字段表示当前传输内容的传输编码,包括chunked,gzip,compress等等,vary首部表示后续的请求使用缓存的文件还是请求新的资源,当缓存服务器收到一个请求,只有当前的请求和缓存的请求头以及vary都匹配才使用缓存,否则请求新资源

HTTP状态码

在chrome console的首部信息中还有一项通用首部,它包含着请求url和响应的状态码,如下所示

上图所示的status code是HTTP请求的响应状态码,标识着客户端和服务端的交互的状态,一般由三位数字和原因短语组成,上图中的状态码304 Not Modified中304表示一个三位的状态数字,Not Moditied代表原因短语,在HTTP的响应请求中,共有五种类别的状态码,分别由1到5的数字开头,下面分别一一介绍

1XX消息

1XX类型的状态码表示请求已经被接受,等待下一步处理,在HTTP1.0并不支持

  • 100 Continue:服务器已经接收到了请求头,通知客户端继续发送主体,列如POST请求的请求数据体,一般在服务器检验请求首部时,客户端会在请求首部发送Expect:100-contunue来指定响应100 Continue状态码
  • 101 Switching Protocols:通过Upgrade消息头来通知客户端切换协议来处理请求

2XX成功

2XX类型的状态码表示请求已经成功被服务器所接受

  • 200 OK:表示请求被成功处理并返回,适用于请求的资源的方法:GET,POST,HEAD
  • 201 Created:请求创建新的资源成功,适用于创建资源的方法:PUT
  • 202 Accepted:请求被接受,但是可能还没有处理
  • 203 Non-Authoritative Information(自HTTP / 1.1起):表示请求的是一个转换代理服务器
  • 204 No Content:请求被服务器成功处理,但无内容返回,适用于PUT请求对资源进行了更新,不需要返回内容,不同于201 Created,一个是更新一个资源,一个是创建一个资源
  • 205 Reset Content:与204一样,不同的是要求客户端重置文档视图,比如清空表单内容
  • 206 Partial Content:部分资源请求成功,通常用于指定请求首部Range的范围请求,比如断点续传

3xx重定向

3XX类型的状态码表示客户端需要进一步的操作才能完成请求

  • 301 Moved Permanently:请求的资源被永久移动到新的地址,响应在Location首部指定新的地址重新保存
  • 302 Found:与301类似,不同的是资源只是暂时性的移动,并不会更新新的地址
  • 303 See Other:与302类似,只是访问新的地址以GET的方式,这是302的一种补充
  • 304 Not Modified:与请求头If-Modified-Since或If-None-Match指定的版本对比,资源并未改变,无需重新获取资源,在上述的请求http://pettyandydog.com 中就是返回304,表示这个请求没有重新请求资源,资源并未改变,所以访问的是缓存

4xx客户端错误

4XX类型的状态码表示客户端出现一些错误,比如违反规范或者与服务器需要的请求预期不符

  • 400 Bad Request:客户端违反了请求的规范,比如格式错误,请求体太大,或者语法错误
  • 401 Unauthorized:没有权限访问资源,会在首部WWW-Authenticate返回该权限的验证信息
  • 403 Forbidden:服务器已经接受请求,但是拒绝执行请求,与401不同的时,401可以访问资源,只是缺少权限而已,加上权限认证就可以访问了
  • 404 Not Found:请求的资源没有被找到,这个在开发过程中也经常遇到,一般都是请求的路径不正确
  • 405 Method Not Allowed:不合理的请求方法,如果客户端使用了服务端禁止的请求方法访问资源就会返回此状态码

5xx服务器错误

  • 500 Internal Server Error:服务器错误,无法处理请求,这个在开发中也经常遇见,泛指服务端出现了异常或者错误
  • 502 Bad Gateway:网关或者代理服务器执行请求,接收到上游的服务器的无效响应
  • 503 Service Unavailable:服务器维护或者过载,无法处理请求,这种情况应该最好发送一个用户友好的界面来解释发生的原因,如果可行时,在Retry-After首部字段指定恢复的时间
  • 504 Gateway Timeout:网关或者代理服务器执行请求,接收到上游的服务器的响应超时,与502的无效响应不同

注意:这里要注意一点的是200 ok和304 Not MoModified这两个状态,因为这两个在请求中出现的频率很高,会经常混淆,,在大多数浏览器中,回车键访问地址返回的是200 ok和刷新访问地址返回的是304 Not Modified,这里举两个例子来区别,第一个例子:首先我发送一个请求(如果上一个请求在响应首部指定了过期时间Cache-Control:max-age=xxx),他会在客户端通过Max-Age进行判断该请求的缓存是否已经过期,如果没有过期就会直接访问缓存服务器的副本,返回200 ok,如果过期了,就会访问服务器重新访问资源,返回200 ok,第二个例子:还是发送一个请求(如果上一个请求在响应首部只是指定了Last-Modified或者Etag),他就会在请求首部加上If-Modified-Since和If-None-Match去请求服务器资源,在服务端会对请求资源的最后修改的时间或者版本号和请求首部If-Modified-Since和If-None-Match的信息进行比较,如果一致表示缓存没有过期,返回304 Not Modified, 直接获取缓存副本,如果不一致,重新请求资源,返回200 OK,这里其实就很清楚了,304 Not Modified是在服务端进行了缓存有效性比较,并且一致性返回的状态码,200 ok是表示访问了服务器的资源注意:这里要注意一点的是200 ok和304 Not MoModified这两个状态,因为这两个在请求中出现的频率很高,会经常混淆,,在大多数浏览器中,回车键访问地址返回的是200 ok和刷新访问地址返回的是304 Not Modified,这里举两个例子来区别,第一个例子:首先我发送一个请求(如果上一个请求在响应首部指定了过期时间Cache-Control:max-age=xxx),他会在客户端通过Max-Age进行判断该请求的缓存是否已经过期,如果没有过期就会直接访问缓存服务器的副本,返回200 ok,如果过期了,就会访问服务器重新访问资源,返回200 ok,第二个例子:还是发送一个请求(如果上一个请求在响应首部只是指定了Last-Modified或者Etag),他就会在请求首部加上If-Modified-Since和If-None-Match去请求服务器资源,在服务端会对请求资源的最后修改的时间或者版本号和请求首部If-Modified-Since和If-None-Match的信息进行比较,如果一致表示缓存没有过期,返回304 Not Modified, 直接获取缓存副本,如果不一致,重新请求资源,返回200 OK,这里其实就很清楚了,304 Not Modified是在服务端进行了缓存有效性比较,并且一致性返回的状态码,200 ok是表示访问了服务器的资源

HTTPS

上述的http协议很好的解决了客户端和服务端之间的通信问题,但是同时也有很多缺点,有一些缺点在传输一些很重要的信息时很致命,就是安全性不足,比如

  1. 通信使用明文传输,内容容易被窃听
  2. 不验证通信双方的身份,可以会遭遇伪装
  3. 无法验证报文的完整性,可能会遭到篡改

我们先来说第一点,首先通信明文传输的问题,客户端传输的内容会经过应用层HTTP封装,到传输层TCP封装,再到链路层的IP封装,然后经过各种路由,各种网络设备,才能传输到指定的服务端进行处理,其实从客户端到路由,各种网络设备,再到服务器都有被窃听的可能,他们可以在这里窃听你所传输的任何资源,如果是明文传输的话,你的一切都将不是秘密了,那现在对内容进行加密在通信,这不就可以解决明文传输的隐患了吗?但是这种方式还是会被窃听到,虽然窃听的是加密后的内容,无法破解,但本质上还是被看到了,还是有一定的隐患,其实明文传输的隐患并不是明文,而是传输过程中的窃听,如果我们对对整个通信通道进行加密来屏蔽掉窃听,这样就可以解决窃听的风险了,那怎么对整个通信通道进行加密呢?就是将HTTP协议披上一层加密协议,SSL(Secure Socket Layer 安全套接协议)或者TLS(Transport Layer Security 安全传输协议),在客户端传输内容时,SSL或者TLS会先进行第一次握手,建立加密通道后才能进行通信,这样就能解决明文传输被窃听的不足了

再来说第二点,对于HTTP通信,并不需要知道对方的正是身份,只要客户端发送请求,服务端就会响应,但不知道是谁请求的,这样不明确身份的传输会有很大的隐患,如果是一个伪装的客户端发送了同样的请求,服务器也会响应,如果是一个伪装的服务器在接收客户端的请求,这样信息也会泄漏,那如何解决身份识别的问题呢?这里还要用到加密协议SSL或者TLS,它除了应用于加密功能之外,还有提供一个证书识别来验证身份的功能,每次客户端要发送请求给服务端前必须要下载服务器提供的证书保存在本地,然后每次请求通过响应服务器的证书和自己本地证书进行对比,一致表示自己要访问就是这个网站,这样就形成了身份识别了,证书一般来说由权威的第三方公司提供,当然也可以自己提供,然后自己维护证书的安全性,典型的就是12306网站

第三点是基于第一点和第二点,在通信明文传输中,可能会遭到窃听,那就有可能会遭到篡改(中间人攻击),在传输的过程中,会经过各种网络设备,有些就会在这里监听请求,继而篡改请求报文,或者响应报文,当然通过SSL加密和认证处理可以解决这一问题

针对上述的三个问题,这里就引出了解决HTTP安全性的方法:HTTP+SSL,这也就是HTTPS(HTTP Secure 超文本传输安全协议),具体的加密(非对称加密)和认证(双向认证)这里就不介绍了,具体可以参考《图解HTTP》的第七章和第八章

参考

http://www.jianshu.com/p/37b1258b3b0d
http://www.cnblogs.com/ttltry-air/archive/2012/08/20/2647898.html
https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE#.E6.8C.81.E7.BA.8C.E8.BF.9E.E7.B7.9A
https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn
https://zh.wikipedia.org/wiki/HTTP%E5%A4%B4%E5%AD%97%E6%AE%B5
http://www8.org/w8-papers/5c-protocols/key/key.html
《图解HTTP》