目录
24.5.6 Request、Response 及 Body 混入
24.1 XMLHttpRequest 对象
XHR 对象的 API 被普遍认为比较难用,而 Fetch API 自从诞生以后就迅速成为了 XHR 更现代的替代 标准。Fetch API 支持期约(promise)和服务线程(service worker),已经成为极其强大的 Web 开发工具。
所有现代浏览器都通过 XMLHttpRequest 构造函数原生支持 XHR 对象:
let xhr = new XMLHttpRequest();
24.1.1 使用 XHR
使用 XHR 对象首先要调用 open()方法,这个方法接收 3 个参数:请求类型("get"、"post"等)、 请求 URL,以及表示请求是否异步的布尔值。下面是一个例子:
xhr.open("get", "example.php", false);
这 里的 URL 是相对于代码所在页面的,当然也可以使用绝对 URL。其次,调用 open()不会实际发送请 求,只是为发送请求做好准备。要发送定义好的请求,必须像下面这样调用 send()方法:
xhr.open("get", "example.txt", false);
xhr.send(null);
send()方法接收一个参数,是作为请求体发送的数据。如果不需要发送请求体,则必须传 null, 因为这个参数在某些浏览器中是必需的。调用 send()之后,请求就会发送到服务器。
收到响应后,XHR 对象的以下属性会被填充上数据:
responseText:作为响应体返回的文本。
responseXML:如果响应的内容类型是"text/xml"或"application/xml",那就是包含响应 数据的 XML DOM 文档。
status:响应的 HTTP 状态。
statusText:响应的 HTTP 状态描述。
收到响应后,第一步要检查 status 属性以确保响应成功返回。一般来说,HTTP 状态码为 2xx 表 示成功。此时,responseText 或 responseXML(如果内容类型正确)属性中会有内容。如果 HTTP 状态码是 304,则表示资源未修改过,是从浏览器缓存中直接拿取的。当然这也意味着响应有效。为确 保收到正确的响应,应该检查这些状态。
虽然可以像前面的例子一样发送同步请求,但多数情况下最好使用异步请求,这样可以不阻塞 JavaScript 代码继续执行。XHR 对象有一个 readyState 属性,表示当前处在请求/响应过程的哪个阶段。 这个属性有如下可能的值:
0:未初始化(Uninitialized)。尚未调用 open()方法。
1:已打开(Open)。已调用 open()方法,尚未调用 send()方法。
2:已发送(Sent)。已调用 send()方法,尚未收到响应。
3:接收中(Receiving)。已经收到部分响应。
4:完成(Complete)。已经收到所有响应,可以使用了。
每次 readyState 从一个值变成另一个值,都会触发 readystatechange 事件。可以借此机会检 查 readyState 的值。一般来说,我们唯一关心的 readyState 值是 4,表示数据已就绪。
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get", "example.txt", true);
xhr.send(null);
在收到响应之前如果想取消异步请求,可以调用 abort()方法:
xhr.abort();
调用这个方法后,XHR 对象会停止触发事件,并阻止访问这个对象上任何与响应相关的属性。中 断请求后,应该取消对 XHR 对象的引用。由于内存问题,不推荐重用 XHR 对象。
24.1.2 HTTP 头部
默认情况下,XHR 请求会发送以下头部字段:
Accept:浏览器可以处理的内容类型。
Accept-Charset:浏览器可以显示的字符集。
Accept-Encoding:浏览器可以处理的压缩编码类型。
Accept-Language:浏览器使用的语言。
Connection:浏览器与服务器的连接类型。
Cookie:页面中设置的 Cookie。
Host:发送请求的页面所在的域。
Referer:发送请求的页面的 URL。注意,这个字段在 HTTP 规范中就拼错了,所以考虑到兼容 性也必须将错就错。(正确的拼写应该是 Referrer。
User-Agent:浏览器的用户代理字符串
虽然不同浏览器发送的确切头部字段可能各不相同,但这些通常都是会发送的。如果需要发送额外 的请求头部,可以使用 setRequestHeader()方法。这个方法接收两个参数:头部字段的名称和值。 为保证请求头部被发送,必须在 open()之后、send()之前调用 setRequestHeader():
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get", "example.php", true);
xhr.setRequestHeader("MyHeader", "MyValue");
xhr.send(null);
有些浏览器允许重写默认头部,有些浏览器则不允许。
可以使用 getResponseHeader()方法从 XHR 对象获取响应头部,只要传入要获取头部的名称即 可。如果想取得所有响应头部,可以使用 getAllResponseHeaders()方法,这个方法会返回包含所 有响应头部的字符串。
let myHeader = xhr.getResponseHeader("MyHeader");
let allHeaders xhr.getAllResponseHeaders();
24.1.3 GET 请求
最常用的请求方法是 GET 请求,用于向服务器查询某些信息。必要时,需要在 GET 请求的 URL 后面添加查询字符串参数。对 XHR 而言,查询字符串必须正确编码后添加到 URL 后面,然后再传给 open()方法。查询字符串中的每个名和值都必须使用 encodeURIComponent()编码,所有名/值对必须以和号(&)分隔:
xhr.open("get", "example.php?name1=value1&name2=value2", true);
这里定义了一个 addURLParam()函数,它接收 3 个参数:要添加查询字符串的 URL、查询参数和 参数值。可以使用这个函数构建请求 URL:
24.1.4 POST 请求
第二个最常用的请求是 POST 请求,用于向服务器发送应该保存的数据。每个 POST 请求都应该在 请求体中携带提交的数据,而 GET 请求则不然。POST 请求的请求体可以包含非常多的数据,而且数据 可以是任意格式。
要初始化 POST 请求,open()方法的第一个参数要传"post":
xhr.open("post", "example.php", true);
接下来就是要给 send()方法传入要发送的数据。因为 XHR 最初主要设计用于发送 XML,所以可 以传入序列化之后的 XML DOM 文档作为请求体。当然,也可以传入任意字符串.
xhr.open("post", "postexample.php", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
let form = document.getElementById("user-info");
xhr.send(serialize(form));
24.1.5 XMLHttpRequest Level 2
略
24.2 进度事件
Progress Events 是 W3C 的工作草案,定义了客户端服务器端通信。这些事件最初只针对 XHR,现 在也推广到了其他类似的 API。有以下 6 个进度相关的事件
loadstart:在接收到响应的第一个字节时触发。
progress:在接收响应期间反复触发。
error:在请求出错时触发。
abort:在调用 abort()终止连接时触发。
load:在成功接收完响应时触发。
loadend:在通信完成时,且在 error、abort 或 load 之后触发。
每次请求都会首先触发 loadstart 事件,之后是一个或多个 progress 事件,接着是 error、abort 或 load 中的一个,最后以 loadend 事件结束
24.2.1 load 事件
增加了一个 load 事件用于替代 readystatechange 事件。load 事件在响应接收完成后立即触发,这样就不用检查 readyState 属性 了。onload 事件处理程序会收到一个 event 对象,其 target 属性设置为 XHR 实例,在这个实例上 可以访问所有 XHR 对象属性和方法。
let xhr = new XMLHttpRequest();
xhr.onload = function() {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
24.2.2 progress 事件
Mozilla 在 XHR 对象上另一个创新是 progress 事件,在浏览器接收数据期间,这个事件会反复触 发。每次触发时,onprogress 事件处理程序都会收到 event 对象,其 target 属性是 XHR 对象,且 包含 3 个额外属性:lengthComputable、position 和 totalSize。其中,lengthComputable 是 一个布尔值,表示进度信息是否可用;position 是接收到的字节数;totalSize 是响应的 ContentLength 头部定义的总字节数。有了这些信息,就可以给用户提供进度条了。
xhr.onprogress = function(event) {
let divStatus = document.getElementById("status");
if (event.lengthComputable) {
divStatus.innerHTML = "Received " + event.position + " of " +
event.totalSize +
" bytes";
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
24.3 跨源资源共享
通过 XHR 进行 Ajax 通信的一个主要限制是跨源安全策略。默认情况下,XHR 只能访问与发起请 求的页面在同一个域内的资源。这个安全限制可以防止某些恶意行为。不过,浏览器也需要支持合法跨 源访问的能力。
跨源资源共享(CORS,Cross-Origin Resource Sharing)定义了浏览器与服务器如何实现跨源通信。 CORS 背后的基本思路就是使用自定义的 HTTP 头部允许浏览器和服务器相互了解,以确实请求或响应 应该成功还是失败。
请求在发送时会有一个额外的头部叫 Origin。Origin 头部包含发送请求的页面的源(协议、 域名和端口),以便服务器确定是否为其提供响应。下面是 Origin 头部的一个示例:
Origin: http://www.nczonline.net
如果服务器决定响应请求,那么应该发送 Access-Control-Allow-Origin 头部,包含相同的源; 或者如果资源是公开的,那么就包含"*"。比如
Access-Control-Allow-Origin: http://www.nczonline.net
如果没有这个头部,或者有但源不匹配,则表明不会响应浏览器请求。否则,服务器就会处理这个 请求。注意,无论请求还是响应都不会包含 cookie 信息。
跨域 XHR 对象允许访问 status 和 statusText 属性,也允许同步请求。出于安全考虑,跨域 XHR 对象也施加了一些额外限制:
不能使用 setRequestHeader()设置自定义头部。
不能发送和接收 cookie。
getAllResponseHeaders()方法始终返回空字符串
24.3.1 预检请求
CORS 通过一种叫预检请求(preflighted request)的服务器验证机制,允许使用自定义头部、除 GET 和 POST 之外的方法,以及不同请求体内容类型。在要发送涉及上述某种高级选项的请求时,会先向服 务器发送一个“预检”请求。这个请求使用 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: http://www.nczonline.net
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000
Access-Control-Allow-Origin:与简单请求相同。
Access-Control-Allow-Methods:允许的方法(逗号分隔的列表)。
Access-Control-Allow-Headers:服务器允许的头部(逗号分隔的列表)。
Access-Control-Max-Age:缓存预检请求的秒数
预检请求返回后,结果会按响应指定的时间缓存一段时间。换句话说,只有第一次发送这种类型的 请求时才会多发送一次额外的 HTTP 请求。
24.3.2 凭据请求
默认情况下,跨源请求不提供凭据(cookie、HTTP 认证和客户端 SSL 证书)。可以通过将 withCredentials 属性设置为 true 来表明请求会发送凭据。如果服务器允许带凭据的请求,那么可 以在响应中包含如下 HTTP 头部: Access-Control-Allow-Credentials: true
24.4 替代性跨源技术
24.4.1 图片探测
图片探测是利用标签实现跨域通信的最早的一种技术。任何页面都可以跨域加载图片而不 必担心限制,因此这也是在线广告跟踪的主要方式。
图片探测是与服务器之间简单、跨域、 单向的通信。数据通过查询字符串发送,响应可以随意设置,不过一般是位图图片或值为 204 的状态码。 浏览器通过图片探测拿不到任何数据,但可以通过监听 onload 和 onerror 事件知道什么时候能接收 到响应。
let img = new Image();
img.onload = img.onerror = function() {
alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";
图片探测频繁用于跟踪用户在页面上的点击操作或动态显示广告。当然,图片探测的缺点是只能发 送 GET 请求和无法获取服务器响应的内容。这也是只能利用图片探测实现浏览器与服务器单向通信的 原因。
24.4.2 JSONP
JSONP 格式包含两个部分:回调和数据。回调是在页面接收到响应之后应该调用的函数,通常回调 函数的名称是通过请求来动态指定的。而数据就是作为参数传给回调函数的 JSON 数据。下面是一个典 型的 JSONP 请求:
function handleResponse(response) {
console.log(`
You're at IP address ${response.ip}, which is in
${response.city}, ${response.region_name}`);
}
let script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
JSONP 调用是通过动态创建<script>,元素并为 src 属性指定跨域 URL 实现的。此时的<script>与元素类似,能够不受限制地从其他域加载资源。因为 JSONP 是有效的 JavaScript,所以 JSONP 响应在被加载完成之后会立即执行。
相比于图片探测,使用 JSONP 可以直接访问响应, 实现浏览器与服务器的双向通信。
JSONP的缺点:
首先,JSONP 是从不同的域拉取可执行代码。如果这个域并不可信,则可能在响应中加入恶意内容。 此时除了完全删除 JSONP 没有其他办法。在使用不受控的 Web 服务时,一定要保证是可以信任的。 第二个缺点是不好确定 JSONP 请求是否失败。为此,开发者经常使用计时器来决定是否放弃等待响应。这种 方式并不准确,毕竟不同用户的网络连接速度和带宽是不一样的。
24.5 Fetch API
Fetch API 能够执行 XMLHttpRequest 对象的所有任务,但更容易使用,接口也更现代化,能够在 Web 工作线程等现代 Web 工具中使用。XMLHttpRequest 可以选择异步,而 Fetch API 则必须是异步。
同时这个 API 也能够应用在服务线程 (service worker)中,提供拦截、重定向和修改通过 fetch()生成的请求接口。
24.5.1 基本用法
1. 分派请求
fetch()只有一个必需的参数 input。多数情况下,这个参数是要获取资源的 URL。这个方法返回 一个期约:
let r = fetch('/bar');
console.log(r); // Promise <pending>
2. 读取响应
读取响应内容的最简单方式是取得纯文本格式的内容,这要用到 text()方法。这个方法返回一个 期约,会解决为取得资源的完整内容:
fetch('bar/text')
.then((response) => { response.text().then((data) => {console.log(data)}) })
结构打平:
fetch('bar/text')
.then((response) => response.text())
.then((data) => console.log(data))
3. 处理状态码和请求失败
Fetch API 支持通过 Response 的 status(状态码)和 statusText(状态文本)属性检查响应状 态。成功获取响应的请求通常会产生值为 200 的状态码,如下所示:
fetch('./bar')
.then((response) => {
console.log(response.status);
// 请求成功 会返回 200
// 请求不存在的资源会返回 404
// 请求的 URL 如果抛出服务器错误会返回 500
console.log(response.statusText);
// OK
// Not Found
// Internal Server Error
}
在前面这几个例子中,虽然请求可能失败(如状态码为 500),但都只执行了期约的解决处理函数。 事实上,只要服务器返回了响应,fetch()期约都会解决。这个行为是合理的:系统级网络协议已经成 功完成消息的一次往返传输。至于真正的“成功”请求,则需要在处理响应时再定义。
因为服务器没有响应而导致浏览器超时,这样真正的 fetch()失败会导致期约被拒绝。 违反 CORS、无网络连接、HTTPS 错配及其他浏览器/网络策略问题都会导致期约被拒绝。
24.5.2 常见 Fetch 请求模式
1. 发送 JSON 数据
let payload = JSON.stringify({
foo: 'bar'
});
let jsonHeaders = new Headers({
'Content-Type': 'application/json'
});
fetch('/send-me-json', {
method: 'POST', // 发送请求体时必须使用一种 HTTP 方法
body: payload,
headers: jsonHeaders
});
2. 在请求体中发送参数
因为请求体支持任意字符串值,所以可以通过它发送请求参数:
let payload = 'foo=bar&baz=qux';
let paramHeaders = new Headers({
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
});
fetch('/send-me-params', {
method: 'POST', // 发送请求体时必须使用一种 HTTP 方法
body: payload,
headers: paramHeaders
});
3. 发送跨源请求
从不同的源请求资源,响应要包含 CORS 头部才能保证浏览器收到响应。没有这些头部,跨源请求 会失败并抛出错误。
24.5.3 Headers 对象
Headers 对象是所有外发请求和入站响应头部的容器。每个外发的 Request 实例都包含一个空的 Headers 实例,可以通过 Request.prototype.headers 访问,每个入站 Response 实例也可以通过 Response.prototype.headers 访问包含着响应头部的 Headers 对象。这两个属性都是可修改属性。 另外,使用 new Headers()也可以创建一个新实例。
1. Headers 与 Map 的相似之处
Headers 对象与 Map 对象极为相似。这是合理的,因为 HTTP 头部本质上是序列化后的键/值对, 它们的 JavaScript 表示则是中间接口。Headers 与 Map 类型都有 get()、set()、has()和 delete() 等实例方法。
2. Headers 独有的特性
Headers 并不是与 Map 处处都一样。在初始化 Headers 对象时,也可以使用键/值对形式的对象, 而 Map 则不可以:
let seed = {foo: 'bar'};
let h = new Headers(seed);
console.log(h.get('foo')); // bar
let m = new Map(seed);
// TypeError: object is not iterable
一个 HTTP 头部字段可以有多个值,而 Headers 对象通过 append()方法支持添加多个值。在 Headers 实例中还不存在的头部上调用 append()方法相当于调用 set()。后续调用会以逗号为分隔符 拼接多个值:
let h = new Headers();
h.append('foo', 'bar');
console.log(h.get('foo'));// "bar"
h.append('foo','bar');
console.log(h.get('foo'));// "bar","bar"
3. 头部护卫
某些情况下,并非所有 HTTP 头部都可以被客户端修改,而 Headers 对象使用护卫来防止不被允 许的修改。不同的护卫设置会改变 set()、append()和 delete()的行为。违反护卫限制会抛出 TypeError。
24.5.4 Request 对象
1. 创建 Request 对象
可以通过构造函数初始化 Request 对象。为此需要传入一个 input 参数,一般是 URL:
let r = new Request('https://foo.com');
console.log(r);
// Request {...}
Request 构造函数也接收第二个参数——一个 init 对象。这个 init 对象与前面介绍的 fetch() 的 init 对象一样。没有在 init 对象中涉及的值则会使用默认值:
// 用所有默认值创建 Request 对象
console.log(new Request(''));
// Request {
// bodyUsed: false
// cache: "default"
// credentials: "same-origin"
// destination: ""
// headers: Headers {}
// integrity: ""
// keepalive: false
// method: "GET"
// mode: "cors"
// redirect: "follow"
// referrer: "about:client"
// referrerPolicy: ""
// signal: AbortSignal {aborted: false, onabort: null}
// url: "<current URL>"
// }
2. 克隆 Request 对象
Fetch API 提供了两种不太一样的方式用于创建 Request 对象的副本:使用 Request 构造函数和使 用 clone()方法。
将 Request 实例作为 input 参数传给 Request 构造函数,会得到该请求的一个副本:
let r1 = new Requset('https://foo.com');
let r2 = new Request(r1);
console.log(r2.url); // https://foo.com/
这种克隆方式并不总能得到一模一样的副本。最明显的是,第一个请求的请求体会被标记为“已使用”:
let r1 = new Request('https://foo.com',
{ method: 'POST', body: 'foobar' });
let r2 = new Request(r1);
console.log(r1.bodyUsed); // true
console.log(r2.bodyUsed); // false
第二种克隆 Request 对象的方式是使用 clone()方法,这个方法会创建一模一样的副本,任何值 都不会被覆盖。与第一种方式不同,这种方法不会将任何请求的请求体标记为“已使用”:
let r1 = new Request('https://foo.com', { method: 'POST', body: 'foobar' });
let r2 = r1.clone();
console.log(r1.url); // https://foo.com/
console.log(r2.url); // https://foo.com/
console.log(r1.bodyUsed); // false
console.log(r2.bodyUsed); // false
如果请求对象的 bodyUsed 属性为 true(即请求体已被读取),那么上述任何一种方式都不能用来 创建这个对象的副本。在请求体被读取之后再克隆会导致抛出 TypeError。
3. 在 fetch()中使用 Request 对象
用 fetch()时,可以传入已 经创建好的 Request 实例而不是 URL。与 Request 构造函数一样,传给 fetch()的 init 对象会覆 盖传入请求对象的值。
fetch()会在内部克隆传入的 Request 对象。与克隆 Request 一样,fetch()也不能拿请求体已 经用过的 Request 对象来发送请求:
let r = new Request('https://foo.com',
{ method: 'POST', body: 'foobar' });
r.text();
fetch(r);
// TypeError: Cannot construct a Request with a Request object that has already
been used.
要想基于包含请求体的相同 Request 对象多次调用 fetch(),必须在第一次发送 fetch()请求前 调用 clone():
let r = new Request('https://foo.com',
{ method: 'POST', body: 'foobar' });
// 3 个都会成功
fetch(r.clone());
fetch(r.clone());
fetch(r);
24.5.5 Response 对象
1. 创建 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 对象的主要方式是调用 fetch(),它返回一个最后会解决为 Response 对象的期约,这个 Response 对象代表实际的 HTTP 响应。下面的代码展示了这样得到的 Response 对象:
fetch('https://foo.com')
.then((response) => {
console.log(response);
});
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: true
// redirected: false
// status: 200
// statusText: "OK"
// type: "basic"
// url: "https://foo.com/"
// }
Response 类还有两个用于生成 Response 对象的静态方法:Response.redirect()和 Response. error()。Response.redirect()接收一个 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.error()用于产生表示网络错误的 Response 对象(网络错误会导致 fetch()期约被拒绝)。
2. 读取响应状态信息
以下代码演示了返回 200、302、404 和 500 状态码的 URL 对应的响应:
fetch('//foo.com').then(console.log);
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: true
// redirected: false
// status: 200
// statusText: "OK"
// type: "basic"
// url: "https://foo.com/"
// }
fetch('//foo.com/redirect-me').then(console.log);
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: true
// redirected: true
// status: 200
// statusText: "OK"
// type: "basic"
// url: "https://foo.com/redirected-url/"
// }
fetch('//foo.com/does-not-exist').then(console.log);
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: false
// redirected: true
// status: 404
// statusText: "Not Found"
// type: "basic"
// url: "https://foo.com/does-not-exist/"
// }
fetch('//foo.com/throws-error').then(console.log);
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: false
// redirected: true
// status: 500
// statusText: "Internal Server Error"
// type: "basic"
// url: "https://foo.com/throws-error/"
// }
3. 克隆 Response 对象
克隆 Response 对象的主要方式是使用 clone()方法,这个方法会创建一个一模一样的副本,不 会覆盖任何值。这样不会将任何请求的请求体标记为已使用:
let r1 = new Response('foobar');
let r2 = r1.clone();
console.log(r1.bodyUsed); // false
console.log(r2.bodyUsed); // false
如果响应对象的 bodyUsed 属性为 true(即响应体已被读取),则不能再创建这个对象的副本。在 响应体被读取之后再克隆会导致抛出 TypeError。
有响应体的 Response 对象只能读取一次。(不包含响应体的 Response 对象不受此限制。)要多次读取包含响应体的同一个 Response 对象,必须在第一次读取前调用 clone():
let r = new Response('foobar');
r.clone().text().then(console.log); // foobar
r.clone().text().then(console.log); // foobar
r.text().then(console.log); // foobar
24.5.6 Request、Response 及 Body 混入
略
24.6 Beacon API
略
24.7 Web Socket
Web Socket(套接字)的目标是通过一个长时连接实现与服务器全双工、双向的通信。在 JavaScript 中创建 Web Socket 时,一个 HTTP 请求会发送到服务器以初始化连接。服务器响应后,连接使用 HTTP 的 Upgrade 头部从 HTTP 协议切换到 Web Socket 协议。这意味着 Web Socket 不能通过标准 HTTP 服务 器实现,而必须使用支持该协议的专有
因为 Web Socket使用了自定义协议,所以 URL方案(scheme)稍有变化:不能再使用 http://或 https://, 而要使用 ws://和 wss://。
使用自定义协议而非 HTTP 协议的好处是,客户端与服务器之间可以发送非常少的数据,不会对 HTTP 造成任何负担。使用更小的数据包让 Web Socket 非常适合带宽和延迟问题比较明显的移动应用。 使用自定义协议的缺点是,定义协议的时间比定义 JavaScript API 要长。Web Socket 得到了所有主流浏 览器支持。
24.7.1 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):连接已经关闭。
任何时候都可以调用 close()方法关闭 Web Socket 连接: socket.close();
24.7.2 发送和接收数据
打开 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"。
24.7.3 其他事件
WebSocket 对象在连接生命周期中有可能触发 3 个其他事件。
open:在连接成功建立时触发。
error:在发生错误时触发。连接无法存续。
close:在连接关闭时触发。
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.");
};
24.8 安全
略
24.9 小结
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,而 使用了自定义协议,目的是更快地发送小数据块。这需要专用的服务器,但速度优势明显。