网络请求与远程资源
XMLHttpRequest对象
使用 XHR 对象首先要调用 open()方法,这个方法接收 3 个参数:请求类型("get"、"post"等)、 请求 URL,以及表示请求是否异步的布尔值。 send()方法接收一个参数,是作为请求体发送的数据。如果不需要发送请求体,则必须传 null, 因为这个参数在某些浏览器中是必需的。调用 send()之后,请求就会发送到服务器。 因为这个请求是同步的,所以 JavaScript 代码会等待服务器响应之后再继续执行。收到响应后,XHR 对象的以下属性会被填充上数据。
responseText:作为响应体返回的文本。
responseXML:如果响应的内容类型是"text/xml"或"application/xml",那就是包含响应 数据的 XML DOM 文档。
status:响应的 HTTP 状态。
statusText:响应的 HTTP 状态描述。 收到响应后,第一步要检查 status 属性以确保响应成功返回。一般来说,HTTP 状态码为 2xx 表 示成功。此时,responseText 或 responseXML(如果内容类型正确)属性中会有内容。如果 HTTP 状态码是 304,则表示资源未修改过,是从浏览器缓存中直接拿取的。当然这也意味着响应有效。为确 保收到正确的响应,应该检查这些状态,如下所示:
XHR 对象有一个 readyState 属性,表示当前处在请求/响应过程的哪个阶段。 这个属性有如下可能的值。
0:未初始化(Uninitialized)。尚未调用 open()方法。
1:已打开(Open)。已调用 open()方法,尚未调用 send()方法。
2:已发送(Sent)。已调用 send()方法,尚未收到响应。
3:接收中(Receiving)。已经收到部分响应。
4:完成(Complete)。已经收到所有响应,可以使用了。
这个例子演示了使用 timeout 设置超时。给 timeout 设置 1000 毫秒意味着,如果请求没有在 1 秒钟内返回则会中断。此时则会触发 ontimeout 事件处理程序,readyState 仍然会变成 4,因此也 会调用 onreadystatechange 事件处理程序。不过,如果在超时之后访问 status 属性则会发生错误。 为做好防护,可以把检查 status 属性的代码封装在 try/catch 语句中。
在收到响应之前如果想取消异步请求,可以调用 abort()方法:
调用这个方法后,XHR 对象会停止触发事件,并阻止访问这个对象上任何与响应相关的属性。中 断请求后,应该取消对 XHR 对象的引用。由于内存问题,不推荐重用 XHR 对象。
HTTP 头部
每个 HTTP 请求和响应都会携带一些头部字段,这些字段可能对开发者有用。XHR 对象会通过一 些方法暴露与请求和响应相关的头部字段。 默认情况下,XHR 请求会发送以下头部字段。 Accept:浏览器可以处理的内容类型。
Accept-Charset:浏览器可以显示的字符集。
Accept-Encoding:浏览器可以处理的压缩编码类型。
Accept-Language:浏览器使用的语言。
Connection:浏览器与服务器的连接类型。
Cookie:页面中设置的 Cookie。
Host:发送请求的页面所在的域。
Referer:发送请求的页面的 URI。注意,这个字段在 HTTP 规范中就拼错了,所以考虑到兼容 性也必须将错就错。(正确的拼写应该是 Referrer。) User-Agent:浏览器的用户代理字符串。 虽然不同浏览器发送的确切头部字段可能各不相同,但这些通常都是会发送的。如果需要发送额外 的请求头部,可以使用 setRequestHeader()方法。这个方法接收两个参数:头部字段的名称和值。 为保证请求头部被发送,必须在 open()之后、send()之前调用 setRequestHeader(),如下面的例 子所示:
服务器通过读取自定义头部可以确定适当的操作。自定义头部一定要区别于浏览器正常发送的头部, 否则可能影响服务器正常响应。有些浏览器允许重写默认头部,有些浏览器则不允许。 可以使用 getResponseHeader()方法从 XHR 对象获取响应头部,只要传入要获取头部的名称即 可。如果想取得所有响应头部,可以使用 getAllResponseHeaders()方法,这个方法会返回包含所 有响应头部的字符串。下面是调用这两个方法的例子:
服务器可以使用头部向浏览器传递额外的结构化数据。getAllResponseHeaders()方法通常返回 类似如下的字符串: Date: Sun, 14 Nov 2004 18:04:03 GMT Server: Apache/1.3.29 (Unix) Vary: Accept X-Powered-By: PHP/4.3.8 Connection: close Content-Type: text/html; charset=iso-8859-1
XMLHttpRequest Level 2
FormData 类型 现代 Web 应用程序中经常需要对表单数据进行序列化,因此 XMLHttpRequest Level 2 新增了 FormData 类型。FormData 类型便于表单序列化,也便于创建与表单类似格式的数据然后通过 XHR 发送。下面的代码创建了一个 FormData 对象,并填充了一些数据:
append()方法接收两个参数:键和值,相当于表单字段名称和该字段的值。可以像这样添加任意 多个键/值对数据。此外,通过直接给 FormData 构造函数传入一个表单元素,也可以将表单中的数据 作为键/值对填充进去:
有了 FormData 实例,可以像下面这样直接传给 XHR 对象的 send()方法:
使用 FormData 的另一个方便之处是不再需要给 XHR 对象显式设置任何请求头部了。XHR 对象能 够识别作为 FormData 实例传入的数据类型并自动配置相应的头部(xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"))。
进度事件
Progress Events 是 W3C 的工作草案,定义了客户端服务器端通信。这些事件最初只针对 XHR,现 在也推广到了其他类似的 API。有以下 6 个进度相关的事件。
loadstart:在接收到响应的第一个字节时触发。 progress:在接收响应期间反复触发。
error:在请求出错时触发。
abort:在调用 abort()终止连接时触发。
load:在成功接收完响应时触发。
loadend:在通信完成时,且在 error、abort 或 load 之后触发。 每次请求都会首先触发 loadstart 事件,之后是一个或多个 progress 事件,接着是 error、abort 或 load 中的一个,最后以 loadend 事件结束。
load事件
Firefox 最初在实现 XHR 的时候,曾致力于简化交互模式。最终,增加了一个 load 事件用于替代 readystatechange 事件。load 事件在响应接收完成后立即触发,这样就不用检查 readyState 属性 了。onload 事件处理程序会收到一个 event 对象,其 target 属性设置为 XHR 实例,在这个实例上 可以访问所有 XHR 对象属性和方法。不过,并不是所有浏览器都实现了这个事件的 event 对象。考虑 到跨浏览器兼容,还是需要像下面这样使用 XHR 对象变量:
只要是从服务器收到响应,无论状态码是什么,都会触发 load 事件。这意味着还需要检查 status 属性才能确定数据是否有效。Firefox、Opera、Chrome 和 Safari 都支持 load 事件。
progress事件
Mozilla 在 XHR 对象上另一个创新是 progress 事件,在浏览器接收数据期间,这个事件会反复触 发。每次触发时,onprogress 事件处理程序都会收到 event 对象,其 target 属性是 XHR 对象,且 包含 3 个额外属性:lengthComputable、position 和 totalSize。其中,lengthComputable 是 一个布尔值,表示进度信息是否可用;position 是接收到的字节数;totalSize 是响应的 Content- Length 头部定义的总字节数。有了这些信息,就可以给用户提供进度条了。以下代码演示了如何向用 户展示进度:
为了保证正确执行,必须在调用 open()之前添加 onprogress 事件处理程序。在前面的例子中, 每次触发 progress 事件都会更新 HTML 元素中的信息。假设响应有 Content-Length 头部,就可以 利用这些信息计算出已经收到响应的百分比
跨源资源共享
跨域 XHR 对象允许访问 status 和 statusText 属性,也允许同步请求。出于安全考虑,跨域 XHR 对象也施加了一些额外限制。
不能使用 setRequestHeader()设置自定义头部。
不能发送和接收 cookie。
getAllResponseHeaders()方法始终返回空字符串。 因为无论同域还是跨域请求都使用同一个接口,所以最好在访问本地资源时使用相对 URL,在访问 远程资源时使用绝对 URL。这样可以更明确地区分使用场景,同时避免出现访问本地资源时出现头部或 cookie 信息访问受限的问题。
预检请求
如果请求满足以下所有条件,则为简单请求,无需进行预检请求:
请求方法为:HEAD、GET、POST;
HTTP 头信息不超出以下字段:Accept、Accept-Language、Content-Language、Content-Type(只限于 application/x-www-form-urlencoded、multipart/form-data、text/plain);
请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;
请求中没有使用 ReadableStream 对象。
使用 OPTIONS 方法发送并包含以下头部。
Origin:与简单请求相同。
Access-Control-Request-Method:请求希望使用的方法。
Access-Control-Request-Headers:(可选)要使用的逗号分隔的自定义头部列表。 下面是一个假设的 POST 请求,包含自定义的 NCZ 头部:
Origin: http://www.nczonline.net Access-Control-Request-Method: POST Access-Control-Request-Headers: NCZ
在这个请求发送后,服务器可以确定是否允许这种类型的请求。服务器会通过在响应中发送如下头
Access-Control-Allow-Origin:与简单请求相同。
Access-Control-Allow-Methods:允许的方法(逗号分隔的列表)。
Access-Control-Allow-Headers:服务器允许的头部(逗号分隔的列表)。
Access-Control-Max-Age:缓存预检请求的秒数。 例如: Access-Control-Allow-Origin: http://www.nczonline.net Access-Control-Allow-Methods: POST, GET Access-Control-Allow-Headers: NCZ Access-Control-Max-Age: 1728000
预检请求返回后,结果会按响应指定的时间缓存一段时间。换句话说,只有第一次发送这种类型的 请求时才会多发送一次额外的 HTTP 请求。
凭据请求
默认情况下,跨源请求不提供凭据(cookie、HTTP 认证和客户端 SSL 证书)。可以通过将 withCredentials 属性设置为 true 来表明请求会发送凭据。如果服务器允许带凭据的请求,那么可 以在响应中包含如下 HTTP 头部: Access-Control-Allow-Credentials: true 如果发送了凭据请求而服务器返回的响应中没有这个头部,则浏览器不会把响应交给 JavaScript (responseText 是空字符串,status 是 0,onerror()被调用)。注意,服务器也可以在预检请求的 响应中发送这个 HTTP 头部,以表明这个源允许发送凭据请求。
图片探测
图片探测是利用<img>
标签实现跨域通信的最早的一种技术。任何页面都可以跨域加载图片而不 必担心限制,因此这也是在线广告跟踪的主要方式。可以动态创建图片,然后通过它们的 onload 和 onerror 事件处理程序得知何时收到响应。 这种动态创建图片的技术经常用于图片探测(image pings)。图片探测是与服务器之间简单、跨域、 26 单向的通信。数据通过查询字符串发送,响应可以随意设置,不过一般是位图图片或值为 204 的状态码。 浏览器通过图片探测拿不到任何数据,但可以通过监听 onload 和 onerror 事件知道什么时候能接收 到响应。下面看一个例子:
这个例子创建了一个新的 Image 实例,然后为它的 onload 和 onerror 事件处理程序添加了同一 个函数。这样可以确保请求完成时无论什么响应都会收到通知。设置完 src 属性之后请求就开始了,这 个例子向服务器发送了一个 name 值。 图片探测频繁用于跟踪用户在页面上的点击操作或动态显示广告。当然,图片探测的缺点是只能发 送 GET 请求和无法获取服务器响应的内容。这也是只能利用图片探测实现浏览器与服务器单向通信的 原因。
Fetch API
Fetch API 能够执行 XMLHttpRequest 对象的所有任务,但更容易使用,接口也更现代化,能够在 Web 工作线程等现代 Web 工具中使用。XMLHttpRequest 可以选择异步,而 Fetch API 则必须是异步。
通常状态码为 200 时就会被认为成功了,其他情况可以被认为未成功。为区分这两种情况,可以在 状态码非 200~299 时检查 Response 对象的 ok 属性:
因为服务器没有响应而导致浏览器超时,这样真正的 fetch()失败会导致期约被拒绝:
违反 CORS、无网络连接、HTTPS 错配及其他浏览器/网络策略问题都会导致期约被拒绝。 可以通过 url 属性检查通过 fetch()发送请求时使用的完整 URL:
自定义选项
只使用 URL 时,fetch()会发送 GET 请求,只包含最低限度的请求头。要进一步配置如何发送请 求,需要传入可选的第二个参数 init 对象。init 对象要按照下表中的键/值进行填充。
body:
指定使用请求体时请求体的内容。
必须是 Blob、BufferSource、FormData、URLSearchParams、ReadableStream 或 String 的 实例
cache:用于控制浏览器与 HTTP 缓存的交互。要跟踪缓存的重定向,请求的 redirect 属性值必须是"follow", 而且必须符合同源策略限制。必须是下列值之一
Default
fetch()返回命中的有效缓存。不发送请求
命中无效(stale)缓存会发送条件式请求。如果响应已经改变,则更新缓存的值。然后 fetch() 返回缓存的值
未命中缓存会发送请求,并缓存响应。然后 fetch()返回响应
no-store
浏览器不检查缓存,直接发送请求
不缓存响应,直接通过 fetch()返回
reload
浏览器不检查缓存,直接发送请求
缓存响应,再通过 fetch()返回
no-cache
无论命中有效缓存还是无效缓存都会发送条件式请求。包含一个 If-None-Match 或 If-Modified-Since 的请求头
如果资源未发生变化,服务器会返回一个 304 Not Modified 的响应,浏览器会从缓存中获取资源。
如果资源已发生变化,服务器会返回一个 200 OK 的响应,包含新的资源。浏览器会更新缓存并使用新资源。
force-cache
浏览器会优先从缓存中获取资源,而不是向服务器发送请求。
即使缓存中的资源已过期,浏览器也会使用它。
只有在缓存中找不到匹配的资源时,浏览器才会发起网络请求。
only-if-cached
请求将只从缓存中获取数据,如果缓存中没有数据,则返回一个失败的 Promise。
如果缓存中有数据,则直接返回缓存的数据;否则,返回一个失败的 Promise。
未命中缓存返回状态码为 504(网关超时)的响应 默认为 default
credentials:用于指定在外发请求中如何包含 cookie。与 XMLHttpRequest 的 withCredentials 标签类似 必须是下列字符串值之一
omit:不发送 cookie
same-origin:只在请求 URL 与发送 fetch()请求的页面同源时发送 cookie
include:无论同源还是跨源都包含 cookie 在支持CredentialManagementAPI的浏览器中 ,也可以是一个PasswordCredential或的实例 默认为 same-origin
headers: 用于指定请求头部
必须是 Headers 对象实例或包含字符串格式键/值对的常规对象
默认值为不包含键/值对的 Headers 对象。这不意味着请求不包含任何头部,浏览器仍然会随请求 发送一些头部。虽然这些头部对 JavaScript 不可见,但浏览器的网络检查器可以观察到
integrity: 用于强制子资源完整性
必须是包含子资源完整性标识符的字符串 默认为空字符串
keepalive: 用于指示浏览器允许请求存在时间超出页面生命周期。适合报告事件或分析,比如页面在 fetch() 请求后很快卸载。设置 keepalive 标志的 fetch()请求可用于替代 Navigator.sendBeacon()
必须是布尔值 默认为 false
method: 用于指定 HTTP 请求方法,基本上就是如下字符串值:
GET
POST
PUT
PATCH
DELETE
HEAD
OPTIONS
CONNECT
TARCE 默认为 GET
mode: 用于指定请求模式。这个模式决定来自跨源请求的响应是否有效,以及客户端可以读取多少响应。 违反这里指定模式的请求会抛出错误,必须是下列字符串值之一
cors:允许遵守 CORS 协议的跨源请求。响应是“CORS 过滤的响应”,意思是响应中可以访问 的浏览器头部是经过浏览器强制白名单过滤的
no-cors:允许不需要发送预检请求的跨源请求(HEAD、GET 和只带有满足 CORS 请求头部的 POST)。响应类型是 opaque,意思是不能读取响应内容
same-origin:任何跨源请求都不允许发送
navigate:用于支持 HTML 导航,只在文档间导航时使用。基本用不到 在通过构造函数手动创建 Request 实例时,默认为 cors;否则,默认为 no-cors
redirect: 用于指定如何处理重定向响应(状态码为 301、302、303、307 或 308),必须是下列字符串值之一
follow:跟踪重定向请求,以最终非重定向 URL 的响应作为最终响应
error:重定向请求会抛出错误 20
manual:不跟踪重定向请求,而是返回 opaqueredirect 类型的响应,同时仍然暴露期望的重定向 URL。允许以手动方式跟踪重定向 默认为 follow
referrer: 用于指定HTTP的Referer头部的内容,必须是下列字符串值之一
no-referrer:以 no-referrer 作为值
client/about:client:以当前 URL 或 no-referrer(取决于来源策略 referrerPolicy)作 为值
:以伪造 URL 作为值。伪造 URL 的源必须与执行脚本的源匹配 默认为 client/about:client
referrerPolicy:用于指定HTTP的Referer头部,必须是下列字符串值之一
no-referrer
请求中不包含 Referer 头部
no-referrer-when-downgrade
对于从安全 HTTPS 上下文发送到 HTTP URL 的请求,不包含 Referer 头部 对于所有其他请求,将 Referer 设置为完整 URL
origin
对于所有请求,将 Referer 设置为只包含源
same-origin
对于跨源请求,不包含 Referer 头部
对于同源请求,将 Referer 设置为完整 URL
strict-origin
对于从安全 HTTPS 上下文发送到 HTTP URL 的请求,不包含 Referer 头部
对于所有其他请求,将 Referer 设置为只包含源
origin-when-cross-origin
对于跨源请求,将 Referer 设置为只包含源
对于同源请求,将 Referer 设置为完整 URL
strict-origin-when-cross-origin
对于从安全 HTTPS 上下文发送到 HTTP URL 的请求,不包含 Referer 头部
对于所有其他跨源请求,将 Referer 设置为只包含源
对于同源请求,将 Referer 设置为完整 URL
unsafe-url
对于所有请求,将 Referer 设置为完整 URL 默认为 no-referrer-when-downgrade
signal: 用于支持通过 AbortController 中断进行中的 fetch()请求,必须是 AbortSignal 的实例 默认为未关联控制器的 AbortSignal 实例
常见Fetch请求模式
发送 JSON 数据
可以像下面这样发送简单 JSON 字符串:
在请求体中发送参数
因为请求体支持任意字符串值,所以可以通过它发送请求参数:
发送文件
因为请求体支持 FormData 实现,所以 fetch()也可以序列化并发送文件字段中的文件:
加载 Blob 文件
Fetch API 也能提供 Blob 类型的响应,而 Blob 又可以兼容多种浏览器 API。一种常见的做法是明确将 图片文件加载到内存,然后将其添加到 HTML 图片元素。为此,可以使用响应对象上暴露的 blob()方法。 这个方法返回一个期约,解决为一个 Blob 的实例。然后,可以将这个实例传给 URL.createObjectUrl() 以生成可以添加给图片元素 src 属性的值:
发送跨源请求
从不同的源请求资源,响应要包含 CORS 头部才能保证浏览器收到响应。没有这些头部,跨源请求 会失败并抛出错误。
如果代码不需要访问响应,也可以发送 no-cors 请求。此时响应的 type 属性值为 opaque,因此无法读取响应内容。这种方式适合发送探测请求或者将响应缓存起来供以后使用。
中断请求
Fetch API 支持通过 AbortController/AbortSignal 对中断请求。调用 AbortController. abort()会中断所有网络传输,特别适合希望停止传输大型负载的情况。中断进行中的 fetch()请求会 导致包含错误的拒绝。
Headers对象
Headers 对象是所有外发请求和入站响应头部的容器。每个外发的 Request 实例都包含一个空的 Headers 实例,可以通过 Request.prototype.headers 访问,每个入站 Response 实例也可以通过 Response.prototype.headers 访问包含着响应头部的 Headers 对象。这两个属性都是可修改属性。 另外,使用 new Headers()也可以创建一个新实例。
Headers 与 Map 的相似之处
Headers 对象与 Map 对象极为相似。这是合理的,因为 HTTP 头部本质上是序列化后的键/值对, 它们的 JavaScript 表示则是中间接口。Headers 与 Map 类型都有 get()、set()、has()和 delete() 等实例方法,如下面的代码所示:
Headers 和 Map 都可以使用一个可迭代对象来初始化,比如:
而且,它们也都有相同的 keys()、values()和 entries()迭代器接口:
Headers 独有的特性
Headers 并不是与 Map 处处都一样。在初始化 Headers 对象时,也可以使用键/值对形式的对象,而 Map 则不可以:
一个 HTTP 头部字段可以有多个值,而 Headers 对象通过 append()方法支持添加多个值。在 Headers 实例中还不存在的头部上调用 append()方法相当于调用 set()。后续调用会以逗号为分隔符 拼接多个值:
头部护卫
某些情况下,并非所有 HTTP 头部都可以被客户端修改,而 Headers 对象使用护卫来防止不被允 许的修改。不同的护卫设置会改变 set()、append()和 delete()的行为。违反护卫限制会抛出 TypeError。 Headers 实例会因来源不同而展现不同的行为,它们的行为由护卫来控制。JavaScript 可以决定 Headers 实例的护卫设置。下表列出了不同的护卫设置和每种设置对应的行为。
选项 | 适用情形 | 限制 |
---|---|---|
| 在通过构造函数创建 Headers 实例时激活 | 无 |
| 在通过构造函数初始化Request对象,且mode值为非 no-cors 时激活 | 不允许修改禁止修改的头部(参见 MDN 文档中的 |
| 在通过构造函数初始化Request对象,且mode 不允许修改非简单头部(参见 MDN 文档中的 | 不允许修改非简单头部(参见 MDN 文档中的simple header 词条) |
response | 值为 no-cors 时激活在通过构造函数初始化 Response 对象时激活 | 不允许修改禁止修改的响应头部(参见 MDN 文 档中的 forbidden response header name 词条) |
| 在通过 error()或 redirect()静态方法初始 化 Response 对象时激活 | 不允许修改任何头部 |
Request对象
创建 Request 对象 可以通过构造函数初始化 Request 对象。为此需要传入一个 input 参数,一般是 URL:
在fetch()中使用Request对象fetch()和 Request 构造函数拥有相同的函数签名并不是巧合。在调用 fetch()时,可以传入已 经创建好的Request实例而不是URL。与Request构造函数一样,传给fetch()的 init 对象会覆 盖传入请求对象的值:
fetch()会在内部克隆传入的 Request 对象。与克隆 Request 一样,fetch()也不能拿请求体已 经用过的 Request 对象来发送请求:
关键在于,通过 fetch 使用 Request 会将请求体标记为已使用。也就是说,有请求体的 Request 只能在一次 fetch 中使用。(不包含请求体的请求不受此限制。)演示如下:
// TypeError: Cannot construct a Request with a Request object that has already been used. 要想基于包含请求体的相同 Request 对象多次调用 fetch(),必须在第一次发送 fetch()请求前 调用 clone():
Response对象
创建 Response 对象 可以通过构造函数初始化 Response 对象且不需要参数。此时响应实例的属性均为默认值,因为它并不代表实际的 HTTP 响应:
读取响应状态信息 Response 对象包含一组只读属性,描述了请求完成后的状态,如下表所示。 headers:响应包含的 Headers 对象 ok:布尔值,表示 HTTP 状态码的含义。200~299 的状态码返回 true,其他状态码返回 false redirected:布尔值,表示响应是否至少经过一次重定向 status:整数,表示响应的 HTTP 状态码 statusText:字符串,包含对 HTTP 状态码的正式描述。这个值派生自可选的 HTTP Reason-Phrase 字段,因此如果服 务器以 Reason-Phrase 为由拒绝响应,这个字段可能是空字符串 type:字符串,包含响应类型。可能是下列字符串值之一
basic:表示标准的同源响应
cors:表示标准的跨源响应
error:表示响应对象是通过 Response.error()创建的
opaque:表示 no-cors 的 fetch()返回的跨源响应
opaqueredirect:表示对 redirect 设置为 manual 的请求的响
url: 包含响应 URL 的字符串。对于重定向响应,这是最终的 URL,非重定向响应就是它产生的 以下代码演示了返回 200、302、404 和 500 状态码的 URL 对应的响应:
使用 ReadableStream 主体
ReadableStream 暴露了 getReader()方法,用于产生 ReadableStream- DefaultReader,这个读取器可以用于在数据到达时异步获取数据块。数据流的格式是 Uint8Array。
异步函数非常适合这样的 fetch()操作。可以通过使用 async/await 将上面的递归调用打平:
另外,read()方法也可以真接封装到 Iterable 接口中。因此就可以在 for-await-of 循环中方 便地实现这种转换:
通过将异步逻辑包装到一个生成器函数中,还可以进一步简化代码。而且,这个实现通过支持只读 取部分流也变得更稳健。如果流因为耗尽或错误而终止,读取器会释放锁,以允许不同的流读取器继续 操作:
Beacon API
为了把尽量多的页面信息传到服务器,很多分析工具需要在页面生命周期中尽量晚的时候向服务器 发送遥测或分析数据。因此,理想的情况下是通过浏览器的 unload 事件发送网络请求。这个事件表示 用户要离开当前页面,不会再生成别的有用信息了。 在 unload 事件触发时,分析工具要停止收集信息并把收集到的数据发给服务器。这时候有一个问题, 因为 unload 事件对浏览器意味着没有理由再发送任何结果未知的网络请求(因为页面都要被销毁了)。 例如,在 unload 事件处理程序中创建的任何异步请求都会被浏览器取消。为此,异步 XMLHttpRequest 或 fetch()不适合这个任务。分析工具可以使用同步 XMLHttpRequest 强制发送请求,但这样做会导 致用户体验问题。浏览器会因为要等待 unload 事件处理程序完成而延迟导航到下一个页面。 为解决这个问题,W3C 引入了补充性的 Beacon API。这个 API 给 navigator 对象增加了一个 sendBeacon()方法。这个简单的方法接收一个 URL 和一个数据有效载荷参数,并会发送一个 POST 请求。可选的数据有效载荷参数有 ArrayBufferView、Blob、DOMString、FormData 实例。如果请 求成功进入了最终要发送的任务队列,则这个方法返回 true,否则返回 false。 可以像下面这样使用这个方法:
这个方法虽然看起来只不过是 POST 请求的一个语法糖,但它有几个重要的特性。
sendBeacon()并不是只能在页面生命周期末尾使用,而是任何时候都可以使用。
调用 sendBeacon()后,浏览器会把请求添加到一个内部的请求队列。浏览器会主动地发送队 列中的请求。
浏览器保证在原始页面已经关闭的情况下也会发送请求。
状态码、超时和其他网络原因造成的失败完全是不透明的,不能通过编程方式处理。
信标(beacon)请求会携带调用 sendBeacon()时所有相关的 cookie。
Web Socket
Web Socket(套接字)的目标是通过一个长时连接实现与服务器全双工、双向的通信。在 JavaScript 中创建 Web Socket 时,一个 HTTP 请求会发送到服务器以初始化连接。服务器响应后,连接使用 HTTP 的 Upgrade 头部从 HTTP 协议切换到 Web Socket 协议。这意味着 Web Socket 不能通过标准 HTTP 服务 器实现,而必须使用支持该协议的专有服务器。
因为 Web Socket 使用了自定义协议,所以 URL 方案(scheme)稍有变化:不能再使用 http://或 https://, 而要使用 ws://和 wss://。者是不安全的连接,后者是安全连接。在指定 Web Socket URL 时,必须包 含 URL 方案,因为将来有可能再支持其他方案。
使用自定义协议而非 HTTP 协议的好处是,客户端与服务器之间可以发送非常少的数据,不会对 HTTP 造成任何负担。使用更小的数据包让 WebSocket 非常适合带宽和延迟问题比较明显的移动应用。 使用自定义协议的缺点是,定义协议的时间比定义 JavaScript API 要长。Web Socket 得了所有主流浏 览器支持。
API
要创建一个新的 Web Socket,就要实例化一个 WebSocket 对象并传入提供连接的 URL:
注意,必须给 WebSocket 构造函数传入一个绝对 URL。同源策略不适用于 Web Socket,因此可以 打开到任意站点的连接。至于是否与来自特定源的页面通信,则完全取决于服务器。(在握手阶段就可 以确定请求来自哪里。) 浏览器会在初始化 WebSocket 对象之后立即创建连接。与 XHR 类似,WebSocket 也有一个 readyState 属性表示当前状态。不过,这个值与 XHR 中相应的值不一样。
WebSocket.OPENING(0):连接正在建立。
WebSocket.OPEN(1):连接已经建立。
WebSocket.CLOSING(2):连接正在关闭。
WebSocket.CLOSE(3):连接已经关闭。 WebSocket 对象没有 readystatechange 事件,而是有与上述不同状态对应的其他事件。 readyState 值从 0 开始。 任何时候都可以调用 close()方法关闭 Web Socket 连接: socket.close(); 调用 close()之后,readyState 立即变为 2(连接正在关闭),并会在关闭后变为 3(连接已经关闭)。
发送和接收数据
打开 Web Socket 之后,可以通过连接发送和接收数据。要向服务器发送数据,使用 send()方法并 传入一个字符串、ArrayBuffer 或 Blob,如下所示:
服务器向客户端发送消息时,WebSocket 对象上会触发 message 事件。这个 message 事件与其 他消息协议类似,可以通过 event.data 属性访问到有效载荷:
与通过 send()方法发送的数据类似,event.data 返回的数据也可能是 ArrayBuffer 或 Blob。 这由 WebSocket 对象的 binaryType 属性决定,该属性可能是"blob"或"arraybuffer"。
其他事件
WebSocket 对象在连接生命周期中有可能触发 3 个其他事件。
open:在连接成功建立时触发。
error:在发生错误时触发。连接无法存续。
close:在连接关闭时触发。
WebSocket 对象不支持 DOM Level 2 事件监听器,因此需要使用 DOM Level 0 风格的事件处理程 序来监听这些事件:
在这些事件中,只有 close 事件的 event 对象上有额外信息。这个对象上有 3 个额外属性: wasClean、code 和 reason。其中,wasClean 是一个布尔值,表示连接是否干净地关闭;code 是一 个来自服务器的数值状态码;reason 是一个字符串,包含服务器发来的消息。可以将这些信息显示给 用户或记录到日志:
安全
探讨 Ajax 安全的文章已经有了很多,事实上也出版了很多专门讨论这个话题的书。大规模 Ajax 应 用程序需要考虑的安全问题非常多,但在通用层面上一般需要考虑以下几个问题。 首先,任何 Ajax 可以访问的 URL,也可以通过浏览器或服务器访问,例如下面这个 URL: /getuserinfo.php?id=23 请求这个 URL,可以假定返回 ID 为 23 的用户信息。访问者可以将 23 改为 24 或 56,甚至其他任何值。getuserinfo.php 文件必须知道访问者是否拥有访问相应数据的权限。否则,服务器就会大门敞开, 泄露所有用户的信息。 在未授权系统可以访问某个资源时,可以将其视为跨站点请求伪造(CSRF,cross-site request forgery) 25 攻击。未授权系统会按照处理请求的服务器的要求伪装自己。Ajax 应用程序,无论大小,都会受到 CSRF 攻击的影响,包括无害的漏洞验证攻击和恶意的数据盗窃或数据破坏攻击。 关于安全防护 Ajax 相关 URL 的一般理论认为,需要验证请求发送者拥有对资源的访问权限。可以 通过如下方式实现。
要求通过 SSL 访问能够被 Ajax 访问的资源(这是因为 SSL 协议可以提供数据加密和身份验证功能,可以确保请求是由合法的用户发出的,并且请求内容没有被篡改。这样,即使攻击者成功地构造了一个恶意请求,也无法执行该请求,因为 SSL 协议会阻止非法来源的请求,从而保护用户帐户的安全。)。
要求每个请求都发送一个按约定算法计算好的令牌(token)。 注意,以下手段对防护 CSRF 攻击是无效的。
要求 POST 而非 GET 请求(很容易修改请求方法)。
使用来源 URL 验证来源(来源 URL 很容易伪造)。
基于 cookie 验证(同样很容易伪造)。
Last updated