1. HTTP协议简介
网络中传输数据需要面临三个问题:
-
客户端如何知道所请求内容的位置?
-
当客户端知道所请求内容的位置后,如何获取所请求的内容?
-
所请求内容以何种形式组织以便被客户端所识别?
对于网络应用来说解决上述问题的方法分别为:统一资源定位符(URIs),超文本传输协议(HTTP)和超文本标记语言(HTML)。HTTP协议解决文件传输的问题。HTTP是应用层协议,主要建立在TCP协议之上(偶尔也可以UDP为底层)。HTTP协议目的是,如何在万维网的网络环境下,更好的利用TCP协议,以实现文件,特别是超文本文件的传输。
早期的HTTP协议主要传输静态文件,即真实存储在服务器上的文件。随着万维网的发展,HTTP协议被用于传输“动态文件”,服务器上的程序根据HTTP请求即时生成的动态文件。我们将HTTP的传输对象统称为资源(resource)。
HTTP版本介绍:
版 本 | 内 容 |
HTTP 0.9 | HTTP 0.9作为HTTP协议的第一个版本。是非常弱的。请求(Request)只有一行,比如:GET www.cnblogs.com |
HTTP 1.0 | HTTP1.0引入了POST和HTTP头方法,POST方法使客户端可以通过HTML表单向服务器发送数据;HTTP头的应用使HTTP不仅能返回错误代码,并且HTTP协议所传输的内容不仅限于纯文本,还可以是图片,动画等一系列格式。同时一次TCP连接后,可以多次通信。 |
HTTP 1.1 | 2000年5月,HTTP1.1确立;增加了host头如:GET /zlyblog HTTP/1.1 Host: www.cnblogs.com这条修改使得一台主机可以在多个域,通过不同的域名指向同一IP就不会产生混乱。还引入了Range头,使得客户端通过HTTP下载时只下载内容的一部分,这使得多线程下载也成为可能。 |
2. HTTP协议基础
2.1 HTTP协议架构
HTTP属于应用层协议,与FTP,Telnet,TFTP等协议一样都是基于TCP/UDP层的封装。
2.2 客户端与服务端的HTTP通信模型:
分析上图,步骤如下:
- 第一步:客户在浏览器输入URL。
- 第二步:浏览器将URL交给网络栈,网络栈将这个域名发送到DNS上,进行域名解析得到IP地址。
- 第三步:网络栈通过TCP/IP协议与IP服务器建立Socket通信通道。
- 第四步:服务器端的80端口监听客户端的链接,这样客户端到服务器就链接上了。
服务器接收到这些内容之后,并按照这些请求的路径找到对应的页面,进一步找到对应的网页内容,返回给客户端。服务器返回给客户端的内容一般包含下面几种类型:
常见的媒体格式类型 | application开头的媒体格式类型 |
text/html : HTML格式 | application/xhtml+xml :XHTML格式 |
3. HTTP的请求响应模型
HTTP协议的通信是一次request-response交流。客户端(guest)向服务器发出请求(request),服务器(server)回复(response)客户端。
3.1 客户端->服务器(requset):HTTP的请求方法
虽然我们所常见的只有GET和POST方法,但实际上HTTP请求方法还有很多,比如: PUT方法,DELETE方法,HEAD方法,CONNECT方法,TRACE方法。
GET | POST |
|
|
GET和POST的本质区别就是POST有:内容。而Get不存在这个内容。就像GET和POST其名称所示那样,GET用于从服务器上取内容,虽然可以通过QueryString向服务器发信息,但这违背了GET的本意,QueryString中的信息在HTTP看来仅仅是获取所取得内容的一个参数而已。而POST是由客户端向服务器端发送内容的方式。 |
HTTP协议的请求格式:
请求行 (Request URL) 请求头信息 (Request Headers) 请求主体(Request Body)
|
例子:
GET / HTTP/1.1
Host: wap.baidu.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 7.0; sp9850kh_1h10_smtnative Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3048.0 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,en-US;q=0.8
X-Requested-With: com.android.browser
- 请求URL(Request URL)
Request URL就是请求的Url地址,即https://www.baidu.com,该Url没有附加其他的参数。其实可以通过?和&符向URL地址后面追加一系列的键值对参数,比如地址https://www.baidu.com/s?ie=utf-8&wd=Android,该Url包含两个键值对,ie=utf-8,以及wd=Android,ie和wd是key,utf-8和Android分别是其对应的value,服务端可以获取ie和wd所对应的value的值。由此我们可以看出,Url可以携带额外的数据信息。一般情况下,URL的长度不能超过2048个字符,即2KB,超过此限制的话服务器可能就不识别。
- 请求头(Request Headers)
Request Headers部分就是请求头,请求头其实也是一些键值对,不过这些键值通常都是W3C定义了的一些标准的Http请求头的名称,请求头包含了客户端想告诉服务端的一些元数据信息,注意是元数据,而不是数据,比如请求头User-Agent会告诉服务器这条请求来自于什么浏览器,再比如请求头Accept-Encoding会告诉服务器客户端支持的压缩格式。除了这些标准的请求头,我们还可以添加自定义的请求头。
- 请求体(Request Body)
之前我们提到,URL的最大长度就是2048个字符,如果我们发送的数据很大,超过了2KB怎么办?我们可以将很大的数据放到请求体中,GET请求不支持请求体,只有POST请求才能设置请求体(Request Body) 。请求体中可以放置任意的字节流,从而可以很方便地发送任意格式的数据,服务端只需要读取该输入流即可。
3.2 服务器->客户端(response)HTTP的响应(HTTP Response)
当Web服务器收到HTTP请求后,会根据请求的信息做某些处理(这些处理可能仅仅是静态的返回页,或是包含Asp.net, PHP, Jsp等语言进行处理后返回),相应的返回一个HTTP响应。HTTP响应在结构上很类似于HTTP请求,也是由三部分组成,分别为:
响应行(Response) 响应头信息(Response Headers) 响应主体(Response Body) |
例子:
HTTP/1.1 200 OK
Cache-Control: no-cache
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Coremonitorno: 0
Date: Mon, 03 Sep 2018 09:15:15 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Server: apache
Set-Cookie: BAIDUID=BAE663625238FEFEE2C142BD23C6EEDD:FG=1; max-age=31536000; expires=Tue, 03-Sep-19 09:15:15 GMT; domain=.baidu.com; path=/; version=1; comment=bd
Set-Cookie: H_WISE_SIDS=125704_114550_122156_125093_114744_123799_124828_125405_120158_123019_125590_118890_118877_118856_118819_118802_107318_125007_124977_117432_122789_125653_124619_125312_125737_124560_125487_125171_114819_124524_125561_124030_110085_123290_115258_125644_125428; path=/; domain=.baidu.com
Set-Cookie: bd_traffictrace=031715
Set-Cookie: BDSVRTM=271; path=/
Set-Cookie: eqid=deleted; path=/; domain=.baidu.com; expires=Thu, 01 Jan 1970 00:00:00 GMT
Tracecode: 09153992771221736970090317
Tracecode: 09151360120376477194090317
Traceid: 153596611504579066986997994886987433580
Vary: Accept-Encoding
Transfer-Encoding: chunked
- 响应行
响应行包含了 HTTP版本,如HTTP/1.1,以及状态码 200 OK。第一部分是HTTP版本,第二部分是响应状态码,第三部分是状态码的描述,因此也可以把第二和第三部分看成一个部分。
响应行 (Response )
状态码包含以下几类:
- 信息类 (100-199)
- 响应成功 (200-299)
- 重定向类 (300-399)
- 客户端错误类 (400-499)
- 服务端错误类 (500-599)
- 响应头信息(Response Headers)
HTTP响应中包含的头包括:
- 响应头(response header)
- 普通头(general header)
- 实体头(entity header)
响应头包含了服务器想要告诉客户端的一些元数据信息,注意不是数据,是元数据,比如通过响应头Content-Encoding告诉客户端服务器所采用的压缩格式,响应头Content-Type告诉客户端响应体是什么格式的数据,再比如服务端可以通过多个Set-Cookie响应头向客户端写入多条Cookie信息,等等。刚刚提到的几个请求头都是W3C规定的标准的请求头名称,我们也可以在服务端向客户端写入自定义的响应头。
- 响应主体(Response Body)
响应体是服务端向客户端传输的实际的数据信息,本质就是一堆字节流,可以表示文本,也可以表示图片或者其他格式的信息,如下
既然HTTP响应的内容不仅仅是HTML,还可以是其它类型,那么浏览器如何正确对接收到的信息进行处理?这是通过媒体类型确定的(Media Type),具体来说对应Content-Type这个HTTP头:
媒体类型的格式为:大类/小类。IANA(The Internet Assigned Numbers Authority,互联网数字分配机构)定义了8个大类的媒体类型,分别是:
- application (比如: application/vnd.ms-excel.)
- audio (比如: audio/mpeg.)
- image (比如: image/png.)
- message (比如: message/http.)
- model (比如: model/vrml.)
- multipart (比如: multipart/form-data.)
- text (比如: text/html.)
- video (比如:video/quicktime.)
4. HTTP会话状态保持
HTTP协议是无状态的,这意味着对于接收HTTP请求的服务器来说,并不知道每一次请求来自同一个客户端还是不同客户端,每一次请求对于服务器来说都是一样的。因此需要一些额外的手段来使得服务器在接收某个请求时知道这个请求来自于某个客户端。
4.1 通过Cookies保持状态
为了解决这个问题,HTTP协议通过Cookies来保持状态,就是在浏览器中存储用户信息,每次请求都向服务端发送这些信息,这样服务端就知道请求发送者是谁了,就知道应该返回什么信息给客户了。缺点是不安全,如张三冒充李四的名字发送请求给服务器,服务器把李四的相关信息发给了张三。
4.2 通过表单变量保持状态
除了Cookies之外,还可以使用表单变量来保持状态,比如Asp.net就通过一个叫ViewState的Input=“hidden”的框来保持状态,比如:
<input type="hidden" name="__VIEWSTATE" value="/wEPDwUKMjA0OTM4MTAwNGRkXUfhlDv1Cs7/qhBlyZROCzlvf5U=" />
这个原理和Cookies大同小异,只是每次请求和响应所附带的信息变成了表单变量。
4.3 通过QueryString保持状态
这个原理和上述两种状态保持方法原理是一样的,QueryString通过将信息保存在所请求地址的末尾来向服务器传送信息,通常和表单结合使用,一个典型的QueryString比如:www.xxx.com/xxx.aspx?var1=value&var2=value2
4.4 通过session保持
就是把客户信息存储在服务端(session),一切用户的身份由服务器指定。Session是怎样做到会话身份识别的呢?首先,用户端向服务端发送一个请求,服务端接收到请求(这里忽悠无须会话控制的情况)后,初始化会话,生成相应的会话信息,核心是会话ID,把会话ID发送给客户端,客户端接收到这个会话ID,把它存储起来,下一次发送请求的时候,附带着这个会话ID一起发送给服务端,服务端只要根据这个会话ID,就知道是谁了。
5. 断点续传
HTTP的断点续传实际上实现非常简单,只要在请求中加一个Range字段就可以了。假如一个文件有1000个字节,那么其范围就是0-999,则:
- Range: bytes=500- 表示读取该文件的500-999字节,共500字节
- Range: bytes=500-599 表示读取该文件的500-599字节,共100字节
Range还有其它几种写法,但上面这两种是最常用的。如果HTTP请求中包含Range字段,那么服务器会返回206(Partial Content),同时HTTP头中也会有一个相应的Content-Range字段,类似下面的格式:
Content-Range: bytes 500-999/1000
Content-Range字段说明服务器返回了文件的某个范围及文件的总长度。这时Content-Length字段就不是整个文件的大小了,而是对应文件这个范围的字节数,这一点一定要注意。
HTTP断点续传对于直接下载的地址是可以如上正常实现的,对于重定向的下载地址如:http://download.pchome.net/php/tdownload2.php?sid=5547&url=/multimedia/viewer/acdc31sr1b051007.exe&svr=1&typ=0这种地址并没有直接标识文件的位置,而是通过程序进行了重定向。如果向服务器请求这样的URL,服务器就会返回302(Moved Temporarily),意思就是需要重定向,同时在HTTP头中会包含一个Location字段,Location字段的值就是重定向后的目的URL。这时就需要断开当前的连接,而向这个重定向后的服务器发请求。