# 网络请求
2005 年,Jesse James Garrett 撰写了一篇文章,“Ajax—A New Approach to Web Applications”。这篇文章中描绘了一个被他称作 Ajax(Asynchronous JavaScript+XML,即异步 JavaScript 加 XML)的技术。这个技术涉及发送服务器请求额外数据而不刷新页面,从而实现更好的用户体验。
把 Ajax 推到历史舞台上的关键技术是 XMLHttpRequest(XHR)对象。这个对象最早由微软发明,然后被其他浏览器所借鉴。
XHR 为发送服务器请求和获取响应提供了合理的接口。这个接口可以实现异步从服务器获取额外数据,意味着用户点击不用页面刷新也可以获取数据。通过 XHR 对象获取数据后,可以使用 DOM 方法把数据插入网页。虽然 Ajax 这个名称中包含 XML,但实际上 Ajax 通信与数据格式无关。这个技术主要是可以实现在不刷新页面的情况下从服务器获取数据,格式并不一定是 XML。
XHR 对象的 API 被普遍认为比较难用,而 Fetch API 自从诞生以后就迅速成为了 XHR 更现代的替代标准。Fetch API 支持期约(promise)和服务线程(service worker),已经成为极其强大的 Web 开发工具。
# 1、XMLHttpRequest 对象
# 1.1 封装简易版 ajax
const ajax = {
createRequest(method, url, data, fn, contentType) {
const xhr = new XMLHttpRequest(); // 创建 XMLHttpRequest 实例
xhr.open(method, url, true);
contentType && xhr.setRequestHeader('Content-Type', contentType);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
fn(xhr.response);
}
};
xhr.send(data); // 发送请求
},
get(url, fn) {
ajax.createRequest('GET', url, null, fn);
},
post(url, data, fn) {
ajax.createRequest('POST', url, data, fn, 'application/json');
},
};
// 发送get
ajax.get('/api/test?a=1&b=2', res => {
console.log(res);
});
// 发送post
ajax.post('/api/user', JSON.stringify({ a: 1, b: 2 }), res => {
console.log(res);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 1.2 XHR
open(method, url, async) 方法,接收三个参数:method 请求类型("get"、"post"等)、请求 url 和是否异步。调用 open() 只会为发送请求准备。
send() 方法接收一个参数,是作为请求体发送的数据。如果不需要发送请求体,则必须传 null,因为这个参数在某些浏览器中是必需的。调用 send() 之后,请求就会发送到服务器。
收到响应后,XHR 对象的以下属性会被填充上数据:
- readyState 表示当前处在请求/响应过程的哪个阶段
- 0 未初始化(Uninitialized)。尚未调用 open() 方法。
- 1 已打开(Open)。已调用 open() 方法,尚未调用 send() 方法。
- 2 已发送(Sent)。已调用 send() 方法,尚未收到响应。
- 3 接收中(Receiving)。已经收到部分响应。
- 4 完成(Complete)。已经收到所有响应,可以使用了。
- responseText 作为响应体返回的文本。
- responseXML 如果响应的内容类型是"text/xml"或"application/xml",那就是包含响应数据的 XML DOM 文档。
- response 响应体
- status:响应的 HTTP 状态。
- statusText:响应的 HTTP 状态描述。
每次 readyState 从一个值变成另一个值,都会触发 readystatechange 事件。
在收到响应之前如果想取消异步请求,可以调用 abort() 方法:
xhr.abort();
# 1.2 HTTP 头部
默认情况下,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() 方法,这个方法会返回包含所有响应头部的字符串。
let myHeader = xhr.getResponseHeader('MyHeader');
let allHeaders = xhr.getAllResponseHeaders();
2
# 1.3 Content-Type
请求参数有 3 种格式:
(1)application/x-www-formurlencoded
const str = 'a=1&b=2';
const contentType = 'application/x-www-form-urlencoded';
ajax.createRequest(
'POST',
'/api/user',
str,
res => {
console.log(res);
},
contentType,
);
2
3
4
5
6
7
8
9
10
11
(2)application/json
const str = JSON.stringify({ a: 1, b: 2 });
const contentType = 'application/json';
ajax.createRequest(
'POST',
'/api/user',
str,
res => {
console.log(res);
},
contentType,
);
2
3
4
5
6
7
8
9
10
11
(3)multipart/form-data
const formData = new FormData();
formData.append('a', 1);
formData.append('b', 2);
ajax.createRequest('POST', '/api/user', formData, res => {
console.log(res);
});
2
3
4
5
6
XHR 对象能够识别作为 FormData 实例传入的数据类型并自动配置相应的头部。
# 1.4 超时处理
xhr.timeout = 1000; // 设置 1 秒超时
xhr.ontimeout = function () {
alert('Request did not return in a second.');
};
2
3
4
# 1.5 overrideMimeType()
overrideMimeType() 方法用于重写 XHR 响应的 MIME 类型。
let xhr = new XMLHttpRequest();
xhr.open('get', 'text.php', true);
xhr.overrideMimeType('text/xml'); // 在 send() 之前调用
xhr.send(null);
2
3
4
# 2、进度事件
有以下 6 个进度相关的事件。
- loadstart:在接收到响应的第一个字节时触发。
- progress:在接收响应期间反复触发。
- error:在请求出错时触发。
- abort:在调用 abort() 终止连接时触发。
- load:在成功接收完响应时触发。
- loadend:在通信完成时,且在 error、abort 或 load 之后触发。
# 2.1 load 事件
load 事件在响应接收完成后立即触发,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);
2
3
4
5
6
7
8
9
10
# 2.2 progress 事件
每次触发时,onprogress 事件处理程序都会收到 event 对象,其 target 属性是 XHR 对象,且包含 3 个额外属性 lengthComputable、position 和 totalSize。
- lengthComputable 是一个布尔值,表示进度信息是否可用;
- position 是接收到的字节数;
- totalSize 是响应的 ContentLength 头部定义的总字节数。
# 2.3 上传文件进度
const uploads = async (uploadUrl: string, file: File) => {
try {
return await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('put', uploadUrl);
xhr.setRequestHeader(
'Requesttoken',
localStorage.getItem(LOGIN_CONF.requesttoken),
);
xhr.upload.onprogress = event => {
if (event.lengthComputable) {
console.log('上传进度', event.loaded / event.total);
}
};
xhr.onerror = () => {
reject('上传失败!');
};
xhr.onabort = () => {
reject('已取消上传');
};
xhr.upload.onabort = () => {
reject('已取消上传');
};
xhr.onload = () => {
console.log('ok', xhr.status);
if (xhr.status === 200) {
resolve(true);
}
};
xhr.send(file);
});
} catch (e) {
return Promise.reject(e);
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
axios
export const uploadFile = (
pathName: string,
data: File,
onUploadProgress: any,
) => {
return axios.request({
method: 'post',
url: pathName,
onUploadProgress,
data,
});
};
2
3
4
5
6
7
8
9
10
11
12
# 3、跨源资源共享
默认情况下,XHR 只能访问与发起请求的页面在同一个域内的资源。
跨源资源共享(CORS,Cross-Origin Resource Sharing)定义了浏览器与服务器如何实现跨源通信。
CORS 背后的基本思路就是使用自定义的 HTTP 头部允许浏览器和服务器相互了解,以确实请求或响应应该成功还是失败。
对于简单的请求,比如 GET 或 POST 请求,没有自定义头部,而且请求体是 text/plain 类型,这样的请求在发送时会有一个额外的头部叫 Origin。Origin 头部包含发送请求的页面的源(协议、域名和端口),以便服务器确定是否为其提供响应。
Origin: http://www.nczonline.net
如果服务器决定响应请求,那么应该发送 Access-Control-Allow-Origin 头部,包含相同的源;或者如果资源是公开的,那么就包含"*"。比如:
Access-Control-Allow-Origin: http://www.nczonline.net
Access-Control-Allow-Origin: *
2
如果没有这个头部,或者有但源不匹配,则表明不会响应浏览器请求。否则,服务器就会处理这个请求。注意,无论请求还是响应都不会包含 cookie 信息。
现代浏览器通过 XMLHttpRequest 对象原生支持 CORS。在尝试访问不同源的资源时,这个行为会被自动触发。要向不同域的源发送请求,可以使用标准 XHR 对象并给 open() 方法传入一个绝对 URL
跨域 XHR 对象允许访问 status 和 statusText 属性,也允许同步请求。出于安全考虑,跨域 XHR 对象也施加了一些额外限制。
- 不能使用 setRequestHeader()设置自定义头部。
- 不能发送和接收 cookie。
- getAllResponseHeaders()方法始终返回空字符串。
# 3.1 预检请求
CORS 通过一种叫预检请求(preflighted request)的服务器验证机制,允许使用自定义头部、除 GET 和 POST 之外的方法,以及不同请求体内容类型。在要发送涉及上述某种高级选项的请求时,会先向服务器发送一个“预检”请求。这个请求使用 OPTIONS 方法发送并包含以下头部
- Origin:与简单请求相同。
- Access-Control-Request-Method:请求希望使用的方法。
- Access-Control-Request-Headers:(可选)要使用的逗号分隔的自定义头部列表。
Origin: http://www.nczonline.net
Access-Control-Request-Method: POST
Access-Control-Request-Headers: NCZ
2
3
在这个请求发送后,服务器可以确定是否允许这种类型的请求。服务器会通过在响应中发送如下头部与浏览器沟通这些信息。
- 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
2
3
4
预检请求返回后,结果会按响应指定的时间缓存一段时间。换句话说,只有第一次发送这种类型的请求时才会多发送一次额外的 HTTP 请求。
# 3.2 凭据请求
默认情况下,跨源请求不提供凭据(cookie、HTTP 认证和客户端 SSL 证书)。可以通过将 withCredentials 属性设置为 true 来表明请求会发送凭据。
如果服务器允许带凭据的请求,那么可以在响应中包含如下 HTTP 头部:
Access-Control-Allow-Credentials: true
如果发送了凭据请求而服务器返回的响应中没有这个头部,则浏览器不会把响应交给 JavaScript(responseText 是空字符串,status 是 0,onerror()被调用)。注意,服务器也可以在预检请求的响应中发送这个 HTTP 头部,以表明这个源允许发送凭据请求。
# 4、替代性跨源技术
CORS 出现之前,实现跨源 Ajax 通信是有点麻烦的。开发者需要依赖能够执行跨源请求的 DOM 特性,在不使用 XHR 对象情况下发送某种类型的请求。虽然 CORS 目前已经得到广泛支持,但这些技术仍然没有过时,因为它们不需要修改服务器。
# 4.1 图片探测
图片探测是利用<img>
标签实现跨域通信的最早的一种技术。任何页面都可以跨域加载图片而不必担心限制,因此这也是在线广告跟踪的主要方式。可以动态创建图片,然后通过它们的 onload 和 onerror 事件处理程序得知何时收到响应。
这种动态创建图片的技术经常用于图片探测(image pings)。图片探测是与服务器之间简单、跨域、单向的通信。数据通过查询字符串发送,响应可以随意设置,不过一般是位图图片或值为 204 的状态码。浏览器通过图片探测拿不到任何数据,但可以通过监听 onload 和 onerror 事件知道什么时候能接收到响应。
let img = new Image();
img.onload = img.onerror = function () {
alert('Done!');
};
img.src = 'http://www.example.com/test?name=Nicholas';
2
3
4
5
设置完 src 属性之后请求就开始了,这个例子向服务器发送了一个 name 值。
图片探测频繁用于跟踪用户在页面上的点击操作或动态显示广告。当然,图片探测的缺点是只能发送 GET 请求和无法获取服务器响应的内容。这也是只能利用图片探测实现浏览器与服务器单向通信的原因。
# 4.2 JSONP
JSONP 是“JSON with padding”的简写,是在 Web 服务上流行的一种 JSON 变体。JSONP 看起来跟 JSON 一样,只是会被包在一个函数调用里,比如:
callback({ name: 'Nicholas' }); // 服务器返回格式必须包含回调方法名
JSONP 格式包含两个部分:回调和数据。回调是在页面接收到响应之后应该调用的函数,通常回调函数的名称是通过请求来动态指定的。而数据就是作为参数传给回调函数的 JSON 数据。
function handleResponse(response) {
console.log(response);
}
document.getElementById('send').onclick = function () {
let script = document.createElement('script');
script.src =
'http://10.10.9.1:8888/api/userInfor/companyList2?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);
};
2
3
4
5
6
7
8
9
# 5、Fetch API
Fetch API 能够执行 XMLHttpRequest 对象的所有任务,但更容易使用,接口也更现代化,能够在 Web 工作线程等现代 Web 工具中使用。XMLHttpRequest 可以选择异步,而 Fetch API 则必须是异步。
Fetch API 本身是使用 JavaScript 请求资源的优秀工具,同时这个 API 也能够应用在服务线程(service worker)中,提供拦截、重定向和修改通过 fetch() 生成的请求接口。
# 5.1 基本用法
function fetch(input: RequestInfo, init?: RequestInit): Promise<Response>
const r = fetch('/api/test');
r.then(res => res.text()).then(text => {
console.log(text);
});
2
3
4
两个参数:
- input,要获取资源的 URL
- init,传入的配置对象,包含 body、headers、method 等信息
# 5.2 常见 Fetch 请求模式
与 XMLHttpRequest 一样,fetch() 既可以发送数据也可以接收数据。使用 init 对象参数,可以配置 fetch() 在请求体中发送各种序列化的数据。
// 发送 json 数据
const init = {
method: 'POST', // 发送请求体时必须使用一种 HTTP 方法
body: JSON.stringify({ foo: 'bar' }),
headers: new Headers({ 'Content-Type': 'application/json' }),
};
// 发送参数
const init = {
method: 'POST',
body: 'foo=bar&baz=qux',
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
}),
};
// 发送FormData(支持发送文件)
const formData = new FormData();
let inputFile = document.querySelector("input[type='file']");
formData.append('file', inputFile.files[0]);
formData.append('user', '111');
const init = {
method: 'POST',
body: formData,
};
// 调用
fetch('/api/user', init)
.then(res => res.text())
.then(text => {
console.log(text);
});
// 加载 blob
const imageElement = document.querySelector('img');
fetch('my-image.png')
.then(response => response.blob())
.then(blob => {
imageElement.src = URL.createObjectURL(blob);
});
// 发送跨源请求
fetch('//cross-origin.com'); // 响应要包含 CORS 头部才能保证浏览器收到响应。
// TypeError: Failed to fetch
// No 'Access-Control-Allow-Origin' header is present on the requested resource.
// 发送探测请求
fetch('//cross-origin.com', { method: 'no-cors' }).then(response =>
console.log(response.type),
);
// opaque
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
中断请求:
let abortController = new AbortController();
fetch('wikipedia.zip', { signal: abortController.signal })
.catch(() => console.log('aborted!');
// 10 毫秒后中断请求
setTimeout(() => abortController.abort(), 10);
// 已经中断
2
3
4
5
6
# 5.3 Headers 对象
Headers 对象是所有外发请求和入站响应头部的容器。每个外发的 Request 实例都包含一个空的 Headers 实例,可以通过 Request.prototype.headers 访问,每个入站 Response 实例也可以通过 Response.prototype.headers 访问包含着响应头部的 Headers 对象。这两个属性都是可修改属性。另外,使用 new Headers() 也可以创建一个新实例。
(1)Headers 与 Map 的相似之处
Headers 与 Map 类型都有 get()、set()、has() 和 delete() 等实例方法
let h = new Headers();
let m = new Map();
// 设置键
h.set('foo', 'bar');
m.set('foo', 'bar');
2
3
4
5
Headers 和 Map 都可以使用一个可迭代对象来初始化
let seed = [['foo', 'bar']];
let h = new Headers(seed);
let m = new Map(seed);
console.log(h.get('foo')); // bar
console.log(m.get('foo')); // bar
2
3
4
5
而且,它们也都有相同的 keys()、values() 和 entries() 迭代器接口:
let seed = [
['foo', 'bar'],
['baz', 'qux'],
];
let h = new Headers(seed);
let m = new Map(seed);
console.log(...h.keys()); // foo, baz
console.log(...m.keys()); // foo, baz
2
3
4
5
6
7
8
(2)Headers 独有的特性
在初始化 Headers 对象时,也可以使用键/值对形式的对象
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
2
3
4
5
一个 HTTP 头部字段可以有多个值,而 Headers 对象通过 append() 方法支持添加多个值。
let h = new Headers();
h.append('foo', 'bar');
console.log(h.get('foo')); // "bar"
h.append('foo', 'baz');
console.log(h.get('foo')); // "bar, baz"
2
3
4
5
某些情况下,并非所有 HTTP 头部都可以被客户端修改,而 Headers 对象使用护卫来防止不被允许的修改。不同的护卫设置会改变 set()、append()和 delete()的行为。违反护卫限制会抛出 TypeError。
# 5.4 Request 对象
Request 对象是获取资源请求的接口。这个接口暴露了请求的相关信息,也暴露了使用请求体的不同方式。
(1)创建 Request 对象
let r = new Request('https://foo.com');
console.log(r);
// Request {...}
2
3
Request 构造函数也接收第二个参数——一个 init 对象。这个 init 对象与前面介绍的 fetch() 的 init 对象一样。没有在 init 对象中涉及的值则会使用默认值。
(2)克隆 Request 对象
Fetch API 提供了两种方式用于创建 Request 对象的副本:使用 Request 构造函数和使用 clone() 方法。
let r1 = new Request('https://foo.com');
let r2 = new Request(r1);
console.log(r2.url); // https://foo.com/
console.log(r1.bodyUsed); // true 已使用
console.log(r2.bodyUsed); // false
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
2
3
4
5
6
7
8
9
10
11
12
13
14
这种克隆方式并不总能得到一模一样的副本。最明显的是,第一个请求的请求体会被标记为“已使用”
多次调用 Request
let r = new Request('https://foo.com', { method: 'POST', body: 'foobar' });
fetch(r);
fetch(r);
// TypeError: Cannot construct a Request with a Request object that has already been used.
// 使用 clone
let r = new Request('https://foo.com', { method: 'POST', body: 'foobar' });
// 3 个都会成功
fetch(r.clone());
fetch(r.clone());
fetch(r);
2
3
4
5
6
7
8
9
10
11
# 5.5 Response 对象
Response 对象是获取资源响应的接口。这个接口暴露了响应的相关信息,也暴露了使用响应体的不同方式。
let r = new Response();
# 5.6 body
body 提供了五个方法:
body.text()
方法返回 UTF-8 格式字符串body.json()
方法返回 JSONbody.formData()
方法返回 FormData 实例body.arrayBuffer()
方法返回 ArrayBuffer 实例body.blob()
方法返回 Blob 实例
# 6、Beacon API
在 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"}',
);
2
3
4
5
6
7
sendBeacon() 方法特性:
- sendBeacon() 并不是只能在页面生命周期末尾使用,而是任何时候都可以使用。
- 调用 sendBeacon() 后,浏览器会把请求添加到一个内部的请求队列。浏览器会主动地发送队列中的请求。
- 浏览器保证在原始页面已经关闭的情况下也会发送请求。
- 状态码、超时和其他网络原因造成的失败完全是不透明的,不能通过编程方式处理。
- 信标(beacon)请求会携带调用 sendBeacon() 时所有相关的 cookie。
# 7、Web Socket
Web Socket(套接字)的目标是通过一个长时连接实现与服务器全双工、双向的通信。
在 JavaScript 中创建 Web Socket 时,一个 HTTP 请求会发送到服务器以初始化连接。服务器响应后,连接使用 HTTP 的 Upgrade 头部从 HTTP 协议切换到 Web Socket 协议。
因为 Web Socket 使用了自定义协议,所以 URL 方案(scheme)稍有变化:不能再使用 http:// 或 https://,而要使用 ws:// 和 wss://。前者是不安全的连接,后者是安全连接。
封装 WebSocket:传送门
# 7.1 API
let socket = new WebSocket('ws://www.example.com/');
浏览器会在初始化 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(连接已经关闭)。
# 7.2 发送和接收数据
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);
// 接收数据
socket.onmessage = function (event) {
let data = event.data;
// 对数据执行某些操作
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 7.3 其他事件
WebSocket 对象在连接生命周期中有可能触发 3 个其他事件。
- open:在连接成功建立时触发。
- error:在发生错误时触发。连接无法存续。
- close:在连接关闭时触发。
# 8、安全
在未授权系统可以访问某个资源时,可以将其视为跨站点请求伪造(CSRF,cross-site request forgery)攻击。未授权系统会按照处理请求的服务器的要求伪装自己。Ajax 应用程序,无论大小,都会受到 CSRF 攻击的影响,包括无害的漏洞验证攻击和恶意的数据盗窃或数据破坏攻击。
关于安全防护 Ajax 相关 URL 的一般理论认为,需要验证请求发送者拥有对资源的访问权限。可以通过如下方式实现。
- 要求通过 SSL 访问能够被 Ajax 访问的资源。
- 要求每个请求都发送一个按约定算法计算好的令牌(token)。
注意,以下手段对防护 CSRF 攻击是无效的。
- 要求 POST 而非 GET 请求(很容易修改请求方法)。
- 使用来源 URL 验证来源(来源 URL 很容易伪造)。
- 基于 cookie 验证(同样很容易伪造)。
← 12.2-处理JSON 14-客户端存储 →