关于规范与本质
规范我认为是官方制定的标准,我们需要去遵守,而本质是让我们更容易去认识新事物。
当浏览器点击发送请求时,我们会觉得很神奇,甚至有的朋友十分抵触浏览器这个黑盒,一度认为http既熟悉又陌生,我们敬畏知识,但是我们也可以从别的方面去认识它。
http的本质
在浅谈http之前,我需要阐述一个观点:从本质来看,http无非就是一串字符串,服务器接收到这个东西时也是字符串。
通过引入node的net模块,我们可以搭建一个简单的服务器。
const net = require('net')
const server = net.createServer()
server.listen(8080)
server.on('connection',socket=>{
socket.on("data", chunk=>{
console.log('chunk:',chunk)
// 请求过来的二进制buffer:(chunk: <Buffer 47 45 54 20 2f 66 61 76 69 63 6f 6e 2e 69 63 6f 20 48 54 54 50 2f 31 2e 31 0d 0a 48 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a 38 30 38 30 0d 0a 43 ... 540 more bytes>)
console.log('chunk toString',chunk.toString())
//转换编码格式,效果如下:
//chunk toString GET /favicon.ico HTTP/1.1
//Host: localhost:8080
//Connection: keep-alive
//sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"
//sec-ch-ua-mobile: ?0
//User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36
//sec-ch-ua-platform: "macOS"
//Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
//Sec-Fetch-Site: same-origin
//Sec-Fetch-Mode: no-cors
//Sec-Fetch-Dest: image
//Referer: http://localhost:8080/
//Accept-Encoding: gzip, deflate, br
//Accept-Language: zh-CN,zh;q=0.9
socket.end()
})
})
无论是net模块还是http模块,都需要调用end方法,不然不会响应给客户端,示例代码之所以请求两次是因为浏览器多了一次自动请求网站图标favicon.ico的行为。
通过服务器的打印我们可以得知我们收到的结果无非是一串字符串,至于如何操作就由我们自行决定,在node模块里如果不进行数据转换,它收到的数据是一串Buffer,需要调用toString转换为utf-8格式的,这样就是我们熟悉的字符串格式。
响应给客户端的数据,我们可以在end之前调用write方法。需要注意,write的数据我们不能随便写,需要符合规范,这就涉及到http报文,报文作为http传输的一种规范,我们需要去遵守。
规范为什么需要遵守?
假如我说吃饭用“cf”表示,吃饱了用“cbl”表示,那么这就是我制定的规范,你们需要遵守,如果用了别的单词,我完全是不能理解你要表达的意思。而浏览器这些厂家就是规范的实现者,它们在内部的实现上遵守规范,不符合规范的他们可能就不处理了。
const net = require('net')
const server = net.createServer()
server.listen(8080)
server.on('connection',socket=>{
socket.on("data", chunk=>{
//书写自定义的报文格式,本质就是字符串!
socket.write(`HTTP/1.1 200 OK
<h1>h1</h1>`)
socket.end()
})
})
我们再增加一下响应内容和响应体:
const net = require('net')
const server = net.createServer()
server.listen(8080)
server.on('connection', socket => {
socket.on("data", chunk => {
socket.write(`HTTP/1.0 200 KO
Set-Cookie:asd
<h1>h1</h1>`)
console.log(chunk.toString())
socket.end()
})
})
我们发现了我们自定义的响应头展示在浏览器了,而浏览器也通过该字段把cookie设置在了当前页面上。
因为报文需要遵守一定的格式,对于换行符要求严格,如果代码格式化的话空格会影响到报文的正确格式。
至此我们对请求响应有了大概的认识,http层面我们是用字符串在服务端与客户端进行交流的,至于浏览器如何处理那就是浏览器的事情了,浏览器本身是规范的遵守者,所以其内部实现都会严格遵守规范。
http报文小实验
我们可以用curl工具复制别的网站的响应报文,然后在服务器响应这样的报文。
const net = require('net')
const server = net.createServer()
server.listen(8080)
server.on('connection',socket=>{
socket.on("data", chunk=>{
socket.write(`HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Length: 2381
Content-Type: text/html
Date: Sun, 27 Mar 2022 04:23:43 GMT
Etag: "588604eb-94d"
Last-Modified: Mon, 23 Jan 2017 13:28:11 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度前必读</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a> 京ICP证030173号 <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>`)
socket.end()
})
})
我们来尝试传输自己的规范,我们管自己的规范叫做http4.0,成功响应码233,响应消息KO。(既然传送的报文是一串字符串,为什么就不可以传输自定义,数据肯定能到达终端,至于如何对数据进行处理,那就是浏览器内部自行处理的。
)
const net = require('net')
const server = net.createServer()
server.listen(8080)
server.on('connection',socket=>{
socket.on("data", chunk=>{
socket.write(`HTTP/4.0 233 KO
Content-Type:application/json
<h1>h1</h1>`)
socket.end()
})
})
postman收到的响应:
curl工具获取响应报文:
然而浏览器还是可以正确处理:
我们可以看到,在浏览器通过content-type的值把响应的数据展示为json文本。而我们的HTTP/4.0并没有生效,这是因为浏览器内部根本不存在对该协议的判断,只会认为我们写错报文做了容错处理,改成目前通用的1.1版本协议。
通过几个小实验我们需要清楚的认识协议的本质,浏览器作为规范的践行者,它会根据我们的响应报文做相应的处理。而规范是官方指定的,至少在我认为,我们应该去熟记常见的规范,这样方便我们去理解浏览器对不同的响应做出的不同处理行为。
常见术语
作为官方标准,我们需要去遵守,前面我们通过测试知道即使我们不遵守标准,数据依旧可以到达目的地,但是如何去让浏览器根据响应做出相应的行为,那就需要我们根据标准去发送相应的报文。
所以在我看来,标准是需要我们去记、去背,而在众多标准中,我们只需要去记住常见的,不常见的翻阅官方文档即可。
RFC
RFC是由互联网工程任务组(IETF)发布的一系列备忘录。文件收集了有关互联网相关信息,以及UNIX和互联网社群的软件文件,以编号排定。RFC文件只有新增,不会有取消或中途停止发行的情形。
HTTP1.0记载于RFC1945。
HTTP1.1记载于RFC2068。(RFC2616是当前最新版本)
RFC是互联网的设计文档,要是不按照RFC标准执行,就有可能导致出现无法通信的情况。
URI与URL
URI(统一标识符),URL(统一资源定位符)。
URL是URI的子集。
URI组成部分:schema,user information,host,port,path,query,fragment组成。
例如下面的成为绝对URI:
https://user:pass@www.baidu.com:443/search/index.html?key=1&value=2#id=3
- https(scheme)
- /user:pass(user information)
- www.baidu.com(host)
- 443(port)
- search/index.html(path)
- key=1&value=2(query)
- id=3(fragment)
除了http等,也可以使用data:或javascript:这类数据或者脚本程序的方案名。
TCP/IP
TCP/IP是指与互联网相关联的协议集合。而HTTP就包括在这个TCP/IP协议族(如IP、DNS、FTP、TCP、HTTP、SNMP等)里。
OSI
以7层为例:
- 应用层:解决业务问题。
- 表示层:负责把网络消息转换为应用层可以读取的消息。(如SSL、TLS)
- 会话层:负责建立会话、握手、维持连接关闭。
- 传输层:负责进程与进程之间的通信,把报文分发给某些进程。(如TCP、UDP)
- 网络层:负责在广域网中把报文从一个主机发送到另一个主机。(如IP)
- 数据链路层:使用mac地址连相应的交换机或者路由器,主要工作在局域网。
- 物理层:确保原始的数据可在各种物理媒体上传输。
以Wireshark抓取访问百度为例:
图中为抓取到的数据,我们从图中也可以看出数据报文最终转换成了16进制数,以中间区域为例,便是分层信息,每一层都有各自的报文头
Frame是wireshark抓取时记录的信息,包含了抓取时间、网卡信息等。
Ethernet II:代表以太网层,记录mac地址等。
Internet Protocol:代表IP层。
Transmission Control Protocol:代表表示层,TLS等,不再有头部概念,已加密。
http演变
http0.9
http0.9于1990年问世,主要用于学术交流,此时需求很简单,仅用来在网络之间传递HTML超文本的内容。此时也是基于请求响应的模式。http0.9其实含有http1.0之前版本的意思。
只有请求行
http0.9,并没有请求头和请求体,因为请求行就能表达需求,所以只能发送类似get的简单请求。
没有响应头信息
http0.9没有响应头信息,只要返回数据即可。
ASCII字符流传输
http0.9返回的内容以ASCII字符流来传输,因为都是HTML格式的文件。
http1.0
http1.0正式作为标准被公布是在1996年的5月,记载于RFC1945。
由于http0.9相对简单,无法适应当时新型网络的发展。此时的网络需求不再单单的html文件加载了,还包括js、css、图片、音频、视频等不同类型文件。并且这些文件不能仅仅局限于ASCII编码。
请求头和响应头
http1.0需要对多类型文件进行描述,所以引入了请求头和响应头。
资源协商
http1.0可以通过特定的请求头和响应头对数据进行协商。
如:
accept:text/html //表示期待服务器返回html类型的文件,服务器看到了,至于给不给你html是另一回事,手动狗头
accept-encoding:gzip,deflate,br //表示期待服务器可以采用gzip、deflate、br其中一种压缩方式
accept-Charset:utf-8 //表示期待返回的文件编码是utf-8
accept-language:zh-CN//表示期待页面语言是中文
我们在服务器收到请求过来的报文,自然是能够解读到这些描述信息,但是我们就是不给它返回gzip压缩,所以我们可以带上这样的响应:
content-encoding:br //浏览器收到会做出相应的处理,使用br压缩算法处理文件
content-type:application/json;charset=UTF-8 //浏览器直接把数据当json展示
状态码
http1.0应对不同处理情况,引入了状态码。
cache机制
http1.0为了减轻服务器压力,引入cache机制,用来缓存下载过的文件。
用户代理
http1.0为了统计客户端的基本信息,引入用户代理字段。
http1.1
http1.1为网络效率做了大量优化,核心有三种方式:
- 增加了持久链接
- 浏览器为每个域名最多同时维护6-8个TCP持久连接。(谷歌时6,safari是8)
- 使用CDN实现域名分片机制。
持久连接
http1.0每进行一次http通信都需要经历建立tcp连接、传输http数据和断开tcp三个阶段。为了解决该问题,http1.1和部分http1.0想出了持久连接(keep-alive)。持久连接在http1.1是默认开启的,可以增加请求头关闭持久连接(Connection:close)。目前主流浏览器中对于同一个域名,默认允许同时建立6个tcp持久连接。
持久连接虽然能减少tcp建立与断开次数,不过都是需要等待前面请求返回之后才能进行下一次请求,如果tcp通道中某个请求没有及时返回,那么就会阻塞后面的所有请求,这便是队头阻塞。
管线化
管线化技术出现后,不用等待响应就可以直接发送下一个请求,这样就能够并行发送多个请求,同时也可以解决队头阻塞问题。(虽然可以整批发送请求给服务器,不过服务器依然需要根据请求顺序来回复浏览器的请求)
不过目前主流浏览器都放弃了管线化技术。
提供虚拟主机的支持
http1.0中,每个域名只能绑定一个唯一的ip地址,不过随着虚拟主机技术的流行,同一个ip可以绑定多个单独的域名。因此http1.1的请求头增加了Host字段,用来表示当前的域名地址。
提供动态生成内容的支持
http1.0需要设置完整的数据大小,如Content-Length字段。
http1.1引入chunk transfer机制,服务器将数据分割成若干份,每个数据块都有附带的长度,最后使用一个零长度的块作为发送数据完成的标志。
使用cookie状态管理
http是无状态协议,它不会对之前发送过的请求和响应的状态进行管理。
为了保持无状态协议这个特征的同时又要解决类似的状态管理的矛盾,于是引入了cookie技术。cookie技术通过在请求和响应报文写入cookie信息来控制客户端的状态。浏览器会根据set-cookie的首部字段信息,作出保存cookie的行为,在之后的请求中都会附带cookie信息。
请求方法的变化
我们知道请求方法其实也是可以自定义的,我们可以通过代码将报文发送给目标地址,而目标地址依旧可以收到我们自定义的请求方法。
但是,我们自定义的不是标准,而浏览器作为RFC规范的践行者,其内部只支持规范中制定的请求方法,所以在实际开发中我们只需要遵循着语义使用对应的方法即可。
方法 | 语义 | 支持的http协议版本 |
---|---|---|
GET | 获取资源 | 1.0、1.1 |
POST | 传输实体主体 | 1.0、1.1 |
PUT | 传输文件 | 1.0、1.1 |
HEAD | 获取报文头部 | 1.0、1.1 |
DELETE | 删除文件 | 1.0、1.1 |
OPTIONS | 询问支持的方法 | 1.1 |
TRACE | 追踪路径 | 1.1 |
CONNECT | 要求用隧道协议连接代理 | 1.1 |
LINK | 建立和资源之间的联系 | 1.0 |
UNLINK | 断开连接关系 | 1.0 |
http2.0
http1.1的主要问题是对带宽的利用率并不理想,具体因素如下:
- TCP的慢启动,这个慢启动过程如同汽车从0加速至最高速度的过程,主要是为了减少网络拥塞的一种策略。
- 多条TCP竞争固定的带宽。
- http队头阻塞,一条管道同一时刻只能处理一个请求。
多路复用机制
所以针对以上情况http2.0提出了多路复用机制,即一个域名只使用一个TCP长连接,同时也消除了队头阻塞问题。
http2.0可以设置资源请求的优先级,同时每份数据都有对应的ID。多路复用技术可以将请求分成一帧一帧的数据去传输。
http2.0添加了一个二进制分帧层,在tls之上,报文之下,多路复用正是通过在协议栈添加的二进制分帧层来实现,有了二进制分帧层还能够实现请求的优先级、服务器推送、头部压缩等特性。
服务器推送
服务器可以在接收到请求之后,附带将要发送的资源一起响应回去。
头部压缩
HTTP2.0可以对请求头和响应头进行压缩。
http3.0
http2.0仍存在的问题:
- TCP队头阻塞:在TCP传输过程中,由于单个数据包丢失而造成的阻塞。(http2多个请求跑在同一条管道,任意一路数据丢包直接阻塞了tcp所有请求。而http1.1同个域名可以有多条tcp,一条阻塞了还有其他条tcp)
- TCP建立连接的延时:三次握手就要1.5个RTT,TLS连接要1-2个RTT。
- TCP协议僵化:主要是中间设备僵化和操作系统内部的协议僵化。
基于以上的问题,http3.0使用折衷的方法,基于UDP实现了类似于TCP的QUIC协议。(多路数据流、传输可靠性)
UDP的不可靠性通过在其之上加入一层QUIC协议保证传输的可靠性。
流量控制与传输可靠性
QUIC通过在UDP的基础之上增加一层来保证数据可靠性传输,同时也提供了数据包传输、拥塞控制以及一些TCP存在的特性。
加密功能
目前QUIC使用TLS1.3,可以减少握手所花费的RTT个数。
多路复用
QUIC实现了同一个物理连接可以有多个独立逻辑的数据流,解决了TCP队头阻塞问题。
快速握手
基于UDP的QUIC协议可以实现0-RTT或者1-RTT来建立连接。
http状态码
状态码的类别:
类别 | 原因短语 | |
---|---|---|
1XX | Informational(信息性状态码) | 接收的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
1XX
响应码规范:RFC6585(2012.4)、RFC7231(2014.6)
http1.0不支持1xx。
- 100 Continue:上传大文件前使用,由客户端发起请求中携带Expect:100-continue头部触发。
- 101 Switch Protocols:协议升级使用,由客户端发起请求中携带Upgrade头部触发,如升级到websocket或者http/2.0。
- 102 Processing:WebDAV请求可能包含许多涉及文件操作的子请求,需要长时间才能完成请求,该状态码表示已经收到并正在处理,但无响应可用。这样可以防止客户端超时。
2XX
- 200 OK:表示成功。
- 201 Created:表示有新资源在服务器端杯成功创建。
- 202 Accepted:服务器接收并开始处理请求,但请求为处理完成。可用于异步、需要长时间处理的任务。
- 203 Non-Authoritative Information:当代理服务器修改了origin server的原始响应包体,代理服务器可以通过修改200位203的方式告知客户端这一事实,方便客户端对此作出相应的处理,203响应可以被缓存。
- 204 No Content:成功执行了请求切不带响应包体,并暗示客户端无需更新页面视图。
- 205 Reset Content:成功执行了请求且不带响应包体,同时指明客户端需要更新当前页面视图。
- 206 Partial Content:表示客户端进行了范围请求,而服务器成功执行了这部分的get请求。使用range协议时返回部分响应内容时的响应码。
- 207 Multi-Status:RFC4928,在WEBDAV协议中以XML返回多个资源的状态。
- 208 Already Reported:RFC5842,为了避免相同集合下资源在207响应码下重复上报,使用208可以使用父集合的响应码。
3XX
重定向使用Location指向的资源或者缓存中的资源。在RFC2068中规定客户端重定向次数不应超过5次,以防止死循环。
- 300 Multiple Choices:资源有多种表述,通过300返回给客户端后由其自行选择访问哪一种表述。由于缺乏资源明确的细节,300很少使用。
- 301 Moved Permanently:资源永久性地重定向到另一个URI中。
- 302 Found:资源临时地重定向到另一个URI中。
- 303 See Other:重定向到其他资源,常用语POST/PUT等方法的响应中。
- 304 Not Modified:当客户端拥有可能过期的缓存时,会携带缓存的标识etag、时间等信息询问服务器缓存是否仍可复用,而304时告诉客户端可以复用缓存。
- 307 Temporary Redirect:类似302,但明确重定向后请求方法必须与原来的请求方法相同,不得改变。
- 308 Permanent Redirect:类似301,但明确重定向后请求方法必须与原来的请求方法相同,不得改变。
RFC1945和RFC2068规范不允许客户端在重定向时改变请求的方法。
当301、302、303响应状态码返回时,几乎所有的浏览器都会把POST改成GET,并删除请求报文内的主体,之后请求会自动再次发送。(301、302标准是禁止将POST方法改变成GET方法的。)
4XX
- 400 Bad Request:服务器认为客户端出现了错误,但不能明确判断为以下哪种错误时使用此错误码。例如HTTP请求格式错误。
- 401 Unauthorized:用户认证信息缺失或者不正确,导致服务器无法处理请求。
- 403 Forbidden:服务器理解请求的含义,但没有权限执行此请求。
- 404 Not Found:服务器没有找到对应的资源。
- 405 Method Not Allowed:服务器不支持请求行中的method方法。
- 406 Not Acceptable:对客户端指定的资源表述不存在(例如对语言或者编码有要求),服务器返回表述列表供客户端选择。
- 407 Proxy Authentication Required:对需要经由代理的请求,认证信息为通过代理服务器的验证。
- 408 Request Timeout:服务器接收请求超时。
- 409 Conflict:资源冲突,例如上传文件时对目标位置已经存在版本更新的资源。
- 410 Gone:服务器没有找到对应的资源,且明确的知道该位置永久性找不到该资源。
- 411 Length Required:如果请求含有包体且未携带Content-Length头部,且不属于chunk类请求时,返回411。
- 412 Precondition Failed:复用缓存时传递的If-Unmodified-Since或If-None-Match头部不被满足。
- 413 Payload Too Large:请求的包体超过服务器能处理的最大长度。
- 414 URI Too Long:请求的URI超出服务器能接受的最大长度。
- 415 Unsupported Media Type:上传的文件类型不被服务器支持。
- 416 Range Not Satisfiable:无法提供Range请求中指定的那段包体。
- 417 Expectation Failed:对于Expect请求头部期待的情况无法满足时的响应码。
- 426 Upgrade Required:服务器拒绝基于当前HTTP协议提供服务,通过Upgrade头部告知客户端必须升级协议才能继续处理。
- 428 Precondition Required:用户请求中缺失了条件类头部,例如If-Match。
- 429 Too Many Requests:客户端发送请求速率过快。
- 451 Unavailabel For Legal Reasons:RFC7725,由于法律原因资源不可访问。
5XX
- 500 Internal Server Error:服务器内部错误,且不属于以下错误类型。
- 501 Not Implemented:服务器不支持实现请求所需要的功能。
- 502 Bad Gateway:代理服务器无法获取到合理响应。
- 503 Service Unavailable:服务器资源尚未准备好处理当前请求。
- 504 Gateway Timeout:代理服务器无法及时的从上游获得响应。
- 505 HTTP Version Not Supported:请求使用的HTTP协议版本不支持。
- 507 Insufficient Storage:服务器没有足够的空间处理请求。
- 508 Loop Detected:访问资源时检测到循环。
- 511 Network Authentication Required:代理服务器发现客户端需要进行身份验证才能获得网络访问权限。
http首部
通用首部字段
首部字段名 | 说明 |
---|---|
Cache-Control | 控制缓存的行为 |
Connection | 逐跳首部、连接的管理 |
Date | 创建报文的日期时间 |
Pragma | 报文指令,是HTTP1.1之前版本的遗留字段,仅做向后兼容而定义,可如下配合(Cache-Control:no-cache Pragma:no-cache) |
Trailer | 报文末段的首部一览 |
Transfer-Encoding | 指定报文主体的传输编码方式,一般可选值有chunked |
Upgrade | 升级为其他协议 |
via | 代理服务器的相关信息 |
Warning | 错误通知 |
Cache-Control
指令参数可选,多个指令之间通过“,”分隔。
缓存请求指令:
指令 | 参数 | 说明 |
---|---|---|
no-cache | 无 | 强制向原服务器再次验证 |
no-store | 无 | 不缓存请求或响应的任何内容 |
max-age = [秒] | 必需 | 相应的最大Age值 |
max-state(=[秒]) | 可省略 | 接收已过期的响应 |
mix-fresh = [秒] | 必需 | 期望在指定时间内的响应仍有效 |
no-transform | 无 | 代理不可更改媒体类型 |
only-if-cached | 无 | 从缓存获取资源 |
cache-extension | - | 新指令标记(token) |
缓存响应指令:
指令 | 参数 | 说明 |
---|---|---|
public | 无 | 可向任意方提供响应的缓存 |
private | 可省略 | 仅向特定用户返回相应 |
no-cache | 可省略 | 缓存前必须先确认有效性 |
no-sotre | 无 | 不缓存请求或响应的任何内容 |
no-transform | 无 | 代理不可改变媒体类型 |
must-revalidate | 无 | 可缓存但必须再向源服务器进行确认 |
proxy-revalidate | 无 | 要求中间缓存服务器对缓存的响应有效性再进行确认 |
max-age=[秒] | 必需 | 响应的最大Age值 |
s-maxage=[秒] | 必需 | 公共缓存服务器响应的最大Age值 |
cache-extension | - | 新指令标记(token ) |
Connection
Connection具备两个作用,控制不再转发给代理的首部字段,管理持久连接。
HTTP1.1版本默认都是持久连接。当服务器想明确断开连接,则指定Connection首部字段 的值为Close。
HTTP1.1之前的版本默认连接是非持久化连接,所以想要在旧版本持久化需要指定值为Keep-Alive。
请求首部字段
首部字段名 | 说明 |
---|---|
Accept | 用户代理可处理的媒体类型 |
Accept-Charset | 优先的字符集 |
Accept-Encoding | 优先的内容编码 |
Accept-Language | 优先的语言 |
Authorization | Web认证信息 |
Expect | 期待服务器的特定行为 |
From | 用户的电子邮箱地址 |
Host | 请求资源所在的服务器 |
If-Match | 比较实体标记(ETag) |
If-Modified-Since | 比较资源的更新时间 |
If-Node-Match | 比较实体标记(于IF-Match相反) |
If-Range | 资源未更新时发送实体Byte的范围请求 |
If-Unmodified-Siince | 比较资源的更新时间(与If-Modified-Since相反) |
Max-Forwards | 最大传输逐跳数 |
Proxy-Authorization | 代理服务器要求客户端的认证信息 |
Range | 实体的字节范围请求 |
Referer | 对请求中URI的原始获取方 |
TE | 传输编码的优先级 |
User-Agent | HTTP客户端程序的信息 |
Accept
Accept首部字段可通知服务器,用户代理能够处理的媒体类型及媒体类型的相对优先级。
//文本文件
text/html text/plain text/css
application/xhtml+xml application/xml
//图片文件
image/jpeg image/gif image/png
//视频文件
video/mpeg video/quicktime
//应用程序使用的二进制文件
application/octet-stream application/zip
给显示的媒体类型增加优先级,可以通过q=来表示权重值,用分号(;)进行分隔。权重值q的范围是0-1,可精确到小数点后三位。
If-Match
形如If-XXX这种形式的请求首部字段,都可成为条件请求。服务器接收到附带条件的1请求后,只有判断指定条件为真时才会执行请求。
只有当If-Match的字段值跟ETag值匹配一致时,服务器才会接受请求,反之不符合返回412
If-Modified-Since
如果在If-Modified-Since字段指定的日期时间后资源发生了更新,服务器会接受请求。
如果在指定If-Modified-Since字段值的日期时间之后,如果请求的资源都没有过更新,则返回状态码304No Modified的响应。
If-Modified-Since用于确认代理或客户端拥有的本地资源的有效性。获取资源的更新日期时间,可通过确认首部字段Last-Modified来确定。
If-None-Match
只有在If-None-Match的字段值与ETag值不一致时,可处理该请求。与If-Match首部字段的作用相反。
If-Range
If-Range字段值若是跟ETag值或更新的日期时间匹配一致,那么就作为范围请求处理。
当range一半的时候资源更新了,即代表客户端已加载的资源失效,服务器会响应412 Precondition Failed告诉客户端需要重新请求,因为期望的资源已经没有了。
If-Unmodified-Since
该字段与If-Modified-Since相反,它的作用是告知服务器,指定的请求资源只有在字段值内指定的日期时间之后,未发生更新的情况下,才能处理请求。
响应首部字段
首部字段名 | 说明 |
---|---|
Accept-Ranges | 是否接受字节范围请求 |
Age | 推算资源创建经过时间 |
ETag | 资源的匹配信息 |
Location | 令客户端重定向至指定URI |
Proxy-Authenticate | 代理服务器对客户端的认证信息 |
Retry-After | 对再次发起请求的时机要求 |
Server | HTTP服务器的安装信息 |
Vary | 代理服务器缓存的管理信息 |
WWW-Authenticate | 服务器对客户端的认证信息 |
Accept-Ranges
该字段用来告知客户端是否能处理范围请求,以指定获取服务器端某个部分的资源。
可指定的值有两种,bytes或none。
Age
告知客户端,源服务器在多久前创建了响应。单位为秒。
ETag
ETag能告知客户端实体标识。ETag有强ETag和弱ETag,强ETag值便是唯一标识符,而弱ETag只用于提示资源是否相同。只有资源发生了根本改变,产生差异性时才会改变ETag的值,这时,会在字段值最开始处附加W/。
//强ETag
ETag:"abcd123"
//弱ETag
ETag:W/"abcd123"
Location
该字段可配合重定向3XX响应,可重定向到Location指定的URI。
实体首部字段
首部字段名 | 说明 |
---|---|
Allow | 资源可支持的HTTP方法 |
Content-Encoding | 实体主体适用的编码方式 |
Content-Language | 实体主体的自然语言 |
Content-Length | 实体主体的大小(单位:字节) |
Content-Location | 替代对应资源的URI |
Content-MD5 | 实体主体的报文摘要 |
Content-Range | 实体主体的位置范围 |
Content-Type | 实体主体的媒体类型 |
Expires | 实体主体过期的日期时间 |
Last-Modified | 资源的最后修改日期时间 |
Allow
Allow用于通知客户端能够支持的所有HTTP方法,当服务器接收到不支持的HTTP方法时,会以状态码405Method Not Allowed作为响应返回,同时还会把所有能支持的HTTP方法写入首部字段Allow后返回。
Content-Encoding
告知客户端服务器对实体内容选用的内容编码方式。主要有gzip、compress、deflate、identity
Content-Language
告知客户端实体主体使用的自然语言。
Content-Length
实体主体部分的大小,单位为字节。对实体主体内容编码传输时,不能使用该字段。
Content-Location
表示报文主体返回资源对应的URI。
Content-MD5
客户端会对接收到的报文主体执行相同的MD5算法,然后与首部字段Content-MD5字段值进行比较。目的在于检查报文主体在传输过程中是否保持完整,以及确认传输到达。
由于HTTP首部字段不能记录二进制值,所以是对报文主体执行MD5算法获得128位二进制数,再通过Base64编码后将结果写入Content-MD5。
Content-Range
针对范围请求,返回响应时使用的首部字段Content-Range,目的在于告知客户端响应返回的实体的哪个部分符合范围请求。字段值以字节为单位,表示当前发送部分及整个实体大小。
Content-Type
与Accept字段一样,字段值用XX/XX形式赋值。
Expires
该字段会将资源失效的日期告知客户端。在指定的日期之前,响应的副本会被缓存服务器一直被保存。
源服务器不希望缓存服务器对资源缓存时,最好在Expires字段内写入与首部字段Date相同的时间值。
Cache-Control有指定max-age时,比起首部字段Expires,会优先处理max-age。
其他首部字段
自行扩展的非标准化首部字段,如X-前缀,在RFC6648中提议停止该做法,然后对已经在使用的X-前缀来说,不应该要求其更改。
首部字段名 | 说明 |
---|---|
X-Frame-Options | 用于控制网站内容在其他网站的Frame标签内的显示问题,主要为了防止点击劫持。有两个字段值,DENY(拒绝)、SAMEORIGIN(仅同源域名下的页面匹配时许可) |
X-XSS-Protection | 针对跨站脚本攻击的一种对策,值有两种,0(将XSS过滤设置成无效状态),1(将XSS过滤设置成有效状态) |
DNT | 表示拒绝被精准广告追踪的一种方法,值有两种,0(同意被追踪),1(拒绝被追踪) |
P3P | 保护用户隐私 |
总结
http需要从本质上去理解,至于首部字段和状态码的使用和配合,在实际使用中需要去遵守RFC规范。
http协议的演进是不断优化的过程,所以记住不同阶段的特性可以从时代背景入手。