最近互联网这个圈子不是很太平,继阿里缩招降薪,导致很多同学“被拥抱变化”之后,百度也宣布暂时停止社招了。于是有人疾呼“Winter is coming”,有人跟风有人反驳,一时唇枪舌剑,热闹得紧。不过身为一名技术人员,这些言论看看也就是了,市场或许真的会起变化,但也不见得是坏事,大浪淘沙,有能耐的总会留下。人常说站在风口,猪也能飞。如果风真的停了,那摔死的也就是些飞猪。像我这种,压根没站在风口也从没起飞的猪,还是脚踏实地加紧锻炼,争取更快更高更强好了。
唉扯远了,说点实在的吧。Web相关的开发人员应该都知道HTTP协议的重要性,无论是做后端还是前端,安卓还是iOS,都要跟HTTP打交道。想必用Fiddler调试Web API的时候,对返回的各种4xx、5xx状态码感到一头雾水绝不是什么愉快的体验。最近也是复习了一些相关的知识,今天就总结一下。
虽然我们有B/S(Browser/Server)结构、C/S(Client/Server)结构这样的说法来区分浏览器-服务器通信和客户端-服务器通信,不过说到底只要是通过发送请求获取服务器资源的一方,无论是Web浏览器还是移动App抑或是桌面应用,其实都可以算是客户端。而Web使用名为HTTP(HyperText Transfer Protocol,超文本传输协议)的协议作为规范,来完成从客户端到服务器端的一系列操作流程。所谓协议,便是规则的约定,是一些既定的标准,我们这些开发者,只要遵守并好好使用就是了。不过说到HTTP么还是要先说下TCP/IP协议族。
TCP/IP协议族
通常所说的网络(包括互联网),是在TCP/IP协议族的基础上运作的,而HTTP是它的一个子集。TCP/IP协议族可以分为4层,分别是应用层、传输层、网络层和链路层。下面分别介绍一下各层的作用:
- 应用层:决定了向用户提供应用服务时通信的活动。FTP(File Transfer Protocol, 文件传输协议)、DNS(Domain Name System,域名系统)和HTTP都属于该层。
- 传输层:提供处于网络连接中的两台计算机之间的数据传输。TCP(Transmission Control Protocol,传输控制协议)和UDP(User Data Protocol,用户数据协议)都属于该层。
- 网络层:用来处理网络上流动的数据包。数据包是网络传输的最小数据单位。该层规定了通过怎样的路径(所谓的传输路线)到达对方计算机,并把数据包传给对方。与对方计算机之间通过多台计算机或网络设备进行传输时,网络层所起的作用就是在众多的选项内选择一条传输路线。
- 链路层:用来处理网络的硬件部分,包括操作系统、硬件的设备驱动、NIC(Network Interface Card,网卡)、光纤、诸如连接器之类的传输媒介等物理可见部分。
三次握手
我们知道TCP和UDP主要的区别是UDP只负责发送,不确保一定送达;而TCP提供可靠的字节流服务(Byte Stream Service),采用三次握手策略确保数据送达。三次握手的过程使用了TCP 标志——SYN(synchronize)和ACK(acknowledgement):
- 发送端发送一个带SYN标志的数据包给对方。
- 接收端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息。
- 发送端回传一个带ACK标志的数据包,表示握手结束。
HTTP通信
利用TCP/IP协议族进行网络通信时,会通过分层顺序与对方进行通讯。以HTTP举例来说,过程是这样的:
- 客户端在应用层(HTTP协议)发出一项想看某个Web页面的HTTP请求。
- 在传输层(TCP协议)把从应用层处收到的数据(HTTP请求报文)进行分割,并在各个报文上打上标记序号及端口号后转发给网络层。
- 在网络层(IP协议),增加作为通信目的地的MAC地址后转发给链路层。
经过以上步骤,一个网络请求就准备齐全了。经过网络传输之后,接收端的服务器在链路层接收到数据,按序往上层发送,一直到应用层。到了应用层才算真正接收到由客户端发送过来的HTTP请求。
发送端在层与层之间传输数据时,每经过一层必定会被打上一个该层所属的首部信息;反之,接收端在层与层传输数据时,每经过一层时会把对应的首部消去。这种把数据信息包装起来的做法,也叫封装(encapsulate)。
HTTP请求
HTTP协议用于客户端与服务器端之间的通信,协议规定,请求从客户端发出,最后服务器端响应该请求并返回。来看一个请求报文的例子:
GET /search.jsp HTTP/1.1
Host: g.hxgoogle.com
起始行的GET
是一个HTTP动词,也称为方法(method),它可以指定请求的资源按期望产生某种行为,随后的字符串/search.jsp
指明了请求访问的资源对象,称为请求URI(request-URI),HTTP/1.1
即HTTP的版本号,用来提示客户端使用的HTTP协议功能。所以这段报文的意思翻译一下是这样的:请求用GET方法访问域名为g.hxgoogle.com的服务器上的/search.jsp页面资源。
一个完整的请求报文由Header和Body组成,Header包括请求方法、请求URI、协议版本、可选的请求首部字段等,Body指报文主体。下面重点介绍一下请求URI和HTTP方法。
URI和URL
URI表示统一资源标识符,是Uniform Resource Identifier的缩写。RFC2396(RFC,Request for Comments,征求修正意见书,一些指定HTTP协议技术标准的文档)分别对这三个单词进行了如下定义:
- Uniform:规定统一的格式可方便处理多种不同类型的资源,而不用根据上下文环境来识别资源指定的访问方式。另外,加入新增的协议方案(如http:或ftp:)也更容易。
- Resource:资源的定义是“可标识的任何东西”。资源不仅可以是单一的,也可以是个集合体。
- Identifier:用来表示可标识的对象。也称为标识符。
综上所述,URI就是由某个协议方案(如http、ftp)表示的资源的定位标识符。如下列举几种URI的例子:
ftp://ftp.is.co.za/rfc/rfc1808.txt
http://www.ietf.org/rfc/rfc2396.txt
ldap://[2001:db8::7]/c=GB?objectClass?one
mailto:John.Doe@example.com
tel:+1-816-555-1212
URI用字符串标识某一互联网资源,而我们相对来说更熟悉的URL(UniformResource Locator,统一资源定位符)则是表示资源的地点。显然URL是URI的子集,而在我们大部分日常使用场景下,说到URL和URI的时候其实表示的是一个意思。
HTTP方法
我们最常用的HTTP方法是GET和POST,这导致很多人以为HTTP方法只有GET和POST。这是不对的,这些年RESTful API非常流行,所以作为Web开发人员至少还应该知道PUT和DELETE。当然HTTP方法并不只有这么几种,下面介绍几种HTTP/1.1中的方法:
- GET:请求访问已被URI识别的资源,资源经服务器端解析后返回响应内容。
- POST:虽然GET方法也可以在Body中包含内容进行传输,不过一般不用,而是使用POST方法。POST在RESTful架构中一般用来修改资源。
- PUT:用于传输资源到URI指定位置进行保存。由于PUT方法自身不带验证机制,存在安全问题,因此一般Web网站不使用该方法。若配合Web应用程序的验证机制或采用RESTful架构设计,可能会开放使用。PUT在RESTful架构中一般用来添加资源。
- DELETE:删除资源。与PUT情况类似,一般不开放。
- HEAD:获得报文首部(Header),用于确认URI的有效性及资源的更新日期等。
- TRACE:追踪路径。发送请求时,在请求Header中加上Max-Forwards字段,譬如
Max-Forwards: 2
这样,每经过一个服务器就将该数字减1,当数字为0时停止传输,最后接收到请求的服务器返回状态码200 OK响应,响应包含最初的请求内容(将HTTP请求原样返回)。 - CONNECT:要求在于代理服务器通信时建立隧道,用隧道协议进行TCP通信。主要使用SSL(Secure Sockets Layer, 安全套接层)和TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。格式为
CONNECT 代理服务器名:端口号 HTTP版本号
。
HTTP响应
HTTP响应同样可分为Header和Body,它一般长这样:
HTTP/1.1 200 ok
Date: ...
Server: ..
...
空行(CR + LF)
<html>
...
</html>
第一行是状态行,包含HTTP版本、表明响应结果的状态码和原因短语。接下来是一些首部字段,一般包括响应首部字段、通用首部字段、实体首部字段和RFC里未定义的首部(Cookie等),最后是报文主体。下面重点说明一下状态码和原因短语,它们描述了本次请求的结果。
状态码
状态码的第一位数字指定了响应类别,共可分为5类:
- 1XX:Informational(信息性状态码),表明接受的请求正在处理。
- 2XX:Success(成功状态码),表明请求正常处理完毕。
- 3XX:Redirection(重定向状态码),表明需进行附加操作以完成请求。
- 4XX:Client Error(客户端错误状态码),表明服务器无法处理请求。
- 5XX:Server Error(服务器错误状态码),表明服务器处理请求出错。
下面列举几种常见的错误码和原因短语:
- 200 OK:请求正常处理。
- 204 No Content:请求正常处理,但没有资源可返回。
- 206 Partial Content: 客户端进行了范围请求,服务器成功执行这部分GET请求。
- 301 Moved Permanently: 永久性重定向,表明该资源已被分配了新的URI。
- 302 Found: 临时性重定向,表明该资源暂时被分配了新的URI。
- 303 See Other:表明请求的资源存在另一个URI,明确要求客户端采用GET方法重定向请求资源。
- 400 Bad Request:请求报文中存在语法错误,需修改请求内容后再次发送。
- 401 Unauthorized*:请求需包含通过HTTP认证(BASIC认证、DIGEST认证等)的认证信息,浏览器初次接收401响应会弹出认证窗口。若之前已进行过一次请求,则表示用户认证失败。
- 403 Forbidden:请求资源的访问被服务器拒绝。服务器端没有必要给出拒绝的详细理由,不过也可以在响应主体部分对原因进行描述。未获得文件系统的访问授权(比如在IIS上部署网站时默认不能通过浏览器访问文件)、访问权限出现问题(比如从未授权的发送源IP地址试图访问)都有可能返回403响应。
- 404 Not Found:服务器无法找到请求的资源(也可在服务器端拒绝访问且不想说明理由时使用)。
- 500 Internal Server Error:服务器端执行请求时发生内部错误。多为服务器端程序出现Bug。
- 503 Service Unavailable:服务器处于超负载或正在停机维护,暂时无法处理请求。
HTTP协议的一些特性
HTTP协议的初始版本中,每进行一次HTTP通信就要断开一次TCP连接。这在当年都是一些小容量文本传输的情况下是可行的,但随着HTTP的普及,传输过程中包含大量图片的情况多了起来。譬如使用浏览器浏览一个包含多张图片的HTML页面时,在发送请求访问该HTML页面资源的同时,也会请求该页面包含的其他资源如各种不同的图片,它们是在不同服务器上的。如果每次请求都得重新建立一次TCP连接的话,无疑会增加通信量的开销,而且频繁断开又重连会导致页面加载缓慢,影响用户体验。
持久连接(HTTP Persistent Connections)
为了解决上述问题,HTTP/1.1和一部分HTTP/1.0开始支持持久连接。持久连接的特点是,只要任意一端没有明确提出断开连接,则保持TCP连接状态。HTTP/1.1中所有的连接默认都是持久连接。
管线化(pipelining)
持久连接使得多数请求以管线化方式发送成为可能。以往发送请求后需等待并收到响应后才能发送下一个请求,管线化技术出现后,无需等待亦可发送下一个请求。这就实现了多个请求的并行发送,提高了网络通信效率。
Cookie
HTTP是无状态协议,它不对之前发生过的请求和响应状态进行管理。无状态自然可以减少服务器的CPU及内存资源消耗,但有些时候我们又需要对过去的状态进行管理,譬如登录验证之后用户在浏览该网站其他网页时应该保持登录的状态而不是重新进行登录。当然要实现状态管理,我们可以使用很多方法,无外乎是在服务器端和客户端都保存一个凭证,之后每次请求都带上这个凭证,然后在服务器端进行比对,获取状态信息。HTTP协议中引入的Cookie技术,也是为了解决状态管理问题,采用的方法也跟我上面说的差不多,只不过这是发生在协议层面,不需要自己写很多代码管理。
具体来说,Cookie技术通过在请求和响应报文中写入Cookie信息来控制客户端状态,过程如下:
- 客户端第一次发送请求,请求报文中没有Cookie信息。
- 服务器端生成Cookie信息,在响应报文中通过Set-Cookie这个首部字段,通知客户端保存Cookie,大概长这样:
HTTP/1.1 200 ok
...
<Set-Cookie: sid=1345077140226724;path=/;expires=Fri,=>23-Oct-15 07:12:20 GMT>
Content-Type: text/plain; charset=UTF-8
- 客户端再次发送请求时,自动在请求报文中加入Cookie值后发送出去。大概长这样:
GET /image/ HTTP/1.1
Host: github.com
Cookie: sid=1345077140226724
- 服务器端收到Cookie信息后,会去检查从哪个客户端发来的连接请求,然后对比服务器上的记录,得到之前的状态信息。
HTTP协议并不算很复杂,不过涉及的内容也不少。总之,今天先这样吧。