sailei1 / blog

1 stars 0 forks source link

http 字段及用途 #89

Closed sailei1 closed 5 years ago

sailei1 commented 5 years ago

HTTP协议规定了非常多的头部字段,实现各种各样的功能,但基本上可以分为四大类:

  1. 通用字段:在请求头和响应头里都可以出现;
  2. 请求字段:仅能出现在请求头里,进一步说明请求信息或者额外的附加条件;
  3. 响应字段:仅能出现在响应头里,补充说明响应报文的信息;
  4. 实体字段:它实际上属于通用字段,但专门描述body的额外信息。

Accept 跟 Content MIME type 形式是“type/subtype”的字符串 比如 1 文本 text/html text/plain 2 图片 image/png 3 音视频 audio/mpeg、video/mp4 4 数据格式不固定由上层应用程序来解释 application/json application/javascript

Encoding type 1 gzip压缩 2 deflate 3 br

有了MIME type和Encoding type,无论是浏览器还是服务器就都可以轻松识别出body的类型,也就能够正 确处理数据了。

客户端用Accept头告诉服务器希望接收什么样的数据,而服务器用Content头告诉客户端 实际发送了什么样的数据。

Accept字段标记的是客户端可理解的MIME type,可以用“,”做分隔符列出多个类型,让服务器有更多的选择余地

Accept-Language字段标记了客户端可理解的自然语言,也允许用“,”做分隔符列出多个类型

请求头 Accept: application/json, text/plain, / Accept-Encoding: gzip, deflate Accept-Language:zh-CN,zh;q=0.9,en;q=0.8,es;q=0.7,fr;q=0.6,ru;q=0.5,tr;q=0.4,vi;q=0.3,zh-TW;q=0.2,de;q=0.1,da;q=0.1,ja;q=0.1,pt;q=0.1

服务器会在响应报文里用头字段Content-Type告诉实体数据的真实类型

响应头 Content-Type: application/json; charset=utf-8 Content-Encoding: gzip

注意 content-type是实体字段,所以请求和响应里都可以用,作用是指明body数据的类型。

字符集在HTTP里使用的请求头字段是Accept-Charset,但响应头里却没有对应的Content-Charset,而是 在Content-Type字段的数据类型后面用“charset=xxx”来表示,这点需要特别注意。

q 代表权重值 最大是1 最小是0.01 默认1。 0表示拒绝 在数据类型或语言代 码后面加一个“;” “;”的意义是小于“,”的。

Accept: text/html,application/xml;q=0.9,*/*;q=0.8

表示浏览器最希望使用的是HTML文件,权重是1,其次是XML文件,权重是0.9,最后是任意数据类型,权重是0.8。服务器收到请求头后,就会计算权重,再根据自己的实际情况优先输出HTML或者XML。

有的时候,服务器会在响应头里多加 一个Vary字段,记录服务器在内容协商时参考的请求头字段,给出一点信息 Vary: Accept-Encoding,User-Agent,Accept

Vary字段可以认为是响应报文的一个特殊的“版本标记”。每当Accept等请求头变化时,Vary也会随着响应 报文一起变化

sailei1 commented 5 years ago

大文件处理

数据压缩 通常浏览器在发送请求时都会带着“Accept-Encoding”头字段,里面是浏览器支持的压缩格式列表,例如 gzip、deflate、br等,这样服务器就可以从中选择一种压缩算法,放进“Content-Encoding”响应头里,再 把原数据压缩后发给浏览器。

gzip 文本压缩率超过60% br算法是专为html设计的,压缩效率和性能比gzip还要好 nginx gzip on 指令 只压缩文本数据 不压缩图片 音频 视频等大文件

分块传输

压缩是把大文件整体变小,我们可以反过来思考,如果大文件整体不能变小,那就把它“拆开”,分解成多个小块,把这些小块分批发给浏览器,浏览器收到后再组装复原。

Transfer-Encoding: chunked 表示报文里的body部分不是一次性发过来的,而是分成了许 多的块(chunk)逐个发送。

分块传输的编码规则 (明文传输)

  1. 每个分块包含两个部分,长度头和数据块;
  2. 长度头是以CRLF(回车换行,即\r\n)结尾的一行明文,用16进制数字表示长度; 3. 数据块紧跟在长度头后,最后也用CRLF结尾,但数据不包含CRLF;
  3. 最后用一个长度为0的块表示结束,即“0\r\n\r\n”。

Transfer-Encoding:chunked”和“Content-Length”这两个字段是互斥的,也就是说响应报文里这两个 字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked),这一点你一定要 记住。

chunked编码用在“流式”收发数据的时候,通常数据是即时生成的,也就是动态数据。

Transfer-Encoding 常见的值 chunked, 也可以用gzip deflate br 表示传输时使用了压缩编码。 注意这与Content-Encoding不同,Transfer-Encoding 在传输后会被自动解码还原出原始数据,而content-Encoding 则必须由应用自行解码

范围请求

范围请求不是Web服务器必备的功能,可以实现也可以不实现,所以服务器必须在响应头里使用字 段“Accept-Ranges: bytes”明确告知客户端:“我是支持范围请求的”。如果不支持的话,服务器可以发送“Accept-Ranges: none”,或者干脆不发送“Accept-Ranges”字段

请求头Range是HTTP范围请求的专用字段,格式是“bytes=x-y”,其中的x和y是以字节为单位的数据范围。

服务器收到Range字段后,需要做四件事。

第一,它必须检查范围是否合法,比如文件只有100个字节,但请求“200-300”,这就是范围越界了。服务器就会返回状态码416,意思是“你的范围请求有误,我无法处理,请再检查一下”。 第二,如果范围正确,服务器就可以根据Range头计算偏移量,读取文件的片段了,返回状态码“206 Partial Content”,和200的意思差不多,但表示body只是原数据的一部分。 第三,服务器要添加一个响应头字段Content-Range,告诉片段的实际偏移量和资源的总大小,格式 是“bytes x-y/length”,与Range头区别在没有“=”,范围后多了总长度。例如,对于“0-10”的范围请 求,值就是“bytes 0-10/100”。 最后剩下的就是发送数据了,直接把片段用TCP发给客户端,一个范围请求就算是处理完了。

多段数据

Range: bytes=0-31 // 获取文件的前32个字节
Content-Type: multipart/byteranges; boundary=00000000001
  Content-Length: 189;
  Connection: keep-alive;
  Accept-Ranges: bytes;

  --00000000001
  Content-Type: text/plain
  Content-Range: bytes 0-9/96
  // this is
  --00000000001
  Content-Type: text/plain
  Content-Range: bytes 20-29/96
  ext json d
  --00000000001--

报文里的“--00000000001”就是多段的分隔符,使用它客户端就可以很容易地区分出多段Range 数据。

要注意这四种方法不是互斥的,而是可以混合起来使用,例如压缩后再分块传输,或者分段后再分块

如果对一个被gzip的文件执行范围请求,比如“Range:bytes=10-19”,那么这个范围是应用于原文件大小

sailei1 commented 5 years ago

连接管理

短连接

它底层的数据传输基于TCP/IP,每次发送请求前需要先与服务器建立连接,收到响应报文后会立即关闭连接。

短连接的缺点相当严重,因为在TCP协议里,建立连接和关闭连接都是非常“昂贵”的操作。TCP建立连接 要有“三次握手”,发送3个数据包,需要1个RTT;关闭连接是“四次挥手”,4个数据包需要2个RTT。 而HTTP的一次简单“请求-响应”通常只需要4个包,如果不算服务器内部的处理时间,最多是2个RTT。这 么算下来,浪费的时间就是“3÷5=60%”,有三分之二的时间被浪费掉了,传输效率低得惊人。

长连接

既然TCP的连接和关闭非常耗时间,那么就把这个时间成本由原来的一个“请求-应答”均摊到多个“请求-应答”上。

连接相关字段 由于长连接对性能的改善效果非常显著,所以在HTTP/1.1中的连接都会默认启用长连接。 请求头 Connection:keep-alive // connection: upgrade 返回101 表示协议升级,例如http 切换到websocket

如果服务器支持长连接,它总会在响应报文里放一个“Connection: keep-alive”字段,告诉客户端:“我是支持长连接的,接下来就用这个TCP一直收发数据吧”。

因为TCP连接长时间不关闭,服务器必须在内存里保存它的状态,这就占用了服务器的资源。

在客户端,可以在请求头里加上“Connection:close”字段,告诉服务器:“这次通信后就关闭连接”。服 务器看到这个字段,就知道客户端要主动关闭连接,于是在响应报文里也加上这个字段,发送之后就调用 Socket API关闭TCP连接。

HTTP/1.1 客户端假定在收到响应后,除非响应中包含了 Connection: close 首部,不然 HTTP/1.1连接就仍维持在打开状态。但是,客户端和服务器仍然可以随时关闭空闲的连接。 不发送 Connection: close 并不意味着服务器承诺永远将连接保持在打开状态。

队头阻塞 “队头阻塞”与短连接和长连接无关,而是由HTTP基本的“请求-应答”模型所导致的。 因为HTTP规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。队列里的请求没有轻 重缓急的优先级,只有入队的先后顺序,排在最前面的请求被最优先处理。

如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本。

“队头阻塞”问题会导致性能下降,可以用“并发连接 (浏览器同一域名下最多6-8个)”和“域名分片 (多开几个二级域名)”技术缓解。

应当如何降低长连接对服务器的负面影响呢? 长连接会长期占用服务器资源,根据服务器性能设置连接数和长连接超时时间,保证服务器TCP资源使用 处于正常范围。

sailei1 commented 5 years ago

重定向和跳转

Location: /index.html

“Location”字段属于响应字段,必须出现在响应报文里。但只有配合301/302状态码才有意义,它标记了 服务器要求重定向的URI,这里就是要求浏览器跳转到“index.html”。

重定向实际上发送了两次HTTP请求,第一个请求返回了302,然后第二个 请求就被重定向到了“/index.html”

301 浏览器看到301,就知道原来的URI“过时”了,就会做适当的优化。比如历史记录、更新书签,下次可能 就会直接用新的URI访问,省去了再次跳转的成本。搜索引擎的爬虫看到301,也会更新索引库,不再使用 老的URI。

302 浏览器或者爬虫看到302,会认为原来的URI仍然有效,但暂时不可用,所以只会执行简单的跳转页面,不 记录新的URI,也不会有其他的多余动作,下次访问还是用原URI。

重定向可以把一个URI指向另一个URI,也可以把多个URI指向同一个URI,用途很多;

sailei1 commented 5 years ago

cookie

请求头字段Cookie 响应头字段Set-Cookie

当用户通过浏览器第一次访问服务器的时候,服务器肯定是不知道他的身份的。所以,就要创建一个独特的 身份标识数据,格式是“key=value”,然后放进Set-Cookie字段里,随着响应报文一同发给浏览器。 浏览器收到响应报文,看到里面有Set-Cookie,知道这是服务器给的身份标识,于是就保存起来,下次再请 求的时候就自动把这个值放进Cookie字段里发给服务器。 因为第二次请求里面有了Cookie字段,服务器就知道这个用户不是新人,之前来过,就可以拿出Cookie里 的值,识别出用户的身份,然后提供个性化的服务。

Cookie是由浏览器负责存储的,而不是操作系统 一般不能超过4K 不同浏览器间cookie总大小也不同

Cookie

Expires”俗称“过期时间”,用的是绝对时间点,可以理解为“截止日期”(deadline)。“Max- Age”用的是相对时间,单位是秒,浏览器用收到报文的时间点再加上Max-Age,就可以得到失效的绝对时 间。 Expires和Max-Age可以同时出现,两者的失效时间可以一致,也可以不一致,但浏览器会优先采用Max-Age 计算失效期。

作用域,让浏览器仅发送给特定的服务器和URI,避免被其他网站盗用

作用域的设置比较简单,“Domain”和“Path”指定了Cookie所属的域名和路径,浏览器在发送Cookie前 会从URI中提取出host和path部分,对比Cookie的属性。如果不满足条件,就不会在请求头里发送Cookie。

HttpOnly http只读,禁止其他方式访问

SameSite”可以防范“跨站请求伪造”(XSRF)攻击,设置成“SameSite=Strict”可以严格 限定Cookie不能随着跳转链接跨站发送,而“SameSite=Lax”则略宽松一点,允许GET/HEAD等安全方法, 但禁止POST跨站发送。

Secure ,表示这个Cookie仅能用HTTPS协议加密传输,明文的HTTP协议会禁止发送。 但Cookie本身不是加密的,浏览器里还是以明文的形式存在。

sailei1 commented 5 years ago

缓存

缓存控制

1 浏览器发现缓存无数据,于是发送请求,向服务器获取资源; 2 服务器响应请求,返回资源,同时标记资源的有效期; 3 浏览器缓存资源,等待下次重用。

Cache-Control:max-age=30; 服务器标记资源有效期使用的头字段是“Cache-Control”,里面的值“max-age=30”就是资源的有效时 间,相当于告诉浏览器,“这个页面只能缓存30秒,之后就算是过期,不能用。”

时间的计算起点是响应报文的创建时刻(即Date字段,也就是离开服务器的时刻),而不是客 户端收到报文的时刻,也就是说包含了在链路传输过程中所有节点所停留的时间。 no_store:不允许缓存 no_cache:可以缓存,但在使用之前必须要去服务器验证是否过期,是否有最新的版本 must-revalidate:如果缓存不过期就可以继续使用,但过期了如果还想用就必须去服务器验证。

缓存检查优先级 no_store > no_cache> must-revalidate > max-age

客户端缓存

“Cache-Control: no-cache”,含义和“max-age=0”基本一样 点一下浏览器的“前进”“后退”按钮,再看开发者工具,你就会惊喜地发现“from disk cache”的字样,意思是没有发送网络请求,而是读取的磁盘上的缓存。

条件请求 条件请求一共有5个头字段,我们最常用的是“if-Modified-Since”和“If-None-Match”这两个。需要第一次的响应报文预先提供“Last-modified”和“ETag”,然后第二次请求时就可以带上缓存里的原值,验证资源是否是最新的。 如果资源没有变,服务器就回应一个“304 Not Modified”,表示缓存依然有效,浏览器就可以更新一下有 效期,然后放心大胆地使用缓存了。

ETag 资源唯一标识 使用ETag就可以精确地识别资源的变动情况,让浏览器能够更有效地利用缓存。

条件请求里其他的三个头字段是“If-Unmodified-Since”“If-Match”和“If-Range

除了 Cache-Control 服务器也可以用Expires 字段来标记资源的有效期 形式跟cookie 差不多 优先级 低于 Cache-Control 还有一个历史遗留字段 Pragma:no-cache 它相当于Cache-Control:no-cache 除非为了兼容HTTP1.0 否则不建议使用

<META HTTP-EQUIV="Pragma" CONTENT="no-cache">

上述代码的作用是告诉浏览器当前页面不被缓存,每次访问都需要去服务器拉取。使用上很简单,但只有部分浏览器可以支持

强制刷新 – 当按下ctrl+F5来刷新页面的时候, 浏览器将绕过各种缓存(本地缓存和协商缓存), 直接让服务器返回最新的资源

sailei1 commented 5 years ago

代理

HTTP代理就是客户端和服务器通信链路中的一个中间环节,为两端提供“代理服务”; 代理最基本的一个功能是负载均衡

代理服务器需要用字段“Via”标明代理的身份 Via是一个通用字段,请求头或响应头里都可以出现。每当报文经过一个代理节点,代理服务器就会把自身的信息追加到字段的末尾,就像是经手人盖了一个章。

image

Via字段只解决了客户端和源服务器判断是否存在代理的问题,还不能知道对方的真实信息。

X-Forwarded-For”的字面意思是“为谁而转发”,形式上和“Via”差不多,也是每经过一个代理节点就会在字段里追加一个信息。但“Via”追加的是代理主机名(或者域名),而“X-Forwarded-For”追加的是 请求方的IP地址。所以,在字段里最左边的IP地址就客户端的地址。 因为http 是明文传输,请求头很容易被窜改,所以 X-Forwarded-For 也不是完全可信的

X-Real-IP”是另一种获取客户端真实IP的手段,它的作用很简单,就是记录客户端IP地址,没有中间的代理信息

sailei1 commented 5 years ago

代理缓存

HTTP传输链路上,不只是客户端有缓存,服务器上的缓存也是非常有价值的,可以让请求不必走完整个 后续处理流程,“就近”获得响应结果。

下一次再有相同的请求,代理服务器就可以直接发送304或者缓存数据,不必再从源服务器那里获取。这样 就降低了客户端的等待时间,同时节约了源服务器的网络带宽。

Cache-Control”属性:max-age、no_store、no_cache和must-revalidate 这4种缓存属性可以约束客户端,也可以约束代理。

首先,我们要区分客户端上的缓存和代理上的缓存,可以使用两个新属性“private”和“public”。 “private”表示缓存只能在客户端保存,是用户“私有”的,不能放在代理上与别人共享。 而“public”的意思就是缓存完全开放,谁都可以存,谁都可以用。

再次,缓存的生存时间可以使用新的“s-maxage”(s是share的意思,注意maxage中间没有“-”),只 限定在代理上能够存多久,而客户端仍然使用“max_age” 代理专用的属性“no-transform” 表示不得修改

我还要提醒你一点,源服务器在设置完“Cache-Control”后必须要为报文加上“Last- modified”或“ETag”字段。否则,客户端和代理后面就无法使用条件请求来验证缓存是否有效,也就不 会有304缓存重定向。

关于缓存的生存时间,多了两个新属性“max-stale”和“min-fresh”。 “max-stale”的意思是如果代理上的缓存过期了也可以接受,但不能过期太多,超过x秒也会不要。 “min-fresh”的意思是缓存必须有效,而且必须在x秒后依然有效。

有的时候客户端还会发出一个特别的“only-if-cached”属性,表示只接受代理缓存的数据,不接受源服务 器的响应。如果代理上没有缓存或者缓存过期,就应该给客户端返回一个504(Gateway Timeout)。

Cache-Control: public, max-age=10,s-maxage=30”,数据可以在浏览器里存10秒,在代理上 存30秒,

其他问题 第一个是“Vary”字段,它是内容协商的结果,相当于报文的一个版本标记。 同一个请求,经过内容协商后可能会有不同的字符集、编码、浏览器等版本。比如,“Vary: Accept-Encoding”“Vary: User-Agent”,缓存代理必须要存储这些不同的版本。 当再收到相同的请求时,代理就读取缓存里的“Vary”,对比请求头里相应的“ Accept- Encoding”“User-Agent”等字段,如果和上一个请求的完全匹配,比如都是“gzip”“Chrome”,就表 示版本一致,可以返回缓存的数据。

另一个问题是“Purge”,也就是“缓存清理”,它对于代理也是非常重要的功能,例如: 过期的数据应该及时淘汰,避免占用空间; 源站的资源有更新,需要删除旧版本,主动换成最新版(即刷新); 有时候会缓存了一些本不该存储的信息,例如网络谣言或者危险链接,必须尽快把它们删除。 清理缓存的方法有很多,比较常用的一种做法是使用自定义请求方法“PURGE”,发给代理服务器,要求 删除URI对应的缓存数据。

CURL -I -X PURGE http://xxx.com/static/js/xx.js
sailei1 commented 5 years ago

HTTPS

浏览器首先要从URI里提取出协议名和域名。因为协议名 是“https”,所以浏览器就知道了端口号是默认的443,它再用DNS解析域名,得到目标的IP地址,然后就可以使用三次握手与网站建立TCP连接了。

在HTTP协议里,建立连接后,浏览器会立即发送请求报文。但现在是HTTPS协议,它需要再用另外一 个“握手”过程,在TCP上建立安全连接,之后才是收发HTTP报文。

握手的目标是安全地交换对称密钥,需要三个随机数,第三个随机数“Pre-Master”必须加密传输,绝对不能让黑客破解;