HTTP——超级文本传输协议,用于传输超媒体文档(如Html)的应用层协议,典型的应用场景是浏览器网页、API接口服务等,它可以传输文本、图片、文件、视频等。
HTTP版本简介
HTTP 0.9
该版本只有一个GET命令,协议规定服务器只能响应HTML格式的字符串,不能响应别的格式,服务器发送完毕就关闭TCP连接。虽然这一版本很简单,但是作为一个原型,奠定了Web服务的可行性。
HTTP 1.0
主要增加以下内容:
- 增加HEAD/POST等新请求方法
- 增加响应状态码
- 增加版本号
- 增加Header头部的概念
- 增加Content-Type,传输数据不再限于文本
此版本并非一个标准,只是记录已有实践和模式的一份参考文档,不具有实际的约束力。
HTTP 1.1
主要增加以下内容: - 增加PUT/DELETE/OPTIONS等新方法
- 增加缓存控制和管理Cache-Control
- 明确连接管理,允许持久连接Keepalive
- 允许响应数据分块,利于传输大文件(Chunked)
- 强制要求Host头
由于此版本过于复杂和庞大,因此2014年又进行了一次修订,拆分为六份较小的文档
这六份文档增加了两个大的需求: - 加大了HTTP的安全性,比如使用了TLS协议
- 让HTTP可以支持更多的应用,目前已经支持四种网络协议:
- 传统的短连接
- 可重用的TCP长连接模型
- 服务端PUSH模型
- WebSocket模型
HTTP 2.0
HTTP1.1存在两个问题:
1、连接慢,请求是串行的,需要保证顺序,例如一个网页中可能会有多个资源
2、性能能差,HTTP 1.1 是以文本的方式,借助CPU的zip压缩方法减少网络带宽,但是耗费了前端和后端的CPU
2010年谷歌推出了新的SPDY协议,并应用于自己服务器,HTTP 2.0就是以SPDY为基础的,它的主要特点有:
- 使用二进制传输,不再是纯文本
- 可以在一个TCP连接中并发多个HTTP请求,移除HTTP 1.1中的串行请求
- 使用HPACK算法压缩头部
- 运行服务器主动向客户端推送数据
- 增强了安全性,基于TLS协议
HTTP 3.0
HTTP 2.0的主要问题有队头阻塞问题,也就是说,若干个HTTP请求在复用一个TCP的连接,那么一旦丢包,造成的问题就是所有的请求必须等待所有的请求都必须等待这个丢了的包重传回来哪怕这个包不是当前HTTP请求的。
基于此,谷歌发明了QUIC(QUICK UDP Internet Connections)协议,它基于UDP,它解决以下这几个问题: - UDP 是无序的,因此不存在队头阻塞问题
- QUIC有一套自己的丢包重传和拥塞控制的协议
- HTTPS握手通常需要6次网络交互,QUIC直接将TLS和TCP合并成三次握手
HTTP协议报文格式
HTTP请求和响应的协议报文格式是有点差别的,下面分别分开介绍:
HTTP请求协议报文格式
Charles抓包内容如下:
GET /kd_face/nMl9ssowtibXiaiajOk17ibqFiau1N5PKEstSxfLadEkKbwE/200 HTTP/1.1
Host: p.qpic.cn
User-Agent: Mozilla/5.0 (Linux; U; Android 11; zh-cn; M2007J17C Build/RKQ1.200826.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/12.1 Mobile Safari/537.36 X5Lite/052151 QbInfoApp GDTTangramMobSDK/8.400 GDTMobSDK/8.400
Q-SID: 1639042205
Accept: image/sharpp,image/*,*/*;q=0.8
Accept-Encoding: gzip, deflate
请求协议报文格式分四大部分:
1、请求行:
- 请求方法:有GET/POST/HEAD/OPTIONS/DELETE等
- 路径: 指明请求服务器具体哪个资源
- HTTP版本:如HTTP/1.1、HTTP/2.0等,指明当前请求使用的是HTTP版本
2、请求头部:
请求头部是一系列key:value\r\n,具体的将在下面的内容进行讲解
3、空行:
请求头部后面必须跟一个空行(\r\n)
4、请求内容:
请求内容是传递给服务器的数据,GET方法无请求内容
HTTP响应协议报文格式
Charles抓包的响应内容如下:
HTTP/1.1 200 OK
Server: ImgHttp3.0.0
Vary: Accept,Origin
Content-Type: image/jpeg
Content-Length: 4609
Last-Modified: Wed, 01 Dec 2021 11:03:10 GMT
Cache-Control: max-age=2592000
X-Delay: 329 us
X-Info: real data
X-BCheck: 0_1
X-Cpt: filename=0
User-ReturnCode: 0
X-DataSrc: 2
X-ReqGue: 0
Size: 4609
chid: 0
fid: 0
Proxy-Connection: Keep-alive
ÿØÿà
响应协议报文格式分四大部分:
1、状态行:
- HTTP版本:如:HTTP/1.1、HTTP/2.0等,表明当前响应内容使用的是哪个HTTP版本,一般与请求时的版本一致
- 状态码:表明响应的状态:成功(200)、错误等
- 状态描述信息:对应状态码的描述信息
2、响应头部:
响应头部是一系列key:value\r\n,具体的将在下面的内容进行讲解
3、空行:
响应头部后面必须跟一个空行(\r\n)
4、响应内容:
响应请求的内容,响应成功下,该部分是服务器返回请求需要的数据
HTTP协议解析
以下将针对HTTP协议报文格式中各个重要部分进行讲解,包括响应状态行中的状态码、HTTP头部、HTTP头部缓存、HTTP头部状态管理、HTTP头部编码和解码、HTTP头部身份认证、HTTP长短连接、HTTP头部代理其及原理等。
HTTP请求方法
幂等:连续一次或多次请求得到结果一样(无副作用)
GET: GET方法请求指定资源的表示形式,使用GET的请求应该只检索数据,是幂等,可缓存。一般定位为获取资源,而不更改服务端资源和状态,所以不论请求多少次结果都是一样的。GET方法可以提交参数(一般用作检索参数),会被附加到URL的后面,如:http://localhost/test?param1=value1¶m2=value2
HEAD: HEAD方法与GET类似,只是返回的响应中没有具体内容,是幂等,可缓存。用于获取头部,而且这些头部与使用GET方法请求时返回的头部一致。一般使用场景是:在下载一个大文件前,先获取获取头部的信息如大小(Content-length)再决定是否要下载,以此节约带宽资源。
POST: POST方法提交实体到指定的资源,一般提交的实体用于会引起资源的变化或产生副作用(添加、修改资源、出现异常等),连续一次或多次调用同一个POST可能带来额外的影响,比如多次提交订单,得到结果是不一样的,所有它是非幂等,且不缓存。POST提交的实体内容不会拼接在URL后面,而是填充协议中的body部分,所以相比GET方式而已长度不受限制、更为安全
PUT: 使用请求中的负载创建或者替换目标资源。与POST相似,但PUT是幂等的,即连续一次或多次调用同一个PUT得到的结果是一样的。可以理解PUT为覆盖,无则添加,有则覆盖。如果提交的PUT请求目标资源不存在,且服务端成功创建了,则服务端返回201表示客户端请求的资源已创建;如果提交的资源已存在,并且依照请求中封装的表现形式成功进行了更新,那么服务器必须返回200(ok)或204(No Content)来表示请求成功完成。
PATCH: PATCH属于局部修改,非幂等,不可缓存。常见的比如userInfo(有id、用户名、年龄等),有的界面只修改年龄,此时使用PATCH只需要提交年龄的参数到服务端,服务端只修改接收到的参数,这样不用提交整个userInfo。PUT也是更改,区别是PUT是需要提交真个实体去修改,比如要提交完整的userInfo实体,如果userInfo中其他字段为空,那么服务端的userInfo的相关字段会被清空。
DELETE: 用于删除指定的资源,是幂等
CONNECT: 用于建立到目标资源标识的服务器的隧道,即建立客户端与请求资源之间的双向通信
OPTIONS: 用于获取目的资源所支持的通信选项。没有请求实体、不可缓存,它一般用于检索服务器支持的请求方法
TRACE: 实现沿通向目标资源的路径的消息环回,提供一种实用的debug机制。请求的最终接收者应当原样返回它收到的消息,除了一下字段部分,作为一个Content-Type为message/http的200(OK)响应的消息的主体(body)返回给客户端。是幂等且不可缓存
HTTP状态码
HTTP状态码表示当前响应的状态,由三个数字组成,第一位数字表示响应的类别,有五大类别:
1xx: 信息状态码,表示服务端已收到请求,需要请求在继续执行操作
2xx: 成功状态码,表示请求正常处理完成
3xx: 重定向状态码,表示客户端需要进一步的操作以完成请求
4xx: 客户端错误状态码,表示客户端请求有语法错误或无法完成请求
5xx: 服务端错误状态码,表示服务端处理请求时出现错误
常见的错误码如下:
状态码 | 状态码描述信息 | 描述 |
---|---|---|
200 | OK | 请求已成功,请求所希望的响应头和数据体将随此响应返回 |
202 | Accepted | 已接收,请求已接收,但未完成处理 |
206 | Partial Content | 部分内容,服务器成功处理部分请求 |
301 | Moved Parmanently | 永久移动,请求的资源已被永久移动到新的URI,返回信息包含新的URI |
302 | Found | 临时移动,与301相似,但资源只是临时移动,客户端应该继续使用原URI |
400 | Bad Request | 客户端请求语法错误,服务器无法理解 |
401 | Unauthorized | 未认证,请求要求用户的身份认证 |
403 | Forbidden | 服务器理解客户端请求,但拒绝执行此请求 |
404 | Not Found | 服务器无法根据客户端的请求找到对应资源(网页或者接口) |
500 | Internal Server Error | 服务器处理请求出现内部错误,无法完成请求 |
502 | Bad Gateway | 充当网关或代理的服务器,从远端服务器接收到了一个无效的请求,一般是网关或端口错误 |
更多状态码请详见:HTTP状态码
HTTP头部
HTTP协议中的头部是key/value的形式,它们有的为请求服务,有的为响应服务,有的为实体服务,基于此,我们将一下常用头发的进行划分:
通用头部
这部分的头部可用于请求和响应的头部,但不可以是应用于实体的头部
字段名 | 说明 |
---|---|
Cache-Control | 控制缓存的行为,将在缓存部分进行讲解 |
Connection | 决定当前事务完成之后,是否关闭连接(即决定连接是长连接还是短连接)。=keep-alive表示连接是之久的,完成事务后不会关闭连接,它是HTTP/1.1的默认值;=close表示短连接,完成事务后关闭连接,是HTTP/1.0的默认值。Connection不能与HTTP/2一起使用 |
Date | 报文创建的日期和时间,格式:<day-name>,<day>,<month>,<year> <hour>:<minute>:<second> GMT。例如:Wed, 21 Oct 2015 07:28:00 GMT |
请求报文头部
这部分主要用于请求时的头部
字段名 | 说明 |
---|---|
Accept | 请求头用来告知客户端可以处理的内容类型,格式:MINE_TYPE/MINE_SUB_TYPE[;q=权重因子,代表优先级],如:/、image/*、text/html, application/xhtml+xml;q=0.9。服务器会根据内容协商机制(下文会进行说明)从诸多选项中选择合适的一项进行应用,并使用Content-Type应答头通知客户端它的选择 |
Accept-Charset | 请求头用来告知服务端客户端可以处理的字符集类型,服务端会截止内容协商机制(下文进行说明)从诸多选项中选择一项合适的进行处理,并使用Content-Type响应头告知客户端它的选择。 例如:Accept-Charset:utf-8,iso-8859-1;q=0.5, *;q=0.1 |
Accept-Encoding | 将客户端能够理解的内容编码方式(某种压缩算法)告知服务端,服务端通过内容协商机制,选择一个客户端提议的方式,并使用响应头Content-Encoding通知客户端它的选择。值有:gzip、compress、deflate、br、identity、、;q=权重因子。例如:Accept-Encoding: gzip, compress;q=0.1,;q=0.2 |
Accept-Language | 声明客户端可以理解的自然语言以及优先选择的区域方言。服务端借助内容协商机制从诸多选项中选择一项合适的进行应用,并使用Content-Language响应头通知客户端它的选择。值有:<language>(含2-3个字符的字符串表示语言或者完整的语言标签,可以使用-来表明方言,如en-US)、<*>(任意语言)、<;q=权重因子>。例如:Accept-Language: en-US, *;q=0.1 |
Authorization | 被用于服务端验证用户代理身份的凭证,通常会在服务器返回401状态码以及WWW-Authenticate头之后,在后续的请求中发生此消息头。格式:Authorization: <验证类型> <凭证>。验证类型:BASIC、DIGEST、SSL等。例如:Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l |
Expect | 包含一个期望条件,表示服务器只有满足此期望条件的情况下才能妥善地处理请求。 值只有:100-continue,对此服务端可以响应:如果服务端可以满足期望条件,响应100状态码,告知客户端继续发送消息;如果服务器不能满足期望条件,响应417告知客户端请求不能得到满足。应用场景一般是客户端请求的内容过大,如上传大文件,可以先发送一个请求含Expect和Content-Length,在得到100的响应之后,再继续发送内容。 |
From | 邮箱地址,一般用于出现问题服务端或者管理员联系到请求发送者 |
Host | 指明请求将要发送到的服务器主机名和端口号,如果没有端口号,http默认使用80,https默认使用443。HTTP/1.1中必须包含一个Host字段,如果HTTP/1.1 缺少或超过一个Host字段,可能会收到400 |
If-Match | 请求首部 If-Match 的使用表示这是一个条件请求。在请求方法为 GET 和 HEAD 的情况下,服务器仅在请求的资源满足此首部列出的 ETag 值时才会返回资源。而对于 PUT 或其他非安全方法来说,只有在满足条件的情况下才可以将资源上传。ETag 之间的比较使用的是强比较算法,即只有在每一个字节都相同的情况下,才可以认为两个文件是相同的。在 ETag 前面添加 W/ 前缀表示可以采用相对宽松的算法。以下是两个常见的应用场景:* 对于 GET 和 HEAD 方法,搭配 Range 首部使用,可以用来保证新请求的范围与之前请求的范围是对同一份资源的请求。如果 ETag 无法匹配,那么需要返回 416 (Range Not Satisfiable,范围请求无法满足) 响应。* 对于其他方法来说,尤其是 PUT , If-Match 首部可以用来避免更新丢失问题。它可以用来检测用户想要上传的不会覆盖获取原始资源之后做出的更新。如果请求的条件不满足,那么需要返回 412 (Precondition Failed,先决条件失败) 响应。例子:If-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d" 或 If-Match: W/"67ab43", "54ed21", "7892dd" 或 If-Match: * |
If-Modified-Since |
If-Modified-Since 是一个条件式请求首部,服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 。如果请求的资源从那时起未经修改,那么返回一个不带有消息主体的 304 响应,而在 Last-Modified 首部中会带有上次修改时间。 不同于 If-Unmodified-Since , If-Modified-Since 只可以用在 GET 或 HEAD 请求中。当与 If-None-Match 一同出现时,它(If-Modified-Since )会被忽略掉,除非服务器不支持 If-None-Match 。最常见的应用场景是来更新没有特定 ETag 标签的缓存实体。语法:If-Modified-Since: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT。例子:If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT |
If-None-Match |
If-None-Match 是一个条件式请求首部。对于 GETGET 和 HEAD 请求方法来说,当且仅当服务器上没有任何资源的 ETag 属性值与这个首部中列出的相匹配的时候,服务器端才会返回所请求的资源,响应码为 200 。对于其他方法来说,当且仅当最终确认没有已存在的资源的 ETag 属性值与这个首部中所列出的相匹配的时候,才会对请求进行相应的处理。对于 GET 和 HEAD 方法来说,当验证失败的时候,服务器端必须返回响应码 304 (Not Modified,未改变)。对于能够引发服务器状态改变的方法,则返回 412 (Precondition Failed,前置条件失败)。需要注意的是,服务器端在生成状态码为 304 的响应的时候,必须同时生成以下会存在于对应的 200 响应中的首部:Cache-Control、Content-Location、Date、ETag、Expires 和 Vary 。ETag 属性之间的比较采用的是弱比较算法,即两个文件除了每个字节都相同外,内容一致也可以认为是相同的。例如,如果两个页面仅仅在页脚的生成时间有所不同,就可以认为二者是相同的。当与 If-Modified-Since 一同使用的时候,If-None-Match 优先级更高(假如服务器支持的话)。以下是两个常见的应用场景:* 采用 GET 或 HEAD 方法,来更新拥有特定的ETag 属性值的缓存 * 采用其他方法,尤其是 PUT ,将 If-None-Match used 的值设置为 * ,用来生成事先并不知道是否存在的文件,可以确保先前并没有进行过类似的上传操作,防止之前操作数据的丢失。这个问题属于更新丢失问题的一种。语法:If-None-Match: <etag_value>, <etag_value>, … 或 If-None-Match: * |
If-Range |
If-Range HTTP 请求头字段用来使得 Range 头字段在一定条件下起作用:当字段值中的条件得到满足时,Range 头字段才会起作用,同时服务器回复206 部分内容状态码,以及Range 头字段请求的相应部分;如果字段值中的条件没有得到满足,服务器将会返回 200 OK 状态码,并返回完整的请求资源。字段值中既可以用 Last-Modified 时间值用作验证,也可以用ETag 标记作为验证,但不能将两者同时使用。If-Range 头字段通常用于断点续传的下载过程中,用来自从上次中断后,确保下载的资源没有发生改变。语法:If-Range: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT 或 If-Range: <etag> |
If-Unmodified-Since | HTTP协议中的 If-Unmodified-Since 消息头用于请求之中,使得当前请求成为条件式请求:只有当资源在指定的时间之后没有进行过修改的情况下,服务器才会返回请求的资源,或是接受 POST 或其他 non-safe 方法的请求。如果所请求的资源在指定的时间之后发生了修改,那么会返回 412 (Precondition Failed) 错误。常见的应用场景有两种:* 与 non-safe 方法如 POST 搭配使用,可以用来优化并发控制,例如在某些wiki应用中的做法:假如在原始副本获取之后,服务器上所存储的文档已经被修改,那么对其作出的编辑会被拒绝提交。* 与含有 If-Range 消息头的范围请求搭配使用,用来确保新的请求片段来自于未经修改的文档。语法:If-Unmodified-Since: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT |
Proxy-Authorization |
Proxy-Authorization 是一个请求首部,其中包含了用户代理提供给代理服务器的用于身份验证的凭证。这个首部通常是在服务器返回了 407 Proxy Authentication Required 响应状态码及 Proxy-Authenticate 首部后发送的。语法:Proxy-Authorization: <type> <credentials>。 |
Range | The Range 是一个请求首部,告知服务器返回文件的哪一部分。在一个 Range 首部中,可以一次性请求多个部分,服务器会以 multipart 文件的形式将其返回。如果服务器返回的是范围响应,需要使用 206 Partial Content 状态码。假如所请求的范围不合法,那么服务器会返回 416 Range Not Satisfiable 状态码,表示客户端错误。服务器允许忽略 Range 首部,从而返回整个文件,状态码用 200 。语法:Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>...。unit: 范围所采用的单位,通常是字节(bytes);range-start:一个整数,表示在特定单位下,范围的起始值;range-end:一个整数,表示在特定单位下,范围的结束值。这个值是可选的,如果不存在,表示此范围一直延伸到文档结束。例子:Range: bytes=200-1000, 2000-6576, 19000- |
Referer | Referer 请求头包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 Referer 请求头识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。语法:Referer: <url>。例如:Referer: https://developer.mozilla.org/en-US/docs/Web/JavaScript |
TE |
**TE** 请求型头部用来指定用户代理希望使用的传输编码类型。(可以将其非正式称为 Accept-Transfer-Encoding , 这个名称显得更直观一些)。可以参考 Transfer-Encoding 来获取更多关于传输编码的细节信息。值得注意的是, 支持 HTTP/1.1 协议的接收方一定可以处理 chunked 传输编码请求,所以没有必要一定在 TE 首部指定“chunked”关键字。然而,如果客户端将要接收编码在chunked包体里面的"trailer"信息的时候,主动指定该头部将会非常有用。 |
User-Agent | User-Agent 首部包含了一个特征字符串,用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。语法:User-Agent: <product> / <product-version> <comment>。例如:Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 |
响应报文头部
这部分主要用于响应时的头部
字段名 | 说明 |
---|---|
Accept-Ranges | 服务器使用 HTTP 响应头 Accept-Ranges 标识自身支持范围请求(partial requests)。字段的具体值用于定义范围请求的单位。当浏览器发现 Accept-Ranges 头时,可以尝试继续中断了的下载,而不是重新开始。语法:Accept-Ranges: bytes 或 Accept-Ranges: none。 |
Age | Age 消息头里包含对象在缓存代理中存贮的时长,以秒为单位。Age的值通常接近于0。表示此对象刚刚从原始服务器获取不久;其他的值则是表示代理服务器当前的系统时间与此应答中的通用头 Date 的值之差。语法:Age: <delta-seconds>(一个非负整数,表示对象在缓存代理服务器中存贮的时长,以秒为单位。) |
Content-Range | 在HTTP协议中,响应首部 Content-Range 显示的是一个数据片段在整个文件中的位置。语法:Content-Range: <unit> <range-start>-<range-end>/<size>(或<unit> <range-start>-<range-end>/*或<unit> /<size>)。<unit>:数据区间所采用的单位。通常是字节(byte)。<range-start>:一个整数,表示在给定单位下,区间的起始值。<range-end>:一个整数,表示在给定单位下,区间的结束值。<size>:整个文件的大小(如果大小未知则用""表示)。例如:Content-Range: bytes 200-1000/67589 |
ETag | ETagHTTP响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web服务器不需要发送完整的响应。而如果内容发生了变化,使用ETag有助于防止资源的同时更新相互覆盖(“空中碰撞”)。如果给定URL中的资源更改,则一定要生成新的Etag值。 因此Etags类似于指纹,也可能被某些服务器用于跟踪。 比较etags能快速确定此资源是否变化,但也可能被跟踪服务器永久存留。语法:ETag: W/"<etag_value>" 或 ETag: "<etag_value>"。'W/' (大小写敏感) 表示使用弱验证器。 弱验证器很容易生成,但不利于比较。 强验证器是比较的理想选择,但很难有效地生成。 相同资源的两个弱Etag 值可能语义等同,但不是每个字节都相同。<etag_value>":实体标签唯一地表示所请求的资源。 它们是位于双引号之间的ASCII字符串(如“675af34563dc-tr34”)。 没有明确指定生成ETag值的方法。 通常,使用内容的散列,最后修改时间戳的哈希值,或简单地使用版本号。 例如,MDN使用wiki内容的十六进制数字的哈希值。 |
Expires | Expires 响应头包含日期/时间, 即在此时候之后,响应过期。无效的日期,比如 0, 代表着过去的日期,即该资源已经过期。如果在Cache-Control 响应头设置了 "max-age" 或者 "s-max-age" 指令,那么 Expires 头会被忽略。语法:Expires: <http-date>。例如:Expires: Wed, 21 Oct 2015 07:28:00 GMT |
Last-Modified | The Last-Modified 是一个响应首部,其中包含源头服务器认定的资源做出修改的日期及时间。 它通常被用作一个验证器来判断接收到的或者存储的资源是否彼此一致。由于精确度比 ETag 要低,所以这是一个备用机制。包含有 If-Modified-Since 或 If-Unmodified-Since 首部的条件请求会使用这个字段。语法:Last-Modified: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT |
Location | Location 首部指定的是需要将页面重新定向至的地址。一般在响应码为3xx的响应中才会有意义。发送新请求,获取Location指向的新页面所采用的方法与初始请求使用的方法以及重定向的类型相关:* 303 (See Also) 始终引致请求使用 GET 方法,而,而 307 (Temporary Redirect) 和 308 (Permanent Redirect) 则不转变初始请求中的所使用的方法;* 301 (Permanent Redirect) 和 302 (Found) 在大多数情况下不会转变初始请求中的方法,不过一些比较早的用户代理可能会引发方法的变更(所以你基本上不知道这一点)。状态码为上述之一的所有响应都会带有一个Location首部。除了重定向响应之外, 状态码为 201 (Created) 的消息也会带有Location首部。它指向的是新创建的资源的地址。语法:Location: <url> |
Proxy-Authenticate | The HTTP Proxy-Authenticate 是一个响应首部,指定了获取 proxy server (代理服务器)上的资源访问权限而采用的身份验证方式。代理服务器对请求进行验证,以便它进一步传递请求。Proxy-Authenticate 首部需要与 407 Proxy Authentication Required 响应一起发送。
|
Retry-After | 在HTTP协议中,响应首部 Retry-After 表示用户代理需要等待多长时间之后才能继续发送请求。这个首部主要应用于以下两种场景:* 当与 503 (Service Unavailable,当前服务不存在) 响应一起发送的时候,表示服务下线的预期时长;* 当与重定向响应一起发送的时候,比如 301 (Moved Permanently,永久迁移),表示用户代理在发送重定向请求之前需要等待的最短时间。语法:Retry-After: <http-date>(表示在此时间之后可以重新尝试。参见 Date 首部来获取HTTP协议中关于日期格式的细节信息);Retry-After: <delay-seconds>(一个非负的十进制整数,表示在重试之前需要等待的秒数) |
Server | Server 首部包含了处理请求的源头服务器所用到的软件相关信息。应该避免使用过长或者过于详细的描述作为 Server 的值,因为这有可能泄露服务器的内部实现细节,有利于攻击者找到或者探测已知的安全漏洞。例子:Server: Apache/2.4.1 (Unix) |
Vary |
Vary 是一个HTTP响应头部信息,它决定了对于未来的一个请求头,应该用一个缓存的回复(response)还是向源服务器请求一个新的回复。它被服务器用来表明在 content negotiation algorithm(内容协商算法)中选择一个资源代表的时候应该使用哪些头部信息(headers).在响应状态码为 304 Not Modified 的响应中,也要设置 Vary 首部,而且要与相应的 200 OK 响应设置得一模一样。语法:Vary: *(所有的请求都被视为唯一并且非缓存的,使用Cache-Control : no-store ,来实现则更适用,这样用于说明不存储该对象更加清晰。);Vary: <header-name>, <header-name>, ...(逗号分隔的一系列http头部名称,用于确定缓存是否可用。) |
WWW-Authenticate | HTTP WWW-Authenticate 响应头定义了使用何种验证方式去获取对资源的连接。WWW-Authenticate header通常会和一个 401 Unauthorized 的响应一同被发送。语法:WWW-Authenticate: <type> realm=<realm> |
实体报文头部
这部分主要用于实体即body的头部
字段名 | 说明 |
---|---|
Allow | Allow 首部字段用于枚举资源所支持的 HTTP 方法的集合。若服务器返回状态码 405 Method Not Allowed,则该首部字段亦需要同时返回给客户端。如果 Allow 首部字段的值为空,说明资源不接受使用任何 HTTP 方法的请求。这是可能的,比如服务器需要临时禁止对资源的任何访问。例如:Allow: GET, POST, HEAD |
Content-Encoding | Content-Encoding 是一个实体消息首部,用于对特定媒体类型的数据进行压缩。当这个首部出现的时候,它的值表示消息主体进行了何种方式的内容编码转换。这个消息首部用来告知客户端应该怎样解码才能获取在 Content-Type 中标示的媒体类型内容。一般建议对数据尽可能地进行压缩,因此才有了这个消息首部的出现。不过对于特定类型的文件来说,比如jpeg图片文件,已经是进行过压缩的了。有时候再次进行额外的压缩无助于负载体积的减小,反而有可能会使其增大。语法:Content-Encoding: gzip(或compress 或deflate或identity或br) |
Content-Language |
Content-Language 是一个 entity header (实体消息首部),用来说明访问者希望采用的语言或语言组合,这样的话用户就可以根据自己偏好的语言来定制不同的内容。举个例子,假如设置了这样一条消息首部( "Content-Language: de-DE " ),那么说明这份文件是为说德语的人提供的(当然这并不意味着文件本身就是用德语写的。比如,它可能是为说德语的人开设的英语教程的一部分,也就是用英语写的)。如果没有指明 Content-Language ,那么默认地,文件内容是提供给所有语言的访问者使用的。多个语言标签也是合法的,同样的,这个首部还可以用来描述不同媒体类型的文件,而不单单局限于文本型文档。语法:Content-Language: de-DE, en-CA(多个语言标签需要用逗号隔开。每一个语言标签都是由一个或多个不区分大小写的子标签构成的,子标签之间用连字号 ("-", %x2D)隔开。通常情况下,一个语言标签是由标识一个大的语言家族的主语言子标签(例如"en" = English),以及后面可选的用来缩小语言范围使更确切的一系列子标签("en-CA" 表示在加拿大范围使用的英语的变种)构成的。) |
Content-Length | Content-Length 是一个实体消息首部,用来指明发送给接收方的消息主体的大小,即用十进制数字表示的八位元组的数目。语法:Content-Length: <length> |
Content-Location |
Content-Location 首部指定的是要返回的数据的地址选项。最主要的用途是用来指定要访问的资源经过内容协商后的结果的URL。Location 与 Content-Location 是不同的,前者(Location )指定的是一个重定向请求的目的地址(或者新创建的文件的URL),而后者( Content-Location ) 指向的是可供访问的资源的直接地址,不需要进行进一步的内容协商。Location 对应的是响应,而Content-Location对应的是要返回的实体。语法:Content-Location: <url> |
Content-Type |
Content-Type 实体头部用于指示资源的MIME类型 media type 。在响应中,Content-Type标头告诉客户端实际返回的内容的内容类型。浏览器会在某些情况下进行MIME查找,并不一定遵循此标题的值; 为了防止这种行为,可以将标题 X-Content-Type-Options 设置为 nosniff。在请求中 (如POST 或 PUT ),客户端告诉服务器实际发送的数据类型。语法:Content-Type: text/html; charset=utf-8(或Content-Type: multipart/form-data; boundary=something)。media-type:资源或数据的 MIME type 。charset:字符编码标准。boundary:对于多部分实体,boundary 是必需的,其包括来自一组字符的1到70个字符,已知通过电子邮件网关是非常健壮的,而不是以空白结尾。它用于封装消息的多个部分的边界。 |
HTTP缓存
缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。当web缓存发现请求的资源已经被被存储时,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。这样带来的好处有:缓解服务器压力,提升性能。缓存不是永久不变的,一个资源的缓存应截止到其下一次发生变化。
缓存分为两大类:私有缓存和共享缓存。私有缓存存储的响应只能用于单独用户,常见有浏览器或其他客户端;共享缓存存储的响应可以被多个用户使用,常见的有代理服务器、CDN等。
HTTP缓存一般只能存储GET响应,对其他类型的响应无能为力。
HTTP通过以下头部实现缓存策略和验证机制:
Cache-Control: 可用于请求和响应头,是HTTP/1.1的缓存控制机制,它的值可以多个一起使用,使用逗号分隔,其值有:
- public:共享缓存,表明响应可以被多个用户使用(客户端、代理等)缓存
- private:私有缓存,表明响应只能被单个用户缓存
- no-store:不使用缓存,不应缓存任何关于客户端请求和服务端响应的内容
- no-cache:每次请求时,缓存会将发到服务器(伴随其它的缓存相关验证字段,如:If-Modified-Since、If-None-Match等),服务器会验证请求的相关验证字段,判断缓存是否过期,如果已过期则返回200、全新的响应内容、Cache-Control、Last-Modified、Etag、Vary等;如果未过期则返回304,表示缓存有效继续使用缓存。
- max-age:设置缓存存储的最大周期,超过这个缓存时间被认为过期(单位秒)。与Expires相反,max-age时间相对于请求的时间,请相对请求时间多少秒之后过期;而Expires时间是相对于响应时间,表示的是到某个具体时间点(年月日小时分钟秒)之后过期
- s-maxage:与max-age一样,只是表示的是代理的缓存时间,如果是代理时,会替代max-age
Expires:响应头,代表过期具体的时间点,是HTTP/1.0的属性。与Cache-Control: max-age=xx共存时,Expires的优先级比较低
Last-Modified与If-Modified-Since:
- Last-Modified:响应头,资源最新修改时间,有服务端提供返回
- If-Modified-Since:请求头,与Last-Modified对应,当缓存过期(max-age)时,客户端向服务端提交请求附带缓存中服务端返回的Last-Modified给If-Modified-Since,服务端收到请求会对比If-Modified-Since和资源最新修改时间,如果相等,则返回304表示没有更新,可以继续使用缓存;如果不相等表示资源有更新,返回200、新响应内容、新Last-Modified、Etag等
only-if-cached:
表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝
Etag与If-None-Match:
- Etag:响应头,有服务器提供返回,表示的资源内容唯一性标识
- If-None-Match:请求头,与Etag对应,当启用了缓存,服务端返回Etag时,下次想服务器请求时,请求头需将上次Etag的值赋值给If-None-Mathc想服务端提交请求,用于校验资源内容是否发生变化,如果没有更新,返回304表示资源无更新可以继续使用缓存;如果已更新,返回返回200、新响应内容、新Last-Modified、Etag等
Vary: Vary Http响应头决定了后续的请求头,如何判断是请求一个新的资源还是使用缓存的技术。当本地缓存或缓存服务器收到一个请求,只有当前请求与缓存的请求头跟响应头里的Vary的都匹配,才使用缓存的响应,这个是用于本地缓存或缓存服务器的缓存验证机制,而不是服务器的验证机制。Vary: <header-name>, <header-name>, ... 或者Vary: *
** HTTP缓存工作方式
1、客户端发起请求——》本地缓存校验——》没有缓存,向服务器发起请求,含有Cache-Control期望的缓存策略——》服务端返回内容及相互缓存头信息:Cache-Control、Etag、Last-Modified、Vary、Expires等——》客户端缓存并做出业务相关的响应
2、客户端发起请求——》本地缓存校验——》有缓存,且未过期——》客户端使用缓存内容
3、客户端发起请求——》本地缓存校验——》有缓存,但已过期——》发送请求到服务端,使用缓存中响应头的Etag(赋值给当前请求头的If-None-Match)、Last-Modified(赋值给当前请求头的If-Modified-Since)、Cache-Control、Vary等——》服务端继续缓存机制的校验——》资源内容有更新,返回200、新资源内容、新Etag、新Last-Modified、新Cache-Control、新Expired等——》客户端缓存并做出业务相关的响应
3.1、客户端发起请求——》本地缓存校验——》有缓存,但已过期——》发送请求到服务端,使用缓存中响应头的Etag(赋值给当前请求头的If-None-Match)、Last-Modified(赋值给当前请求头的If-Modified-Since)、Cache-Control、Vary等——》服务端继续缓存机制的校验——》资源内容没有更新,返回304、Etag、Last-Modified、Cache-Control、Expired等——》客户端继续使用原缓存
其中:Etag和Vary是强缓存验证,必须每个字节都相等才表示相等;Last-Modified是弱缓存验证,因为Last-Modified只能精确到秒,如果1秒之内资源进行更新了,Last-Modified是无法感知的。所以校验优先级如下:
Vary > max-age > Expires > Etag(If-None-Match) > Last-Modified(If-Modified-Since)
Vary、max-age和Expires一般在本地缓存或缓存服务器进行验证
Etag和Last-Modified一般在服务器进行验证
HTTP状态管理
HTTP协议是无状态的,但是有时需要保存用户的登录状态,所以才有了HTTP状态管理机制,HTTP的Cookie使HTTP协议记录状态成为了可能。
Cookie
HTTP Cookie是服务器发送给客户端并保存在本地的一小块数据,它会再客户端下次向同一服务器再发起请求时被携带并发送到服务器。
它的主要用于以下三个方面:
- 会话状态管理:如用户登录状态、购物车、游戏分数等
- 个性化设置:如用户自定义设置、主题等
- 客户端行为跟踪:如分析用户的行为等
Cookie现状: Cookie曾一度被用于客户端存储数据,但随着互联网技术的发展,Cookie逐渐被淘汰。这是因为服务器指定Cookie之后,客户端的每次请求都会携带Cookie数据,会带来额外的性能开销。已被Web storage API或IndexedDB或TOKEN逐渐取代。
Cookie使用:
服务端通过Set-Cookie响应头将Cookie发送给客户端。格式:Set-Cookie: key=value[;Expires=Date Time][;Max-Age=second][;Secure][;HttpOnly][;SameSize=xxx][;Domain=xxx][;Path=xxx]
- key/value:即Cookie的键值对
- Expires:Cookie的过期时间点,格式:Wed, 21 Oct 2015 07:28:00 GMT 表示Expires时间点之后过期
- Max-Age:有效期,单位秒,表示多少秒之后过期
- Secure:表示Cookie应只通过被HTTPS协议加密过的请求发送给服务端,它不是对Cookie进行安全性的加密。
- HttpOnly:仅作用于服务器,它是JavaScript的Document.cookie无法访问带有HttpOnly的Cookie的值
- Domain:指定了哪些主机可以接受Cookie, 如不指定默认为origin,不包含子域名。如果指定了Domain,则一般包含子域名。例如:Domain=mozilla.org,则 Cookie 也包含在子域名中(如developer.mozilla.org)
- Path:指定了主机下哪些路径可以接受Cookie(改路径必须存在于请求的URL中)。已字符%x2f("/")作为路径分隔符,子路径也会被匹配。例如Path=/docs,则以下地址都会匹配
a、/docs
b、/docs/one
c、/docs/sub/api - SameSize:运行服务器要求某个Cookie在跨站请求时不会被发送,从而阻止跨站请求伪造攻击。其取值有:
a、None:客户端会在同站请求、跨站请求下继续发送cookies,不区分大小写
b、Strict:客户端只在访问相同站点时发送cookie
c、Lax:与Strict相似,但用户从外部站点导航至URL时除外(例如通过链接)。
以下是服务端的响应举例:
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
以下是客户端请求的举例:
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
Session
Session代表客户端和服务端的一次会话过程。Session存储特定用户会话所需的属性和配置信息。当用户在应用程序的Web页面之间跳转时,存储在Session中的信息将不会丢失,而是在整个用户会话中一直存在。当客户端关闭会话或者Session超时时会话结束。
Session是存在在服务端端,服务器将Session数据存储在服务端,并使用SessionId与之关联,接着把SessionId返回给客户端,作为会话id,客户端将sessionId使用cookie缓存或者存在在隐藏表达或者存在在全局变量做作为下次请求的会话id传递给服务端,这样保持会话状态的持续性。
与Cookie的区别:
- 存放位置不同:Cookie存在客户端,Session存在服务端
- 安全性:由于Cookie存在客户端,所有安全性比较低
- 有效期不同:Cookie可以设置长时间保持,而Session一般失效时间较短(客户端关闭或者Session超时都会失效)
- 存储大小不同:单个Cookie保持的数据不能超过4K;Session可存储数据大小远高于Cookie
- 数据类型不同:Cookie只能保持ASCII码,Session可以存任意数据类型。
HTTP编码和解码
我们都知道计算机机器只能识别二进制,对应我们使用的各种字符是无法直接识别的,这就是编码和解码的用处所在:
编码:编码就是将字符根据选用的编码规范(字符集规范)通过编码方式(编码算法)处理转换为对应的二进制数值
解码:解码就是将二进制根据选用的编码规范(字符集规范)通过解码方式(解码算法)处理转换为我们能识别的字符
上述表述的涉及:二进制、编码规范(字符集规范)、编码方式、字符,这些都是什么感念?
二进制:由0和1组织的一串数字
字节:字节是计算机存储容量的计量单位(单位:B)。因为计算机只能识别0和1组成的二进制位,一个树就是1位(bit),8位就是一个字节
字符:任何一个文字或符合就是一个字符,不同的编码会导致一个字符所占的内存不同。例如:‘a’ 是一个字符,在ASCII中占1个字节,在Unicode中占4个字节。
编码规范&编码方式:
- 编码规范:是套二进制和字符相互转换的规则。常见的编码规范有:ASCII、GBK、Unicode、ISO-8859-1等,每一种编码规范的应用场景不一样,比如ASCII适用于英语,GBK适用于中文,Unicode适用于全球不同语言。
- 编码方式:每一种编码规范可以有不同的实现方式(即不同的算法实现相同的规范),我们称之为编码方式。比如Unicode规范有UTF-8、UTF-16、UTF-32等不同的编码方式。一套编码规范为什么需要不同的编码方式呢?因为如果直接使用字符对应的二进制地址来显示文字是十分浪费的,比如Unicode编码规范中包含几百万个字符,起码需要3个字节的容量,为了将来的扩展Unicode还保留了更多的未使用空间,最多可以存储4个字节的容量,在Unicode编码规范中00000000 00000000 00000000 00000001其实只占用1个字节的字符,但是实际为它分配了4个字节的空间,这就会导致一个可以用1G保存的文件,现在需要4G才能保存,这是极大的浪费。于是就有编码方式,它其实是通过一种算法,将编码规范中的二进制地址使用了特定的算法转换为更短的二进制数,使用这个更短的二进制地址来表示编码规范中对应的字符,从而达到节省空间的作用。编码方式编码的过程:从字库表中找到字符对应的正常的二进制地址——》使用二进制地址从编码字符集中找到正常的二进制数——》使用编码方式转换为更短的二进制数;编码方式的解码过程:更短的二进制数——》使用编码方式转换为编码规范中编码字符集中正常的二进制数——》使用正常的二进制数从字库表中找到对应的字符
每一种编码规范都由的字库表、编码字符集(字符集)、编码方式组成
字库表:每一套编码规范不一定包含世界上所有的字符,每一套编码规范都有对应的字库表,字库表存储的是编码规范中所能显示的所有字符及其与之对应的二进制地址。
编码字符集(字符集):在一个字库表中,每个字符都有一个对应的二进制地址,而字符集就是这些地址的集合。
例如:在ASCII中,字母A在编码字符集中排第65位,编码后A的数值是0100 0001,也就是十进制65的二进制的转换结果。编码字符集是用来存储这些二进制数的,而这个二进制数既是编码字符集中的一个元素,也是字库表中字母A的地址,我们根据这个地址可以显示出字母A。
常见的编码规范有:
ASCII:是最早产生的编码规范,一共包含00000000-01111111共128个字符,可以表示阿拉伯数字、大小英文字母以及一些简单的符合。ASCII码只需要1个字节的存储空间,最高位为0,它没有特定的编码方式,直接使用地址对应的二进制数来表示。
GBK:支持国家标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字。GBK字符集中所有字符占2个字符,不论中文英文都是2个字节。没有特殊的编码方式,一般在国内,汉字较多时使用。
ISO-8859-1:收录的字符出ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符合。因为ISO-8858-1编码范围使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他编码的字节流都不会被抛弃。换言之,把其他任何其他编码的字节流当做ISO-8859-1编码看待都没有问题。这是个很重要的特性,Mysql数据库默认编码是Latin1就是利用这个特性。ASCII编码是一个7位的容器,ISO-8859-1是一个8位容器。ISO-8859-1只占1个字节,且Mysql数据库默认编码就是ISO-8859-1。
Unicode: 从以上几种编码规范可以看出,各种编码规范互不兼容,且只能表示自己需要的字符,于是,国家标准化组织ISO决定制定一套全世界通用的编码规范Unicode。Unicode包含了全世界所有的字符,最多可以保存4个字节容量的字符。也就是说,要区分每个字符,每个字符需要的地址需要4个字节。这是十分浪费的存储空间,于是出有了:UTF-8、UTF-16、UTF-32等的编码方式。使用最广泛的是UTF-8.
- UTF-8:UTF-8是可变长编码方式,最小编码单位为1个字节。UTF-8编码中一个字节的前3位为描述性部分,后面为实际部分
- 如果一个字节的第一位为0,那么代表当前字符为单字节字符,占用一个字节空间。0之后的所有部分(后7位)代表Unicode中的序号
- 如果一个字节以110开头且第二个字节以10开头,那么代表当前字符为双字节字符,占用两个字节的空间。110之后的所有部分(后5位)加上后一个字节的除10外的部分(6位)代表在Unicode中的序号。
- 如果一个字节以1110开头,且第二、第三个字节以10开头,那么代表当前字符为三字节字符,占用3个字节的空间。
111之后的所有部分(后5位)加上后两个字节的除10外的部分(12位)代表在Unicode中的序号。 - 如果一个字节以10开头,那么代表当前字符为多字节的第二个字节或第三个字节。10之后的部分(6位)和之前的部分一同组成在Unicode中的序号。
我们分别看三个从一个字节到三个字节的UTF-8编码例子:
实际字符 | Unicode字库序号16进制 | Unicode字库序号的二进制 | UTF-8编码后的二进制 | UTF-编码后的16进制 |
---|---|---|---|---|
& | 0024 | 010 0100 | 0010 0100 | 24 |
¢ | 00A2 | 000 1010 0010 | 1100 0010 1010 0010 | C2A2 |
€ | 20AC | 0010 0000 1010 1100 | 1110 0010 1000 0010 1010 1100 | E282AC |
HTTP身份认证
HTTP请求时,如果服务器需要进行身份认证,会返回401状态告知客户端需要发送认证信息进行认证,此时客户端将认证信息填充在头部的Authorization中。Authorization: type(认证类型) authinfo(认证信息)
场景的认证类型有:
BASIC:基本认证,它是一种比较简单的HTTP认证方式,客户端通过明文(Base64编码格式)传输认证信息(如用户名和密码)到服务端进行认证,通常需要配合HTTPS来保证信息传输的安全。其认证流程如下图所示:
Basic认证缺点:
- 认证信息明文(Base64)传输,需要配合HTTPS来保证信息传输的安全
- 即使进行了加密,第三方仍可通过加密后的认证信息来重放攻击
- 没有提供任何针对代理和中间节点的防护措施
- 假冒服务器很容易骗过认证,诱导用户输入敏感信息。
DIGEST:摘要认证。而弥补BASIC认证存在的缺点,从HTTP/1.1开始使用DIGEST认证,它不会像BASIC认证那样方式明文,其认证流程如下图所示:
SSL客户端认证: 借由HTTPS的客户端证书完成认证的方式。凭借客户端证书认证,服务器可以确认访问是否来自自己登陆的客户端。
FormBase认证: 基于表单认证,它并不是HTTP协议中的定义的,而是使用有Web应用程序各自实现基于表单的认证方式,通过Cookie和Session的方式来保持用户的状态。
HTTP长连接和短连接
HTTP协议是基于请求/响应的模式,因此只要服务器响应了,本次HTTP请求就结束了。基于历史和技术发展,HTTP/1.0默认采用短连接,即客户端和服务端没进行一次HTTP操作,就建立一次连接,结束就关闭;HTTP/1.1开始,默认使用长连接,用于连接复用,较少建立连接和关闭连接的性能损耗。
HTTP请求头使用Connection来表示当前请求使用的是长连接还是短连接,Connection的值由:
- close: 表示连接使用短连接模式
- keep-alive:表示连接使用长连接模式
短连接:每次请求都会建立新的连接,结束则关闭。优势:每次连接都是新建的,所以只要能建立连接,数据基本上都能送达;缺陷:每次都需要重新建立连接,每次连接都需要经过TCP三次握手,使得相对耗时,降低了性能,而且每次都新建连接,但TCP连接数是有限制的,如果同时存在很多请求时,就会出现无法完成正常工作。
长连接:每次请求不会立马关闭连接,而是空闲一段时间(这个空闲时间可以通过服务器返回的keep-alive协议头来指定),用于连接复用,这样无需建立新的连接。优势:多次请求会复用连接,所有减少了连接和关闭的开销,并使多次数据传输的总耗时减少。缺陷:因为空闲一段时间才可能关闭,所有需要消耗一些CPU资源;还有就是需要花费额外的开销来维护连接是否可用(网络异常、服务器异常等)。长连接维持一个连接是否可用可以使用:利用TCP自身保活机制实现,保活机制会定时发送探测报文来识别对方是否可达,一般默认定时时间间隔2小时,可以在系统层面调整这个定时间隔;上次应用主动定时发送一个心跳包,探测是否能成功送达另一端,可达则继续保持连接,不可达断开连接。
HTTP代理
代理即是客户端也是服务端,它将客户端的请求(可能会进行一些额外的处理)转发到服务端,再将服务端的响应(可能会进行额外处理)返回给客户端。流程是:客户端发出请求——》代理将客户端的请求包装并向服务端发出请求——》服务器将响应返回给代理——》代理将服务端的响应包装后返回给客户端——》客户端收到代理的响应。
代理的作用:
- 抓包:它可以获取到客户端的请求,也可以得到服务端的响应,实现HTTP包体的抓取
- 匿名:它将客户端的请求转发给服务端,在代理的包装下服务端甚至不知道请求来自客户端,代理不包装下,服务端并不知道代理的存在。
- 过滤: 它可以过滤掉一些客户端的请求直接返回失败,也可以过滤掉服务端的响应直接返回失败给客户端。
HTTP内容协商
内容协商指的是客户端和服务端就响应内容进行交涉,然后提供给客户端最为适合的内容。内容协商以响应内容的语言、字符集、编码方式等作为判断的基准。
内容协商方式
客户端驱动:客户端发起请求,服务端发送可选项,客户端做出选择在发送第二次请求。这种需要至少两次的请求,增加了时延。
服务端驱动:客户端发起请求,并在头部提供可选项供服务端决定选择那一项。至少一次请求即可完成,相比客户端驱动要快,HTTP提供的q(权重,选项优先级)进行近似匹配。如果客户端请求头部头部选项集与服务端的无法匹配,服务端需要作出猜测。
- 头部选项集:头部选项集是有客户端发送给服务器用于交互偏好信息,以便服务器可以从资源的不同版本选择出最符合客户端的偏好并返回给客户端,客户端与服务端头部选项集如下表所示:
客户端头部集 | 服务端响应头部 |
---|---|
Accept:告知服务端客户端支持的媒体类型 | Content-Type:服务端根据内容协商机制选择的媒体类型 |
Accept-Language:告知服务端客户端支持的语言 | Content-Language:服务端根据内容协商机制选择的语言 |
Accept-Charset:告知服务端客户端支持的字符集 | Content-Type:服务端根据内容协商机制选择的字符集 |
Accept-Encoding:告知服务端客户端支持的编码方式 | Content-Encoding:服务端根据内容协商机制选择的编码方式 |
- 近似匹配:从头部选项集中选择最符合客户端的偏好。
例如客户端的指定的Accept-Language: en;q=0.5, fr;q=0.0, nl;q=1.0, tr;q=0.0
这个请求头部表示:用户最愿意接受的是nl(荷兰语),其次en(英文),接着是fr(法语)和tr(土耳其语)。服务端必须从资源的对应语音版本中选择最符合客户的编号,没有符合版本时,需要指定一种默认返回值。
q值的取值范围0-1.0(1的优先级最高)
透明协商:某个中间设备(通常是缓存代理)代表客户端进行协商。这样免除了服务器的协商开销,比客户端驱动快,但HTTP并没有此方面的规范。
HTTPS
HTTPS是安全超文本协议,是使用TLS/SSL加密的HTTP协议,可以理解为HTTPS=HTTP+TLS/SSL,是在TCP与HTTP之间加了一层TLS/SSL。为什么需要HTTPS呢,签名的内容介绍其实可以看出HTTP有一些突出的缺陷:
- 通信内容是明文的(未经加密),内容很容易被窃取
- 通信双方没有进行身份验证,可能出现伪装请求的情况
- 内容的全整性无法得到保证,会有途径某个网络节点(路由器、代理服务器等)内容被改动的情况。
HTTPS针对HTTP的缺陷增加了:内容加密、身份验证、数据完整性保护。
要了解HTTPS需要对加密、身份验证、TLS/SSL有基本的理解:
SSL:安全套接字层,1994年为Netscape研发,SSL协议位于TCP协议与各种应用层协议之间,为数据通信提供安全支持
TLS:传输层安全,前身是SSL(最初版本SSL1.0、SSL2.0、SSL3.0,由Netscape开发),1999年从3.1版本开始被IETF标准化并改名为TLS,发展至今有:TLS1.0、TLS1.1、TLS1.2三个版本。SSL3.0和TLS1.0由于安全漏洞,已经很少被使用,TLS1.3改动较大,未被广泛使用,目前广泛使用的是TLS1.1、TLS1.2。
加密算法:
加密算法分有以下几种类型:
- 对称加密:对称加密算法加密和解码都使用同一个秘钥,常见的有:DES、AES-GCM、ChaCha20-Poly1305等
- 非对称加密:非对称加密算法加密和解码使用的秘钥是不同,加密秘钥称为公钥、解码秘钥称为私密,公钥和算法都是公开的,私钥是保密的。相比对称加密而言,非对称加密性能较低,但安全性较强。常见的有:RSA、DSA、ECDSA、DH、ECDHE等。
- 哈希算法:将任意长度的信息转换为较短的固定长度的值,通常长度要比信息小得多,且算法不可逆。常见的有:MD5、SHA-1、SHA-2、SHA-256等。
数字签名:签名是指在信息的后面加上的一段内容(这段内容是将信息经过hash后的值,再对hash值进行加密。这段内容称为签名信息)。签名一般使用私钥对消息的hash值进行签名;验证签名使用公钥对签名进行解密得到消息的hash值
- 生成签名步骤:
a、对消息进行hash(哈希)计算,得到hash值
b、使用私钥对hash值进行加密,得到签名
c、将签名加到内容后面,一起发送 - 验证签名步骤:
a、收到消息后,提取消息中的签名
b、使用公钥对签名进行解密得到哈希值1
c、对消息进行哈希计算,得到哈希值2
d、比较哈希值1和哈希值2,如果相同,则验证成功;如果不相同则验证失败。
数字签名既对验证了身份,有保证了数据完整性。
数字证书:
考虑这种情形:客户端如何获得服务端的公钥?当服务器将公钥分发给客户端时,如果一开始就被第三方劫持,然后第三方自己再伪造一对秘钥,将伪造的公钥发给客户端,当服务器发送数据给客户端时,第三方将信息进行劫持,用劫持得到的公钥进行解密后,然后用伪造的私钥将数据(数据可能被篡改)加密发送客户端,而客户端收到后使用伪造的公钥解密,整个过程是透明的,客户端对于信息泄露、数据被篡改都毫无感知。为了防止这种情况,数字证书就出现了。
数字证书是有权威的证书认证机构(Certificate Authority,CA)颁发给服务端的,CA机构通过服务端提供的相关信息生成证书,证书中包含持有人信息(证书所有者的专有名称、证书颁发机构名称、证书有效起始日期、证书过期日期、证书数据格式的版本号、序列号等)、服务器公钥、数字签名等,最重要的是公钥在数字证书中。
数字证书是如何保证公钥来自请求的服务器的?——》数字证书中有持有人的相关信息,通过这点可以确定其不是第三方。
证书也是可以伪造的,如何保证证书是真的?——》即使第三方有CA公钥,能改解析数字签名并篡改,但是篡改之后,第三方需要证书进行加密,但是第三方没有CA私钥,无法加密,强行加密会导致客户端无法解密(客户端只人CA的公钥)
一个证书包含三部分:证书内容(持有人信息、服务器公钥等)、哈希算法、数字签名。证书内容被哈希算法hash计算得到hash值(此值也称证书摘要),然后CA机构使用自己的私钥对证书摘要进行加密得到数字签名。
客户端发起请求时,服务器将数据证书发给客户端,客户端通过CA提供的公钥对数字签名进行解密得到hash值(即证书摘要),同时将证书内容使用相同的哈希算法进行hash得到另一个hash值,对比两个hash值,如果相等说明证书正确,如果不相等说明证书不正确。
- 证书生成步骤:
a、服务器将公钥A发给CA
b、CA用自己的私钥B将证书内容(持有者信息、服务器公钥A等)进行加密,生成数字签名A
c、CA把证书内容(持有者信息、服务器公钥A等)和数字签名整合在一起,生成证书,返回给服务器。
注意:私钥B和公钥A不是配对的 - 证书验证步骤:
a、客户端得到证书
b、客户端得到CA提供的公钥B
c、客户端使用公钥B对证书中的数字签名解密,得到哈希值1(证书摘要1)
d、客户端对证书中的证书内容(持有者信息、服务器公钥A等)进行哈希计算,得到哈希值2(证书摘要2)
e、比较哈希值1和哈希值2,如果相同则证书合法,如果不相同则证书不合法
注意:公钥B和私钥B是配对的,分别用于对证书的验证(解密)和生成(加密)
HTTPS是安全的超文本传输协议,需对内容进行可逆加密以便接收者能解密,采用的加密方式如果是对称加密,使得秘钥一样,很容易破解,而如果是非对称加密,公钥也容易被他人截获且效率不高,所以HTTPS采用了对称和非对称混合加密方式,那么是怎么保证非对称公钥的安全、有效性以及对称秘钥的安全性,所以需要依托权威机构(即CA)来确保公钥的安全和有效性,接着采用协商机制来确定采用什么对称秘钥,HTTPS的TLS/SSL的四次握手确定会话秘钥就是这么来的。下图是TLS/SSL四次握手流程图:
第一次握手:客户端发送握手请求,包含如下信息:
- 客户端支持的TLS/SSL版本集合
- 客户端支持的加密方法集合
- 客户度随机生成的随机数1,此随机数需客户端和服务端都保存,作为最终会话秘钥的组成部分
第二次握手:服务端收到客户端的握手请求,保存随机数1,并向客户端发送响应信息,包括如下信息:
- 确认使用的TLS/SSL版本,如果服务端与客户端支持的版本不一致服务端关闭加密通道
- 确认使用的加密算法(包括非对称加密算法和对称加密算法)
- 服务端的数字证书,证书含证书内容(服务器公钥、持有者信息、证书过期时间等)、哈希算法、数字签名
- 服务器生成的随机数2
- 如果服务端需要确认客户端的省份,会包含要求客户端提供客户端证书的请求。比如金融机构往往只允许客户连入自己的网络,就会向正式客户提供USB秘钥,里面就包含一张客户端证书。
第三次握手:客户端收到服务器的响应,会先对服务器的数字证书进行验证:验证证书是否过期(证书中有过期日期),接着验证证书的合法性(使用客户端或者系统中CA提供的公钥对证书中的数组签名解密,得到哈希值1;接着对证书内容使用哈希算法计算出哈希值2,比较两者是否相等,相等则代表证书合法,不相等代表证书不合法并结束通信)。
证书验证通过后,客户端保存服务端响应中的随机数2和证书中的公钥(即服务器的公钥),接着向服务端发送如下内容:
- 生成一个随机数3,并使用服务器公钥进行加密
- 编码改变通知,表示随后的信息将使用双方商定的加密方法和秘钥发送
- 客户端握手结束通知值a,表示客户端的握手阶段已结束。这一项同时也是前面发送的所有内容的hash值,在用服务器公钥加密,用于服务器的校验
- 如果第二次握手,服务器有请求客户端进行身份认证,此时客户端还会发送证书及其相关信息
第四次握手:服务器收到第三次握手信息后,会对客户端握手结束通知值a进行验证(即第一次握手和第二次握手内容计算hash,再进行比较),验证通过,会使用私钥解析出随机数3并保存,然后计算出会话秘钥(使用随机1、随机2、随机3进行计算),接着向客户端发送如下信息:
- 编码改变通知,表示随后的信息将用双方商定的算法和秘钥发送
- 服务器握手结束通知b,表示服务器的握手阶段已结束。这一项同时也是前面发送的所有内容的hash值,再用服务器的私钥进行加密得到的结果,用于客户端校验
自此HTTPS四层握手结束,接下来HTTPS就会使用协商好的加密算法和会话秘钥(=随机数1、随机数2、随机数3,是对称加密方式)进行加密与解密。非对称加密用在对随机数3、客户端握手结束通知值a、服务器握手结束通知b进行加密与解密;对称加密用在对真正内容传输的加密与解密,会话秘钥由四次握手产生的随机数1、随机数2、随机数3生成(这样可以保证秘钥的随机性,加到安全 性);每次确认的加密算法都是随机的、会话秘钥的生成也是随机生成,这样可以保证基本每次用的算法、秘钥不一样,加大了安全性。
HTTP瓶颈与解决方案
HTTP版本发展至今,出现了不少瓶颈和解决方案,
常见的瓶颈有:
- 一条连接上只能发送一个请求
- 请求只能从客户端开始,客户端不能接收出响应之外的指令
- 请求/响应头部未经压缩发送,头部信息越多,数据量越大,会导致达到的延时性
- 每次互相发送相同的头部造成浪费
- 可以任意选择压缩格式,非强制压缩发送
常见的解决方案有:
1、SPDY
谷歌在2012年发布SPDY,目的在于解决HTTP的性能瓶颈,缩短Web的加载时间。SPDY没有完全改下HTTP协议,而在TCP/IP的应用层与运输层之间通过新加会话层的形式运作。同时考虑安全性问题,SPDY规定通信使用SSL。它使得HTTP协议额外获得如下功能: - 多路复用流:单一的TCP连接,可以无限处理多个HTTP请求
- 赋予请求优先级:给请求逐个分派优先级顺序
- 压缩HTTP头部:减少通信产生的数据包的数量和发送的字节数
- 推送功能:支持服务器主动向客户端主动推送数据的功能
- 服务器提示功能:服务器可以主动提示客户端请求所需的资源
使用SPDY,服务器得做出相应的改动,SPDY确实是一种可以消除HTTP瓶颈的技术,但很多Web问题并非仅仅有HTTP瓶颈导致。
SPDY是在TLS/SSL与HTTP之间加了一层SPDY。
2、WebSocket
WebSocket是建立在HTTP基础上的协议,因此连接的发起方仍是客户端,一旦建立WebSocket连接,不了服务端还是客户端,任意一方都可以直接向对方发送信息。
WebSoket的主要特点: - 推送功能:支持服务器向客户端推送数据
- 较少通信量:只要建立起WebSocket连接,就希望一直保持连接状态,和HTTP相比,不但每次连接时的总开销减少,而且由于WebSocket的头部信息很小,通信量也相应减小。