
2025-04-16 日报 Day158

今日的鸡汤
一旦时机到来,我们要能迅速地发现时机、把握时机,不犹豫,不踌躇,乘风而起,破万里浪。
今日学习内容
1、JS 红皮书 P735-750 第二十四章:网络请求与远程资源
今日笔记
1、Response 对象: Response 对象是获取资源响应的接口。这个接口暴露了响应的相关信息,也暴露了使用响应体的不同方式。
- 创建Response对象: 可以通过构造函数初始化 Response 对象且不需要参数。此时响应实例的属性均为默认值,因为它并不代表实际的 HTTP 响应:
let r = new Response();
console.log(r);
// Response {
// body: (…)
// bodyUsed: false
// headers: Headers {}
// ok: true
// redirected: false
// status: 200
// statusText: “OK”
// type: “default”
// url: “”
// }
Response 构造函数接收一个可选的 body 参数。这个 body 可以是 null,等同于 fetch()参数init 中的 body。还可以接收一个可选的 init 对象,这个对象可以包含下表所列的键和值。
可以像下面这样使用 body 和 init 来构建 Response 对象:
let r = new Response(‘foobar’, {
status: 418,
statusText: ‘I'm a teapot’
});
console.log(r);
// Response {
// body: (…)
// bodyUsed: false
// headers: Headers {}
// ok: false
// redirected: false
// status: 418
// statusText: “I’m a teapot”
// type: “default”
// url: “”
// }
Response 类还有两个用于生成 Response 对象的静态方法:Response.redirect()和 Response.error()。前者接收一个 URL 和一个重定向状态码(301、302、303、307 或 308),返回重定向的 Response对象:
console.log(Response.redirect(‘https://foo.com ‘, 301));
// Response {
// body: (…)
// bodyUsed: false
// headers: Headers {}
// ok: false
// redirected: false
// status: 301
// statusText: “”
// type: “default”
// url: “”
// }
提供的状态码必须对应重定向,否则会抛出错误:
Response.redirect(‘https://foo.com ‘, 200);
// RangeError: Failed to execute ‘redirect’ on ‘Response’: Invalid status code
另一个静态方法 Response.error()用于产生表示网络错误的 Response 对象(网络错误会导致fetch()期约被拒绝)。
console.log(Response.error());
// Response {
// body: (…)
// bodyUsed: false
// headers: Headers {}
// ok: false
// redirected: false
// status: 0
// statusText: “”
// type: “error”
// url: “”
// } - 读取响应状态信息: Response 对象包含一组只读属性,描述了请求完成后的状态,如下表所示。
- 克隆Response对象: 克隆 Response 对象的主要方式是使用 clone()方法,这个方法会创建一个一模一样的副本,不会覆盖任何值。
2、Request、Response及Body混入: Request 和 Response 都使用了 Fetch API 的 Body 混入,以实现两者承担有效载荷的能力。这个混入为两个类型提供了只读的 body 属性(实现为 ReadableStream)、只读的 bodyUsed 布尔值(表示 body 流是否已读)和一组方法,用于从流中读取内容并将结果转换为某种 JavaScript 对象类型。
通常,将 Request 和 Response 主体作为流来使用主要有两个原因。一个原因是有效载荷的大小可能会导致网络延迟,另一个原因是流 API 本身在处理有效载荷方面是有优势的。除此之外,最好是一次性获取资源主体。
3、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 请求
// URL: ‘https://example.com/analytics-reporting-url ‘
// 请求负载:’{foo: “bar”}’
navigator.sendBeacon(‘https://example.com/analytics-reporting-url ‘, ‘{foo: “bar”}’);
这个方法虽然看起来只不过是 POST 请求的一个语法糖,但它有几个重要的特性。
sendBeacon()并不是只能在页面生命周期末尾使用,而是任何时候都可以使用。
调用 sendBeacon()后,浏览器会把请求添加到一个内部的请求队列。浏览器会主动地发送队列中的请求。
浏览器保证在原始页面已经关闭的情况下也会发送请求。
状态码、超时和其他网络原因造成的失败完全是不透明的,不能通过编程方式处理。
信标(beacon)请求会携带调用 sendBeacon()时所有相关的 cookie。
4、WebSocket: 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 方案,因为将来有可能再支持其他方案。 - API: 要创建一个新的 Web Socket,就要实例化一个 WebSocket 对象并传入提供连接的 URL:
let socket = new WebSocket(“ws://www.example.com/server.php “);
注意,必须给 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,如下所示:
let socket = new WebSocket(“ws://www.example.com/server.php “);
let stringData = “Hello world!”;
let arrayBufferData = Uint8Array.from([‘f’, ‘o’, ‘o’]);
let blobData = new Blob([‘f’, ‘o’, ‘o’]);
socket.send(stringData);
socket.send(arrayBufferData.buffer);
socket.send(blobData);
服务器向客户端发送消息时,WebSocket 对象上会触发 message 事件。这个 message 事件与其他消息协议类似,可以通过 event.data 属性访问到有效载荷:
socket.onmessage = function(event) {
let data = event.data;
// 对数据执行某些操作
};
与通过 send()方法发送的数据类似,event.data 返回的数据也可能是 ArrayBuffer 或 Blob。这由 WebSocket 对象的 binaryType 属性决定,该属性可能是”blob”或”arraybuffer”。 - 其他事件: WebSocket 对象在连接生命周期中有可能触发 3 个其他事件。
open:在连接成功建立时触发。
error:在发生错误时触发。连接无法存续。
close:在连接关闭时触发。
WebSocket 对象不支持 DOM Level 2 事件监听器,因此需要使用 DOM Level 0 风格的事件处理程序来监听这些事件:
let socket = new WebSocket(“ws://www.example.com/server.php “);
socket.onopen = function() {
alert(“Connection established.”);
};
socket.onerror = function() {
alert(“Connection error.”);
};
socket.onclose = function() {
alert(“Connection closed.”);
};
在这些事件中,只有 close 事件的 event 对象上有额外信息。这个对象上有 3 个额外属性:
wasClean、code 和 reason。其中,wasClean 是一个布尔值,表示连接是否干净地关闭;code 是一个来自服务器的数值状态码;reason 是一个字符串,包含服务器发来的消息。可以将这些信息显示给用户或记录到日志:
socket.onclose = function(event) {
console.log(as clean? ${event.wasClean} Code=${event.code} Reason=${ event.reason}
);
};
5、安全: 探讨 Ajax 安全的文章已经有了很多,事实上也出版了很多专门讨论这个话题的书。大规模 Ajax 应用程序需要考虑的安全问题非常多,但在通用层面上一般需要考虑以下几个问题。
首先,任何 Ajax 可以访问的 URL,也可以通过浏览器或服务器访问,例如下面这个 URL:
/getuserinfo.php?id=23 请求这个 URL,可以假定返回 ID 为 23 的用户信息。访问者可以将 23 改为 24 或 56,甚至其他任何值。getuserinfo.php 文件必须知道访问者是否拥有访问相应数据的权限。否则,服务器就会大门敞开,泄露所有用户的信息。在未授权系统可以访问某个资源时,可以将其视为跨站点请求伪造(CSRF,cross-site request forgery)攻击。未授权系统会按照处理请求的服务器的要求伪装自己。Ajax 应用程序,无论大小,都会受到 CSRF攻击的影响,包括无害的漏洞验证攻击和恶意的数据盗窃或数据破坏攻击。
关于安全防护 Ajax 相关 URL 的一般理论认为,需要验证请求发送者拥有对资源的访问权限。可以通过如下方式实现。
要求通过 SSL 访问能够被 Ajax 访问的资源。
要求每个请求都发送一个按约定算法计算好的令牌(token)。
注意,以下手段对防护 CSRF 攻击是无效的。
要求 POST 而非 GET 请求(很容易修改请求方法)。
使用来源 URL 验证来源(来源 URL 很容易伪造)。
基于 cookie 验证(同样很容易伪造)
6、小结: Ajax 是无须刷新当前页面即可从服务器获取数据的一个方法,具有如下特点。
让 Ajax 迅速流行的中心对象是 XMLHttpRequest(XHR)。
这个对象最早由微软发明,并在 IE5 中作为通过 JavaScript 从服务器获取 XML 数据的一种手段。
之后,Firefox、Safari、Chrome 和 Opera 都复刻了相同的实现。W3C 随后将 XHR 行为写入 Web标准。
虽然不同浏览器的实现有些差异,但 XHR 对象的基本使用在所有浏览器中相对是规范的,因此可以放心地在 Web 应用程序中使用。
XHR 的一个主要限制是同源策略,即通信只能在相同域名、相同端口和相同协议的前提下完成。访问超出这些限制之外的资源会导致安全错误,除非使用了正式的跨域方案。这个方案叫作跨源资源共享(CORS,Cross-Origin Resource Sharing),XHR 对象原生支持 CORS。图片探测和 JSONP 是另外两种跨域通信技术,但没有 CORS 可靠。
Fetch API 是作为对 XHR 对象的一种端到端的替代方案而提出的。这个 API 提供了优秀的基于期约的结构、更直观的接口,以及对 Stream API 的最好支持。
Web Socket 是与服务器的全双工、双向通信渠道。与其他方案不同,Web Socket 不使用 HTTP,而使用了自定义协议,目的是更快地发送小数据块。这需要专用的服务器,但速度优势明显。