九江县建设规划局网站,wordpress 删除demo,做游戏钓鱼网站,dedecms模板安装教程点击上方“码农沉思录”#xff0c;选择“设为星标”优质文章#xff0c;及时送达HTTP 内容协商什么是内容协商在 HTTP 中#xff0c;内容协商是一种用于在同一 URL 上提供资源的不同表示形式的机制。内容协商机制是指客户端和服务器端就响应的资源内容进行交涉#xff0c;… 点击上方“码农沉思录”选择“设为星标”优质文章及时送达HTTP 内容协商什么是内容协商在 HTTP 中内容协商是一种用于在同一 URL 上提供资源的不同表示形式的机制。内容协商机制是指客户端和服务器端就响应的资源内容进行交涉然后提供给客户端最为适合的资源。内容协商会以响应资源的语言、字符集、编码方式等作为判断的标准。内容协商的种类内容协商主要有以下3种类型服务器驱动协商(Server-driven Negotiation)这种协商方式是由服务器端进行内容协商。服务器端会根据请求首部字段进行自动处理客户端驱动协商(Agent-driven Negotiation)这种协商方式是由客户端来进行内容协商。透明协商(Transparent Negotiation)是服务器驱动和客户端驱动的结合体是由服务器端和客户端各自进行内容协商的一种方法。内容协商的分类有很多种主要的几种类型是 Accept、Accept-Charset、Accept-Encoding、Accept-Language、Content-Language。一般来说客户端用 Accept 头告诉服务器希望接收什么样的数据而服务器用 Content 头告诉客户端实际发送了什么样的数据。为什么需要内容协商我们为什么需要内容协商呢在回答这个问题前我们先来看一下 TCP 和 HTTP 的不同。在 TCP / IP 协议栈里传输数据基本上都是 headerbody 的格式。但 TCP、UDP 因为是传输层的协议它们不会关心 body 数据是什么只要把数据发送到对方就算是完成了任务。而 HTTP 协议则不同它是应用层的协议数据到达之后需要告诉应用程序这是什么数据。当然不告诉应用这是哪种类型的数据应用也可以通过不断尝试来判断但这种方式无疑十分低效而且有很大几率会检查不出来文件类型。所以鉴于此浏览器和服务器需要就数据的传输达成一致浏览器需要告诉服务器自己希望能够接收什么样的数据需要什么样的压缩格式什么语言哪种字符集等而服务器需要告诉客户端自己能够提供的服务是什么。所以我们就引出了内容协商的几种概念下面依次来进行探讨内容协商标头Accept接受请求 HTTP 标头会通告客户端自己能够接受的 MIME 类型那么什么是 MIME 类型呢在回答这个问题前你应该先了解一下什么是 MIMEMIME: MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。也就是说MIME 类型其实就是一系列消息内容类型的集合。那么 MIME 类型都有哪些呢文本文件text/html、text/plain、text/css、application/xhtmlxml、application/xml图片文件image/jpeg、image/gif、image/png视频文件video/mpeg、video/quicktime应用程序二进制文件application/octet-stream、application/zip比如如果浏览器不支持 PNG 图片的显示那 Accept 就不指定image/png而指定可处理的 image/gif 和 image/jpeg 等图片类型。一般 MIME 类型也会和 q 这个属性一起使用q 是什么q 表示的是权重来看一个例子Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8这是什么意思呢若想要给显示的媒体类型增加优先级则使用 q 来额外表示权重值没有显示权重的时候默认值是1.0 我给你列个表格你就明白了qMIME1.0text/html1.0application/xhtmlxml0.9application/xml0.8* / *也就是说这是一个放置顺序权重高的在前低的在后application/xml;q0.9 是不可分割的整体。Accept-CharsetAccept-charset 属性规定服务器处理表单数据所接受的字符编码Accept-charset 属性允许你指定一系列字符集服务器必须支持这些字符集从而得以正确解释表单中的数据。Accept-Charset 没有对应的标头服务器会把这个值放在 Content-Type中用 charsetxxx来表示例如浏览器请求 GBK 或 UTF-8 的字符集然后服务器返回的是 UTF-8 编码就是下面这样Accept-Charset: gbk, utf-8Content-Type: text/html; charsetutf-8Accept-Language首部字段 Accept-Language 用来告知服务器用户代理能够处理的自然语言集(指中文或英文等)以及自然语言集的相对优先级。可一次指定多种自然语言集。和 Accept 首部字段一样按权重值 q 来表示相对优先级。Accept-Language: en-US,en;q0.5Accept-Encoding表示 HTTP 标头会标明客户端希望服务端返回的内容编码这通常是一种压缩算法。Accept-Encoding 也是属于内容协商 的一部分使用并通过客户端选择 Content-Encoding 内容进行返回。即使客户端和服务器都能够支持相同的压缩算法服务器也可能选择不压缩并返回这种情况可能是由于这两种情况造成的:要发送的数据已经被压缩了一次第二次压缩并不会导致发送的数据更小服务器过载无法承受压缩带来的性能开销通常如果服务器使用 CPU 超过 80% Microsoft 则建议不要使用压缩下面是 Accept-Encoding 的使用方式Accept-Encoding: gzipAccept-Encoding: compressAccept-Encoding: deflateAccept-Encoding: brAccept-Encoding: identityAccept-Encoding: *Accept-Encoding: deflate, gzip;q1.0, *;q0.5上面的几种表述方式就已经把 Accept-Encoding 的属性列全了gzip: 由文件压缩程序 gzip 生成的编码格式使用 Lempel-Ziv编码(LZ77)和32位CRC的压缩格式感兴趣的同学可以读一下 (https://en.wikipedia.org/wiki/LZ77_and_LZ78#LZ77)compress: 使用Lempel-Ziv-Welch(LZW)算法的压缩格式有兴趣的同学可以读 (https://en.wikipedia.org/wiki/LZW)deflate: 使用 zlib 结构和 deflate 压缩算法的压缩格式参考 (https://en.wikipedia.org/wiki/Zlib) 和 (https://en.wikipedia.org/wiki/DEFLATE)br: 使用 Brotli 算法的压缩格式参考 (https://en.wikipedia.org/wiki/Brotli)不执行压缩或不会变化的默认编码格式* : 匹配标头中未列出的任何内容编码如果没有列出 Accept-Encoding 这就是默认值并不意味着支持任何算法只是表示没有偏好;q 采用权重 q 值来表示相对优先级这点与首部字段 Accept 相同。Content-TypeContent-Type 实体标头用于指示资源的 MIME 类型。作为响应Content-Type 标头告诉客户端返回的内容的内容类型实际上是什么。Content-type 有两种值 : MIME 类型和字符集编码例如Content-Type: text/html; charsetUTF-8在某些情况下浏览器将执行 MIME 嗅探并且不一定遵循此标头的值为防止此行为可以将标头 X-Content-Type-Options 设置为 nosniff。Content-EncodingContent-Encoding 实体标头用于压缩媒体类型它让客户端知道如何进行解码操作从而使客户端获得 Content-Type 标头引用的 MIME 类型。表示如下Content-Encoding: gzipContent-Encoding: compressContent-Encoding: deflateContent-Encoding: identityContent-Encoding: brContent-Encoding: gzip, identityContent-Encoding: deflate, gzipContent-LanguageContent-Language 实体标头用于描述面向受众的语言以便使用户根据用户自己的首选语言进行区分。例如Content-Language: de-DEContent-Language: en-USContent-Language: de-DE, en-CA下面根据内容协商对应的请求/响应标头我列了一张图供你参考注意其中 Accept-Charset 没有对应的 Content-Charset 而是通过 Content-Type 来表示。HTTP 认证HTTP 提供了用于访问控制和身份认证的功能下面就对 HTTP 的权限和认证功能进行介绍通用 HTTP 认证框架RFC 7235 定义了 HTTP 身份认证框架服务器可以根据其文档的定义来检查客户端请求。客户端也可以根据其文档定义来提供身份验证信息。请求/响应的工作流程如下服务器以401(未授权) 的状态响应客户端告诉客户端服务器需要认证信息客户端提供至少一个 www-Authenticate 的响应标头进行授权信息的认证。想要通过服务器进行身份认证的客户端可以在请求标头字段中添加认证标头进行身份认证一般的认证过程如下首先客户端发起一个 HTTP 请求不带有任何认证标头服务器对此 HTTP 请求作出响应发现此 HTTP 信息未带有认证凭据服务器通过 www-Authenticate标头返回 401 告诉客户端此请求未通过认证。然后客户端进行用户认证认证完毕后重新发起 HTTP 请求这次 HTTP 请求带有用户认证凭据(注意整个身份认证的过程必须通过 HTTPS 连接保证安全)到达服务器后服务器会检查认证信息如果不符合服务器认证信息会返回 403 Forbidden 表示用户认证失败如果满足认证信息则返回 200 OK。我们知道客户端和服务器之间的 HTTP 连接可以被代理缓存重新发送所以认证信息也适用于代理服务器。代理认证由于资源认证和代理认证可以共存因此需要不同的头和状态码在代理的情况下会返回状态码 407(需要代理认证) Proxy-Authenticate 响应头包含至少一个适用于代理的情况Proxy-Authorization请求头用于将证书提供给代理服务器。下面分别来认识一下这两个标头Proxy-AuthenticateHTTP Proxy-Authenticate 响应标头定义了身份验证方法应使用该身份验证方法来访问代理服务器后面的资源。它将请求认证到代理服务器从而允许它进一步发送请求。例如Proxy-Authenticate: BasicProxy-Authenticate: Basic realmAccess to the internal siteProxy-Authorization这个 HTTP 请求标头和上面的 Proxy-Authenticate 拼接很相似但是概念不同这个标头用于向代理服务器提供凭据例如Proxy-Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l下面是代理服务器的请求/响应认证过程这个过程和通用的过程类似我们就不再详细展开描述了。禁止访问如果代理服务器收到的有效凭据不足以获取对给定资源的访问权限则服务器应使用403 Forbidden状态代码进行响应。与 401 Unauthorized 和 407 Proxy Authorization Required 不同该用户无法进行身份验证。WWW-Authenticate 和 Proxy-Authenticate 头WWW-Authenticate 和 Proxy-Authenticate 响应头定义了获得对资源访问权限的身份验证方法。他们需要指定使用哪种身份验证方案以便希望授权的客户端知道如何提供凭据。它们的一般表示形式如下WWW-Authenticate: realmProxy-Authenticate: realm我想你从上面看到这里一定会好奇 和 realm是什么东西现在就来解释下。 是认证协议Basic 是下面协议中最普遍使用的RFC 7617 中定义了Basic HTT P身份验证方案该方案将凭据作为用户ID /密码对传输并使用 base64 进行编码。(感兴趣的同学可以看看 https://tools.ietf.org/html/rfc7617)其他的认证协议主要有认证协议参考来源Basic查阅 RFC 7617base64编码的凭据Bearer查阅 RFC 6750承载令牌来访问受 OAuth 2.0保护的资源Digest查阅 RFC 7616Firefox仅支持md5哈希请参见错误bug 472823以获得SHA加密支持HOBA查阅 RFC 7486Mutual查阅 RFC 8120AWS4-HMAC-SHA256查阅 AWS docsrealm 用于描述保护区或指示保护范围这可能是诸如 Access to the staging site(访问登陆站点) 或者类似的这样用户就可以知道他们要访问哪个区域。Authorization 和 Proxy-Authorization 标头Authorization 和 Proxy-Authorization 请求标头包含用于通过代理服务器对用户代理进行身份验证的凭据。在此再次需要类型其后是凭据取决于使用哪种身份验证方案可以对凭据进行编码或加密。一般表示如下Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1lProxy-Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1lHTTP 缓存通过把请求/响应缓存起来有助于提升系统的性能Web 缓存减少了延迟和网络传输量因此减少资源获取锁需要的时间。由于链路漫长网络时延不可控浏览器使用 HTTP 获取资源的成本较高。所以非常有必要把数据缓存起来下次再请求的时候尽可能地复用。当 Web 缓存在其存储中具有请求的资源时它将拦截该请求并直接返回资源而不是到达源服务器重新下载并获取。这样做可以实现两个小目标减轻服务器负载提升系统性能下面我们就一起来探讨一下 HTTP 缓存都有哪些不同类型的缓存HTTP 缓存有几种不同的类型这些可以分为两个主要类别私有缓存 和 共享缓存。共享缓存共享缓存是一种缓存它可以存储多个用户重复使用的请求/响应。私有缓存私有缓存也称为专用缓存它只适用于单个用户。不缓存过期资源所有的请求都会直接到达服务器由服务器来下载资源并返回。我们主要探讨浏览器缓存和代理缓存但真实情况不只有这两种缓存还有网关缓存CDN反向代理缓存和负载平衡器把它们部署在 Web 服务器上可以提高网站和 Web 应用程序的可靠性性能和可伸缩性。不缓存过期资源不缓存过期资源即浏览器和代理不会缓存过期资源客户端发起的请求会直接到达服务器可以使用 no-cache 标头代表不缓存过期资源。no-cache 属于 Cache-Control 通用标头其一般的表示方法如下Cache-Control: no-cache也可以使用 max-age 0 来实现不缓存的效果。Cache-Control: max-age0私有缓存私有缓存只用来缓存单个用户你可能在浏览器设置中看到了 缓存浏览器缓存包含服务器通过 HTTP 下载下来的所有文档。这个高速缓存用于使访问的文档可以进行前进/后退保存操作而无需重新发送请求到源服务器。可以使用 private 来实现私有缓存这与 public 的用法相反缓存服务器只对特定的客户端进行缓存其他客户端发送过来的请求缓存服务器则不会返回缓存。它的一般表示方法如下Cache-Control: private共享缓存共享缓存是一种用于存储要由多个用户重用的响应缓存。共享缓存一般使用 public 来表示public 属性只出现在客户端响应中表示响应可以被任何缓存所缓存。一般表示方法如下Cache-Control: public缓存控制HTTP/1.1 中的 Cache-Control 常规标头字段用于执行缓存控制使用此标头可通过其提供的各种指令来定义缓存策略。下面我们依次介绍一下这些属性不缓存no-store 才是真正意义上的不缓存每次服务器接受到客户端的请求后都会返回最新的资源给客户端。Cache-Control: no-store缓存但需要验证同上面的 不缓存过期资源私有和共享缓存同上缓存过期缓存中一个很重要的指令就是max-age这是资源被视为新鲜的最长时间 与 Expires 相反此指令是相对于请求时间的。对于应用程序中不会更改的文件通常可以添加主动缓存。下面是 mag-age 的表示Cache-Control: max-age31536000缓存验证must-revalidate 表示缓存必须在使用之前验证过时资源的状态并且不应使用过期的资源。Cache-Control: must-revalidate下面是一个缓存验证图什么是新鲜的数据一旦资源存储在缓存中理论上就可以永远被缓存使用。但是不管是浏览器缓存还是代理缓存其存储空间是有限的所以缓存会定期进行清除这个过程叫做 缓存回收(cache eviction) (自译)。另一方面服务器上的缓存也会定期进行更新HTTP 作为应用层的协议它是一种客户-服务器模式HTTP 是无状态的协议因此当资源发生更改时服务器无法通知缓存和客户端。因此服务器必须通过某种方式告知客户端缓存已经被更新。服务器会提供过期时间这个概念告知客户端在此到期时间之前资源是新鲜的也就是未更改过的。在此到期时间的范围之外资源已过时。过期算法(Eviction algorithms) 通常会将新资源优先于陈旧资源使用。这里需要注意一下过期的资源并不会被回收或忽略当高速缓存接收到过期资源时它会使用 If-None-Match 转发此请求以检查它是否仍然有效。如果有效服务器会返回 304 Not Modified响应头并且没有任何响应体从而节省了一些带宽。下面是使用共享缓存代理的过程这个图应该比较好理解只说一下 Age 的作用Age 是 HTTP 响应标头告诉客户端源服务器在多久之前创建了响应它的单位为秒Age 标头通常接近于0如果是0则可能是从源服务器获取的如果不是表示可能是由代理服务器创建那么 Age 的值表示的是缓存后的响应再次发起认证到认证完成的时间值。缓存的有效性是由多个标头来共同决定的而并非某一个标头来决定。如果指定了 Cache-control:max-ageN 那么缓存会保存 N 秒。如果这个通用标头不存在的话则会检查是否存在 Expires 标头。如果 Exprires 标头存在那么它的值减去 Date 标头的值就可以确定其有效性。最后如果max-age 和 expires 都不存在就去寻找 Last-Modified 标头如果存在此标头则高速缓存的有效性等于 Date 标头的值减去 Last-modified 标头的值除以10。缓存验证当到达缓存资源的有效期时将对其进行验证或再次获取。仅当服务器提供了强验证器或弱验证器时才可以进行验证。当用户按下重新加载按钮时将触发重新验证。如果缓存的响应包含 Cache-controlmust-revalidate标头则在正常浏览下也会触发该事件。另一个因素是 高级 - 缓存首选项 面板中的缓存验证首选项。有一个选项可在每次加载文档时强制进行验证。Etag我们上面提到了强验证器和弱验证器实现验证器功能的标头正式 Etag 的作用这意味着 HTTP 用户代理(例如浏览器)不知道该字符串表示什么并且无法预测其值。如果 Etag 标头是资源响应的一部分则客户端可以在未来请求的标头中发出 If-None-Match以验证缓存的资源。Last-Modified响应标头可以用作弱验证器因为它只有1秒可以分辨的时间。如果响应中存在 Last-Modified标头则客户端可以发出 If-Modified-Since请求标头来验证缓存资源。(关于 Etag 更多我们会在条件请求介绍)避免碰撞通过使用 Etag 和 If-Match 标头你可以检测避免碰撞。例如在编辑 MDN 时将对当前 Wiki 内容进行哈希处理并将其放入响应中的 Etag 中Etag: 33a64df551425fcc55e4d42a148795d9f25f89d4当将更改保存到 Wiki 页面(发布数据)时POST 请求将包含 If-Match 标头其中包含 Etag 值以检查有效性。If-Match: 33a64df551425fcc55e4d42a148795d9f25f89d4如果哈希值不匹配则表示文档已在中间进行了编辑并返回 412 Precondition Failed 错误。缓存未占用资源Etag 标头的另一个典型用法是缓存未更改的资源如果用户再次访问给定的 URL(已设置Etag)并且该 URL过时则客户端将在 If-None-Match 标头字段中发送其 Etag 的值If-None-Match: 33a64df551425fcc55e4d42a148795d9f25f89d4服务器将客户端的 Etag(通过 If-None-Match 发送)与 Etag 进行比较以获取其当前资源版本如果两个值都匹配(即资源未更改)则服务器会发回 304 Not Modified状态没有主体它告诉客户端响应的缓存仍然可以使用。HTTP CROS 跨域CROS 的全称是 Cross-Origin Resource Sharing(CROS)中文译为 跨域资源共享它是一种机制。是一种什么机制呢它是一种让运行在一个域(origin)上的 Web 应用被准许访问来自不同源服务器上指定资源的机制。在搞懂这个机制前你需要线了解什么是 域(origin)OriginWeb 概念中域(Origin) 的内容由scheme(protocol) - 协议host(domain) - 主机和用于访问它的 URL port - 端口定义。仅仅当 scheme 、host、port 都匹配时两个对象才有相同的来源。这种协议相同域名相同端口相同的安全策略也被称为 同源策略(Same Origin Policy)。某些操作仅限于具有相同来源的内容可以使用 CORS 取消此限制。跨域的特点下面是跨域问题的例子看看你是否清楚什么是跨域了(1) http://example.com/app1/index.html(2) http://example.com/app2/index.html上面这两个 URL 是否具有跨域问题呢上面两个 URL 是不具有跨域问题的因为这两个 URL 具有相同的协议(scheme)和主机(host)那么下面这两个是否具有跨域问题呢http://Example.com:80http://example.com这两个 URL 也不具有跨域问题为什么不具有端口不一样啊。其实它们两个端口是一样的。或许你会认为这两个 URL 是不一样的放心关于一样不一样的论据我给你抛出来了协议和域名部分是不区分大小写的但是路径部分则根据服务器平台而定。Windows 和 Mac OS X 系统是不区分大小写的而采用UNIX和Linux系的服务器系统是区分大小写的也就是说上面的 Example.com 和 example.com 其实是一个网址并且由于两个地址具有相同的 scheme 和 host 默认情况下服务器通过端口80传递 HTTP 内容所以上面这两个地址也是相同的。下面这两个 URL 地址是否具有跨域问题http://example.com/app1https://example.com/app2这两个 URL 的 scheme 不同所以这两个 URL 具有跨域问题再看下面这三个 URL 是否具有跨域问题http://example.comhttp://www.example.comhttp://myapp.example.com这三个 URL 也是具有跨域问题的因为它们隶属于不通服务器的主机 host。下面这两个 URL 是否具有跨域问题http://example.comhttp://example.com:8080这两个 URL 也是具有跨域问题因为这两个 URL 的默认端口不一样。同源策略处于安全的因素浏览器限制了从脚本发起跨域的 HTTP 请求。XMLHttpRequest 和其他 Fetch 接口 会遵循 同源策略(same-origin policy)。也就是说使用这些 API 的应用程序想要请求相同的资源那么他们应该具有相同的来源除非来自其他来源的响应包括正确的 CORS 标头也可以。同源策略是一种很重要的安全策略它限制了从一个来源加载的文档或脚本如何与另一个来源的资源进行交互。它有助于隔离潜在的恶意文档减少可能的攻击媒介。我们上面提到如果两个 URL 具有相同的协议、主机和端口号(如果指定)的话那么两个 URL 具有相同的来源。下面有一些实例你判断一下是不是具有相同的来源目标来源 http://store.company.com/dir/page.html现在我带你认识了两遍不同的源现在你应该知道如何区分两个 URL 是否属于同一来源了吧好你现在知道了什么是跨域问题现在我要问你哪些请求会产生跨域请求呢这是我们下面要讨论的问题跨域请求跨域请求可能会从下面这几种请求中发出调用 XMLHttpRequest 或者 Fetch api。XMLHttpRequest 是什么(我是后端程序员前端不太懂简单解释下如果解释的不好还请前端大佬们不要胖揍我)所有的现代浏览器都有一个内置的 XMLHttpReqeust 对象这个对象可以用于从服务器请求数据。XMLHttpReqeust 对于开发人员来说很重要XMLHttpReqeust 对象可以用来做下面这些事情更新网页无需重新刷新页面页面加载后从服务器请求数据页面加载后从服务端获取数据在后台将数据发送到服务器使用 XMLHttpRequest(XHR) 对象与服务器进行交互你可以从 URL 检索数据从而不必刷新整个页面这使网页可以更新页面的一部分而不会中断用户的操作。XMLHttpRequest 在 AJAX 异步编程中使用很广泛。再来说一下 Fetch API 是什么Fetch 提供了请求和响应对象(以及其他网络请求)的通用定义。它还提供了相关概念的定义例如 CORS 和 HTTP Origin 头语义并在其他地方取代了它们各自的定义。Web 字体(用于 CSS 中 font-face中的跨域字体使用)以便服务器可以部署 TrueType 字体这些字体只能由允许跨站点加载和使用的网站使用。WebGL 纹理使用 drawImage() 绘制到画布上的图像/视频帧图片的 CSS 形状跨域功能概述跨域资源共享标准通过添加新的 HTTP 标头来工作这些标头允许服务器描述允许哪些来源从 Web 浏览器读取信息。另外对于可能导致服务器数据产生副作用的 HTTP 请求方法(尤其是 GET 或者具有某些 MIME 类型 POST 方法以外 HTTP 方法)该规范要求浏览器预检请求使用 HTTP OPTIONS 请求方法从服务器请求受支持的方法然后在服务器批准后发送实际请求。服务器还可以通知客户端是否应与请求一起发送凭据(例如 Cookies 和 HTTP 身份验证)。注意CORS 故障会导致错误但是出于安全原因该错误的详细信息不适用于 JavaScript。所有代码都知道发生了错误。确定具体出问题的唯一方法是查看浏览器的控制台以获取详细信息。访问控制下面我会和大家探讨三种方案这些方案都演示了跨域资源共享的工作方式。所有这些示例都使用XMLHttpRequest它可以在任何支持的浏览器中发出跨站点请求。简单请求一些请求不会触发 CORS预检(关于预检我们后面再介绍)。简单请求是满足一下所有条件的请求允许以下的方法GET、HEAD和 POST除了由用户代理自动设置的标头(例如 Connection、User-Agent 或者在 Fetch 规范中定义为禁止标头名称的其他标头)外唯一允许手动设置的标头是那些 Fetch 规范将其定义为 CORS安全列出的请求标头 它们是AcceptAccept-LanguageContent-LanguageContent-Type(下面会介绍)DPRDownlinkSave-DataViewport-WidthWidthContent-Type 标头的唯一允许的值是application/x-www-form-urlencodedmultipart/form-datatext/plain没有在请求中使用的任何 XMLHttpRequestUpload 对象上注册事件侦听器这些可以使用XMLHttpRequest.upload 属性进行访问。请求中未使用 ReadableStream对象。例如假定 web 内容 https://foo.example 想要获取 https://bar.other 域的资源那么 JavaScript 中的代码可能会像下面这样写const xhr new XMLHttpRequest();const url https://bar.other/resources/public-data/;xhr.open(GET, url);xhr.onreadystatechange someHandler;xhr.send();这使用 CORS 标头来处理特权从而在客户端和服务器之间执行某种转换。让我们看看在这种情况下浏览器将发送到服务器的内容并让我们看看服务器如何响应GET /resources/public-data/ HTTP/1.1Host: bar.otherUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8Accept-Language: en-us,en;q0.5Accept-Encoding: gzip,deflateConnection: keep-aliveOrigin: https://foo.example注意请求的标头 Origin 它表明调用来自于 https://foo.example。让我们看看服务器是如何响应的HTTP/1.1 200 OKDate: Mon, 01 Dec 2008 00:23:53 GMTServer: Apache/2Access-Control-Allow-Origin: *Keep-Alive: timeout2, max100Connection: Keep-AliveTransfer-Encoding: chunkedContent-Type: application/xml[…XML Data…]服务端发送 Access-Control-Allow-Origin 作为响应。使用 Origin 标头和 Access-Control-Allow-Origin 展示了最简单的访问控制协议。在这个事例中服务端使用 Access-Control-Allow-Origin 作为响应也就说明该资源可以被任何域访问。如果位于https://bar.other的资源所有者希望将对资源的访问限制为仅来自https://foo.example的请求他们应该发送如下响应Access-Control-Allow-Origin: https://foo.example现在除了 https://foo.example 之外的任何域都无法以跨域方式访问到 https://bar.other 的资源。预检请求和上面探讨的简单请求不同预检请求首先通过 OPTIONS 方法向另一个域上的资源发送 HTTP 请求用来确定实际请求是否可以安全的发送。跨站点这样被预检因为它们可能会影响用户数据。下面是一个预检事例const xhr new XMLHttpRequest();xhr.open(POST, https://bar.other/resources/post-here/);xhr.setRequestHeader(X-PINGOTHER, pingpong);xhr.setRequestHeader(Content-Type, application/xml);xhr.onreadystatechange handler;xhr.send(Arun);上面的事例创建了一个 XML 请求体用来和 POST 请求一起发送。此外设置了非标准请求头 X-PINGOTHER 这个标头不是 HTTP/1.1 的一部分但通常对 Web 程序很有用。由于请求的 Content-Type 使用 application/xml并且设置了自定义标头因此该请求被预检。如下图所示如下所述实际的 POST 请求不包含 Access-Control-Request- * 标头只有 OPTIONS 请求才需要它们。下面我们来看一下完整的客户端/服务器交互首先是预检请求/响应OPTIONS /resources/post-here/ HTTP/1.1Host: bar.otherUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8Accept-Language: en-us,en;q0.5Accept-Encoding: gzip,deflateConnection: keep-aliveOrigin: http://foo.exampleAccess-Control-Request-Method: POSTAccess-Control-Request-Headers: X-PINGOTHER, Content-TypeHTTP/1.1 204 No ContentDate: Mon, 01 Dec 2008 01:15:39 GMTServer: Apache/2Access-Control-Allow-Origin: https://foo.exampleAccess-Control-Allow-Methods: POST, GET, OPTIONSAccess-Control-Allow-Headers: X-PINGOTHER, Content-TypeAccess-Control-Max-Age: 86400Vary: Accept-Encoding, OriginKeep-Alive: timeout2, max100Connection: Keep-Alive上面的1 -11 行代表预检请求预检请求使用 OPYIIONS 方法浏览器根据上面的 JavaScript 代码段所使用的请求参数确定是否需要发送此请求以便服务器可以响应是否可以使用实际请求参数发送请求。OPTIONS 是一种 HTTP / 1.1方法用于确定来自服务器的更多信息并且是一种安全的方法这意味着它不能用于更改资源。请注意与 OPTIONS 请求一起还发送了另外两个请求标头(分别是第9行和第10行)Access-Control-Request-Method: POSTAccess-Control-Request-Headers: X-PINGOTHER, Content-TypeAccess-Control-Request-Method 标头作为预检请求的一部分通知服务器当发送实际请求时将使用POST 请求方法发送该请求。Access-Control-Request-Headers 标头通知服务器当发送请求时它将与X-PINGOTHER 和 Content-Type 自定义标头一起发送。服务器可以确定这种情况下是否接受请求。下面的 1 - 11行是服务器发回的响应表示POST 请求和 X-PINGOTHER 是可以接受的我们着重看一下下面这几行Access-Control-Allow-Origin: http://foo.exampleAccess-Control-Allow-Methods: POST, GET, OPTIONSAccess-Control-Allow-Headers: X-PINGOTHER, Content-TypeAccess-Control-Max-Age: 86400服务器完成响应表明源 http://foo.example 是可以接受的 URL能够允许 POST、GET、OPTIONS 进行请求允许自定义标头 X-PINGOTHER, Content-Type。最后Access-Control-Max-Age 以秒为单位给出一个值这个值表示对预检请求的响应可以缓存多长时间在此期间内无需发送其他预检请求。完成预检请求后将发送实际请求POST /resources/post-here/ HTTP/1.1Host: bar.otherUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8Accept-Language: en-us,en;q0.5Accept-Encoding: gzip,deflateConnection: keep-aliveX-PINGOTHER: pingpongContent-Type: text/xml; charsetUTF-8Referer: https://foo.example/examples/preflightInvocation.htmlContent-Length: 55Origin: https://foo.examplePragma: no-cacheCache-Control: no-cacheArunHTTP/1.1 200 OKDate: Mon, 01 Dec 2008 01:15:40 GMTServer: Apache/2Access-Control-Allow-Origin: https://foo.exampleVary: Accept-Encoding, OriginContent-Encoding: gzipContent-Length: 235Keep-Alive: timeout2, max99Connection: Keep-AliveContent-Type: text/plain[Some GZIPd payload]正式响应中很多标头我们在之前的文章已经探讨过了本篇不再做详细的介绍读者可以参考你还在为 HTTP 的这些概念头疼吗 查阅带凭证的请求XMLHttpRequest 或 Fetch 和 CORS 最有趣的功能就是能够发出知道 HTTP Cookie 和 HTTP 身份验证的 凭证 请求。默认情况下在跨站点 XMLHttpRequest 或 Fetch 调用中浏览器将不发送凭据。调用 XMLHttpRequest对象或 Request 构造函数时必须设置一个特定的标志。在下面这个例子中最初从 http://foo.example 加载的内容对设置了 Cookies 的 http://bar.other 上的资源进行了简单的 GET 请求 foo.example 上可能的代码如下const invocation new XMLHttpRequest();const url http://bar.other/resources/credentialed-content/;function callOtherDomain() {if (invocation) { invocation.open(GET, url, true); invocation.withCredentials true; invocation.onreadystatechange handler; invocation.send(); }}第7行显示 XMLHttpRequest 上的标志必须设置该标志才能使用 Cookie 进行调用。默认情况下调用是不在使用 Cookie 的情况下进行的。由于这是一个简单的 GET 请求因此不会进行预检但是浏览器将拒绝任何没有 Access-Control-Allow-Credentials 的响应标头为true指的是响应不会返回 web 页面的内容。上面的请求用下图可以表示这是客户端和服务器之间的示例交换GET /resources/access-control-with-credentials/ HTTP/1.1Host: bar.otherUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8Accept-Language: en-us,en;q0.5Accept-Encoding: gzip,deflateConnection: keep-aliveReferer: http://foo.example/examples/credential.htmlOrigin: http://foo.exampleCookie: pageAccess2HTTP/1.1 200 OKDate: Mon, 01 Dec 2008 01:34:52 GMTServer: Apache/2Access-Control-Allow-Origin: https://foo.exampleAccess-Control-Allow-Credentials: trueCache-Control: no-cachePragma: no-cacheSet-Cookie: pageAccess3; expiresWed, 31-Dec-2008 01:34:53 GMTVary: Accept-Encoding, OriginContent-Encoding: gzipContent-Length: 106Keep-Alive: timeout2, max100Connection: Keep-AliveContent-Type: text/plain[text/plain payload]上面第10行包含指向http://bar.other 上的内容 Cookie但是如果 bar.other 没有以 Access-Control-Allow-Credentials:true 响应(下面第五行)响应将被忽略并且不能使用网站返回的内容。请求凭证和通配符当回应凭证请求时服务器必须在 Access-Control-Allow-Credentials 中指定一个来源而不能直接写* 通配符因为上面示例代码中的请求标头包含 Cookie 标头如果 Access-Control-Allow-Credentials 中是指定的通配符 * 的话请求会失败。注意上面示例中的 Set-Cookie 响应标头还设置了另外一个值如果发生故障将引发异常(取决于所使用的API)。HTTP 响应标头下面会列出一些服务器跨域共享规范定义的 HTTP 标头上面简单概述了一下现在一起来认识一下主要会介绍下面这些Access-Control-Allow-OriginAccess-Control-Allow-CredentialsAccess-Control-Allow-HeadersAccess-Control-Allow-MethodsAccess-Control-Expose-HeadersAccess-Control-Max-AgeAccess-Control-Request-HeadersAccess-Control-Request-MethodOriginAccess-Control-Allow-OriginAccess-Control-Allow-Origin 是 HTTP 响应标头指示响应是否能够和给定的源共享资源。Access-Control-Allow-Origin 指定单个资源会告诉浏览器允许指定来源访问资源。对于没有凭据的请求 *通配符告诉浏览器允许任何源访问资源。例如如果要允许源 https://mozilla.org 的代码访问资源可以使用如下的指定方式Access-Control-Allow-Origin: https://mozilla.orgVary: Origin如果服务器指定单个来源而不是*通配符则服务器还应在 Vary 响应标头中包含该来源。Access-Control-Allow-CredentialsAccess-Control-Allow-Credentials 是 HTTP 的响应标头这个标头告诉浏览器当包含凭证请求(Request.credentials)时是否将响应公开给前端 JavaScript 代码。这时候你会问到 Request.credentials 是什么玩意不要着急来给你看一下首先来看 Request 是什么玩意实际上Request 是 Fetch API 的一类接口代表着资源请求。一般创建 Request 对象有两种方式使用 Request() 构造函数创建一个 Request 对象还可以通过 FetchEvent.request api 操作来创建再来说下 Request.credentials 是什么意思Request 接口的凭据只读属性指示在跨域请求的情况下用户代理是否应从其他域发送 cookie。(其他 Request 对象的方法详见 https://developer.mozilla.org/en-US/docs/Web/API/Request)当发送的是凭证模式的请求包含 (Request.credentials)时如果 Access-Control-Allow-Credentials 值为 true浏览器将仅向前端 JavaScript 代码公开响应。Access-Control-Allow-Credentials: true凭证一般包括 cookie、认证头和 TLS 客户端证书当用作对预检请求响应的一部分时这表明是否可以使用凭据发出实际请求。注意简单的 GET 请求不会进行预检。可以参考一个实际的例子 https://www.jianshu.com/p/ea485e5665b3Access-Control-Allow-HeadersAccess-Control-Allow-Headers 是一个响应标头这个标头用来响应预检请求它发出实际请求时可以使用哪些HTTP标头。示例自定义标头这是 Access-Control-Allow-Headers 标头的示例。它表明除了像 CROS 安全列出的请求标头外对服务器的 CROS 请求还支持名为 X-Custom-Header 的自定义标头。Access-Control-Allow-Headers: X-Custom-Header多个标头这个例子展示了 Access-Control-Allow-Headers 如何使用多个标头Access-Control-Allow-Headers: X-Custom-Header, Upgrade-Insecure-Requests绕过其他限制尽管始终允许使用 CORS 安全列出的请求标头并且通常不需要在 Access-Control-Allow-Headers 中列出这些标头但是无论如何列出它们都将绕开适用的其他限制。Access-Control-Allow-Headers: Accept这里你可能会有疑问哪些是 CORS 列出的安全标头(别嫌累就是这么麻烦)有下面这些 Accep、Accept-Language、Content-Language、Content-Type 当且仅当包含这些标头时无需在 CORS 上下文中发送预检请求。Access-Control-Allow-MethodsAccess-Control-Allow-Methods 也是响应标头它指定了哪些访问资源的方法可以使用预检请求。例如Access-Control-Allow-Methods: POST, GET, OPTIONSAccess-Control-Allow-Methods: *Access-Control-Expose-HeadersAccess-Control-Expose-Headers 响应标头表明哪些标头可以作为响应的一部分公开。默认情况下仅公开6个CORS安全列出的响应标头分别是Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma如果希望客户端能够访问其他标头则必须使用 Access-Control-Expose-Headers 标头列出它们。下面是示例要公开非 CORS 安全列出的请求标头可以像如下这样指定Access-Control-Expose-Headers: Content-Length要另外公开自定义标头例如 X-Kuma-Revision可以指定多个标头并用逗号分隔Access-Control-Expose-Headers: Content-Length, X-Kuma-Revision在不是凭证请求中你还可以使用通配符Access-Control-Expose-Headers: *但是这不会通配 Authorization 标头因此如果需要公开它则需要明确列出Access-Control-Expose-Headers: *, AuthorizationAccess-Control-Max-AgeAccess-Control-Max-Age 响应头表示预检请求的结果可以缓存多长时间例如Access-Control-Max-Age: 600表示预检请求可以缓存10分钟Access-Control-Request-Headers浏览器在发出预检请求时使用 Access-Control-Request-Headers 请求标头使服务器知道在发出实际请求时客户端可能发送的 HTTP 标头。Access-Control-Request-Headers: X-PINGOTHER, Content-TypeAccess-Control-Request-Method同样的Access-Control-Request-Method 响应标头告诉服务器发出预检请求时将使用那种 HTTP 方法。此标头是必需的因为预检请求始终是 OPTIONS并且使用的方法与实际请求不同。Access-Control-Request-Method: POSTOriginOrigin 请求标头表明匹配的来源它不包含任何信息仅仅包含服务器名称它与 CORS 请求以及 POST 请求一起发送它类似于 Referer 标头但与此标头不同它没有公开整个路径。例如Origin: https://developer.mozilla.orgHTTP 条件请求HTTP 具有条件请求的概念通过比较资源更新生成的值与验证器的值进行比较来确定资源是否进行过更新。这样的请求对于验证缓存的内容、条件请求、验证资源的完整性来说非常重要。原则HTTP 条件请求是根据特定标头的值执行不同的请求这些标头定义了一个前提条件如果前提条件匹配或不匹配则请求的结果将有所不同。对于 安全 的方法像是 GET、用于请求文档的资源仅当条件请求的条件满足时发回文档资源所以这种方式可以节约带宽。什么是安全的方法对于 HTTP 来说安全的方法是不会改变服务器状态的方法换句话说如果方法只是只读操作那么它肯定是安全的方法比如说 GET 请求它肯定是安全的方法因为它只是请求资源。几种常见的方法肯定是安全的它们是 GET、HEAD和 OPTIONS。所有安全的方法都是幂等的(这他妈幂等又是啥意思)但不是所有幂等的方法都是安全的例如 PUT 和 DELETE 都是幂等的但不安全。幂等性如果相同的客户端发起一次或者多次 HTTP 请求会得到相同的结果则说明 HTTP 是幂等的。(我们这次不深究幂等性)对于 非安全 的方法像是 PUT只有原始文档与服务器上存储的资源相同时才可以使用条件请求来传输文档。(PUT 方法通常用来传输文件就像 FTP 协议的文件上传一样)验证所有的条件请求都会尝试检查服务器上存储的资源是否与某个特定版本的资源相匹配。为了满足这种情况条件请求需要指示资源的版本。由于无法和整个文件逐个字符进行比较因此需要把整个文件描绘成一个值然后把此值和服务器上的资源进行比较这种方式称为比较器比较器有两个条件文档的最后修改日期一个不透明的字符串用于唯一标识每个版本称为实体标签或 Etag。比较两个资源是否时相同的版本有些复杂根据上下文有两种相等性检查当期望的是字节对字节进行比较时例如在恢复下载时使用强 Etag进行验证当用户代理需要比较两个资源是否具有相同的内容时使用若 Etag 进行验证HTTP 协议默认使用 强验证它指定何时进行弱验证强验证强验证保证的是字节 级别的验证严格的验证非常严格可能在服务器级别难以保证但是它能够保证任何时候都不会丢失数据但这种验证丢失性能。要使用 Last-Modified 很难实现强验证通常这是通过使用带有资源的 MD5 哈希值的 Etag 来完成的。弱验证弱验证不同于强验证因为如果内容相等它将认为文档的两个版本相同例如一个页面与另一个页面的不同之处仅在于页脚的日期不同因此该页面被认为与其他页面相同。而使用强验证时则被认为这两个版本是不同的。构建一个若验证的 Etag 系统可能会非常复杂因为这需要了解每个页面元素的重要性但是对于优化缓存性能非常有用。下面介绍一下 Etag 如何实现强弱验证。Etag 响应头是特定版本的标识它能够使缓存变得更高效并能够节省带宽因为如果缓存内容未发生变更Web 服务器则不需要重新发送完整的响应。除此之外Etag 能够防止资源同时更新互相覆盖。如果给定 URL 上的资源发生变更必须生成一个新的 Etag 值通过比较它们可以确定资源的两个表示形式是否相同。Etag 值有两种一种是强 Etag一种是弱 Etag强 Etag 值无论实体发生多么细微的变化都会改变其值一般的表示如下Etag: 33a64df551425fcc55e4d42a148795d9f25f89d4弱 Etag 值弱 Etag 值只用于提示资源是否相同。只有资源发生了根本改变产生差异时才会改变 Etag 值。这时会在字段值最开始处附加 W/。Etag: W/0815下面就来具体探讨一下条件请求的标头和 Etag 的关系条件请求条件请求主要包含的标头如下If-MatchIf-None-MatchIf-Modified-SinceIf-Unmodified-SinceIf-RangeIf-Match对于 GET 和 POST 方法服务器仅在与列出的 Etag(响应标头) 之一匹配时才返回请求的资源。这里又多了一个新词 Etag我们稍后再说 Etag 的用法。对于像是 PUT 和其他非安全的方法在这种情况下它仅仅将上传资源。下面是两种常见的案例对于 GET 和 POST 方法会结合使用 Range 标头它可以确保新发送请求的范围与上一个请求的资源相同如果不匹配的话会返回 416 响应。对于其他方法特别是 PUT 方法If-Match 可以防止丢失更新服务器会比对 If-Match 的字段值和资源的 Etag 值仅当两者一致时才会执行请求。反之则返回状态码 412 Precondition Failed 的响应。例如If-Match: bfc13a64729c4290ef5b2c2730249c88ca92d82dIf-Match: *If-None-Match条件请求它与 If-Match 的作用相反仅当 If-None-Match 的字段值与 Etag 值不一致时可处理该请求。对于GET 和 HEAD 仅当服务器没有与给定资源匹配的 Etag 时服务器将返回 200 OK作为响应。对于其他方法仅当最终现有资源的 Etag 与列出的任何值都不匹配时才会处理请求。当 GET 和 POST 发送的 If-None-Match与 Etag 匹配时服务器会返回 304。If-None-Match: bfc13a64729c4290ef5b2c2730249c88ca92d82dIf-None-Match: W/67ab43, 54ed21, 7892ddIf-None-Match: *If-Modified-SinceIf-Modified-Since 是 HTTP 条件请求的一部分只有在给定日期之后服务端修改了请求所需要的资源才会返回 200 OK 的响应。如果在给定日期之后服务端没有修改内容响应会返回 304 并且不带任何响应体。If-Modified-Since 只能使用 GET 和 HEAD 请求。If-Modified-Since 与 If-None-Match 结合使用时它将被忽略除非服务器不支持 If-None-Match。一般表示如下If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT注意这是格林威治标准时间。HTTP 日期始终以格林尼治标准时间表示而不是本地时间。If-RangeIf-Range 也是条件请求如果满足条件(If-Range 的值和 Etag 值或者更新的日期时间一致)则会发出范围请求否则将会返回全部资源。它的一般表示如下If-Range: Wed, 21 Oct 2015 07:28:00 GMTIf-Range: bfc13a64729c4290ef5b2c2730249c88ca92d82dIf-Unmodified-SinceIf-Unmodified-Since HTTP 请求标头也是一个条件请求服务器只有在给定日期之后没有对其进行修改时服务器才返回请求资源。如果在指定日期时间后发生了更新则以状态码 412 Precondition Failed 作为响应返回。If-Unmodified-Since: Wed, 21 Oct 2015 07:28:00 GMT条件请求示例缓存更新条件请求最常见的示例就是更新缓存如果缓存是空或没有缓存则以200 OK的状态发送回请求的资源。如下图所示客户端第一次发送请求没有缓存为空并且没有条件请求服务器在收到客户端请求后设置验证器 Last-Modified 和 Etag 标签并把这两个标签随着响应一起发送回客户端。下一次客户端再发送相同的请求后会直接从缓存中提取只要缓存没有过期就不会有任何新的请求到达服务器重新下载资源。但是一旦缓存过期客户端不会直接使用缓存的值而是发出条件请求。验证器的值用作 If-Modified-Since 和If-Match标头的参数。缓存过期后客户端重新发起请求服务器收到请求后发现如果资源没有更改服务器会发回 304 Not Modified响应这使缓存再次刷新并让客户端使用缓存的资源。尽管有一个响应/请求往返消耗一些资源但是这比再次通过有线传输整个资源更有效。如果资源已经发生更改则服务器仅使用新版本的资源返回 200 OK 响应就像没有条件请求并且客户端会重新使用新的资源从这个角度来讲缓存是条件请求的前置条件。断点续传HTTP 可以支持文件的部分下载通过保留已获得的信息此功能允许恢复先前的操作从而节省带宽和时间。支持断点续传的服务器通过发送 Accept-Ranges 标头广播此消息一旦发生这种情况客户端可以通过发送缺少范围的 Ranges标头来恢复下载这里你可能有疑问 Ranges 和 Content-Range是什么来解释一下RangeRange HTTP 请求标头指示服务器应返回文档指定部分的资源可以一次请求一个 Range 来返回多个部分服务器会将这些资源返回各个文档中。如果服务器成功返回那么将返回 206 响应如果 Range 范围无效服务器返回416 Range Not Satisfiable错误服务器还可以忽略 Range 标头并且返回 200 作为响应。Range: bytes200-1000, 2000-6576, 19000-还有一种表示是Range: bytes0-499, -500它们分别表示请求前500个字节和最后500个字节如果范围重叠则服务器可能会拒绝该请求。Content-RangeHTTP 的 Content-Range 响应标头是针对范围请求而设定的返回响应时使用首部字段 Content-Range能够告知客户端响应实体的哪部分是符合客户端请求的字段以字节为单位。它的一般表示如下Content-Range: bytes 200-1000/67589上段代码表示从所有 67589 个字节中返回 200-1000 个字节的内容那么上面的 Content-Range你也应该知道是什么意思了断点续传的原理比较简单但是这种方式存在潜在的问题如果在两次下载资源的期间进行了资源更新那么获得的范围将对应于资源的两个不同版本并且最终文档将被破坏。为了阻止这种情况的出现就会使用条件请求。对于范围来说有两种方法可以做到这一点。一种方法是使用 If-Modified-Since和If-Match如果前提条件失败服务器将返回错误然后客户端从头开始重新下载。即使此方法有效当文档资源发生改变时它也会添加额外的 响应/请求 交换。这会降低性能并且 HTTP 具有特定的标头来避免这种情况 If-Range。该解决方案效率更高但灵活性稍差一些因为在这种情况下只能使用一个 Etag。通过乐观锁避免丢失更新Web 应用程序中最普遍的操作是资源更新。这在任何文件系统或应用程序中都很常见但是任何允许存储远程资源的应用程序都需要这种机制。使用 put 方法你可以实现这一点客户端首先读取原始文件对其进行修改然后把它们发送到服务器。上面这种请求响应存在问题一旦考虑到并发性事情就会变得不准确。当客户端在本地修改资源打算重新发送之前第二个客户端可以获取相同的资源并对资源进行修改操作这样就会造成问题。当它们重新发送请求到服务器时第一个客户端所做的修改将被第二次客户端的修改所覆盖因为第二次客户端修改并不知道第一次客户端正在修改。资源提交并更新的一方不会传达给另外一方所以要保留哪个客户的更改将随着他们提交的速度而变化这取决于客户端服务器的性能甚至取决于人工在客户端编辑文档的性能。例如下面这个流程如果没有两个用户同时操作服务器也就不存在这个问题。但是现实情况是不可能只有单个用户出现的所以为了规避或者避免这个问题我们希望客户端资源在更新时进行提示或者修改被拒绝时收到通知。条件请求允许实现乐观锁算法。这个概念是允许所有的客户端获取资源的副本然后让他们在本地修改资源并成功通过允许第一个客户端提交更新来控制并发基于此服务端的后面版本的更新都将被拒绝。这是使用 If-Match 或 If-Unmodified-Since标头实现的。如果 Etag 与原始文件不匹配或者自获取以来已对文件进行了修改则更改为拒绝更新并显示412 Precondition Failed错误。HTTP CookiesHTTP 协议中的 Cookie 包括 Web Cookie 和浏览器 Cookie它是服务器发送到 Web 浏览器的一小块数据。服务器发送到浏览器的 Cookie浏览器会进行存储并与下一个请求一起发送到服务器。通常它用于判断两个请求是否来自于同一个浏览器例如用户保持登录状态。HTTP Cookie 机制是 HTTP 协议无状态的一种补充和改良Cookie 主要用于下面三个目的会话管理登陆、购物车、游戏得分或者服务器应该记住的其他内容个性化用户偏好、主题或者其他设置追踪记录和分析用户行为Cookie 曾经用于一般的客户端存储。虽然这是合法的因为它们是在客户端上存储数据的唯一方法但如今建议使用现代存储 API。Cookie 随每个请求一起发送因此它们可能会降低性能(尤其是对于移动数据连接而言)。客户端存储的现代 API 是 Web 存储 API(localStorage 和 sessionStorage)和 IndexedDB。创建 Cookie当接收到客户端发出的 HTTP 请求时服务器可以发送带有响应的 Set-Cookie 标头Cookie 通常由浏览器存储然后将 Cookie 与 HTTP 标头一同向服务器发出请求。可以指定到期日期或持续时间之后将不再发送Cookie。此外可以设置对特定域和路径的限制从而限制 cookie 的发送位置。Set-Cookie 和 Cookie 标头Set-Cookie HTTP 响应标头将 cookie 从服务器发送到用户代理。下面是一个发送 Cookie 的例子HTTP/2.0 200 OKContent-type: text/htmlSet-Cookie: yummy_cookiechocoSet-Cookie: tasty_cookiestrawberry[page content]此标头告诉客户端存储 Cookie现在随着对服务器的每个新请求浏览器将使用 Cookie 头将所有以前存储的 cookie 发送回服务器。GET /sample_page.html HTTP/2.0Host: www.example.orgCookie: yummy_cookiechoco; tasty_cookiestrawberryCookie 主要分为三类它们是 会话Cookie、永久Cookie 和 Cookie的 Secure 和 HttpOnly 标记下面依次来介绍一下会话 Cookies上面的示例创建的是会话 Cookie 会话 Cookie 有个特征客户端关闭时 Cookie 会删除因为它没有指定Expires 或 Max-Age 指令。这两个指令你看到这里应该比较熟悉了。但是Web 浏览器可能会使用会话还原这会使大多数会话 Cookie 保持永久状态就像从未关闭过浏览器一样永久性 Cookies永久性 Cookie 不会在客户端关闭时过期而是在特定日期(Expires)或特定时间长度(Max-Age)外过期。例如Set-Cookie: ida3fWa; ExpiresWed, 21 Oct 2015 07:28:00 GMT;Cookie的 Secure 和 HttpOnly 标记安全的 Cookie 需要经过 HTTPS 协议通过加密的方式发送到服务器。即使是安全的也不应该将敏感信息存储在cookie 中因为它们本质上是不安全的并且此标志不能提供真正的保护。HttpOnly 的作用会话 cookie 中缺少 HttpOnly 属性会导致攻击者可以通过程序(JS脚本、Applet等)获取到用户的 cookie 信息造成用户cookie 信息泄露增加攻击者的跨站脚本攻击威胁。HttpOnly 是微软对 cookie 做的扩展该值指定 cookie 是否可通过客户端脚本访问。如果在 Cookie 中没有设置 HttpOnly 属性为 true可能导致 Cookie 被窃取。窃取的 Cookie 可以包含标识站点用户的敏感信息如 ASP.NET 会话 ID 或 Forms 身份验证票证攻击者可以重播窃取的 Cookie以便伪装成用户或获取敏感信息进行跨站脚本攻击等。Cookie 的作用域Domain 和 Path 标识定义了 Cookie 的作用域即 Cookie 应该发送给哪些 URL。Domain 标识指定了哪些主机可以接受 Cookie。如果不指定默认为当前主机(不包含子域名)。如果指定了Domain则一般包含子域名。例如如果设置 Domainmozilla.org则 Cookie 也包含在子域名中(如developer.mozilla.org)。例如设置 Path/docs则以下地址都会匹配/docs/docs/Web//docs/Web/HTTP祝大家在2020年工作顺路家庭幸福合家团圆