第24章 网络请求与远程资源
前端有多种方式请求服务器资源,比如表单提交,ajax, fetch等方式。ajax的出现,让前端不需要刷新页面就可以局部更新页面数据,之后的jQuery和axios对其进行了封装和优化;而最新的fetch API则成为替代XHR,成为未来的网络请求方案。
XMLHttpRequest(XHR)对象是实现ajax技术的关键,均已在各大浏览器实现。
# XMLHttpRequest 对象
# XHR使用
实现原生的XHR方式如下:
let xhr = new XMLHttpRequest()
xhr.open('get', 'example.txt', false)
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.send(null)
2
3
4
5
6
7
8
9
10
11
12
- open():入参为请求类型("get"、"post"等)、请求 URL,以及表示请求是否异步的布尔值。
- send():入参只有一个,作为请求体发送的数据。如果没有,必须传null。
- readyStat:表示当前处在请求/响应过程的哪个阶段。4为接收完成。
- status:响应的 HTTP 状态。
- responseText:响应体返回的文本。
在收到响应之前如果想取消异步请求,可以调用 abort()方法:
xhr.abort();
# HTTP头部
包括请求头和响应头2种。
默认请求头如下:
Accept:浏览器可以处理的内容类型。
Accept-Charset:浏览器可以显示的字符集。
Accept-Encoding:浏览器可以处理的压缩编码类型。
Accept-Language:浏览器使用的语言。
Connection:浏览器与服务器的连接类型。
Cookie:页面中设置的 Cookie。
Host:发送请求的页面所在的域。
Referer:发送请求的页面的 URI。
User-Agent:浏览器的用户代理字符串。
可以通过setRequestHeader()
方法设置自定义头部,如token。
可以使用 getResponseHeader()
方法和getAllResponseHeaders()
从 XHR 对象获取响应头部。
# GET请求
- 用于向服务器查询某些信息。
- 查询字符串必须使用encodeURIComponent()编码,key/value使用
&
分隔。 - 请求数据是查询字符串格式
- 由于浏览器url长度限制,get请求参数有长度限制。
- 响应快,性能好
# POST 请求
- 用于向服务器发送应该保存的数据,数据通过请求体携带。
- 请求数据可以是任意格式,且无大小限制。
- 占用资源多,响应慢
# XMLHttpRequest Level 2
FormData 类型
let data = new FormData(); data.append("name", "Nicholas");
1
2XHR 对象能够识别作为 FormData 实例传入的数据类型并自动配置相应的头部。
超时
timeout 属性设置了一个时间且在该时间过后没有收到响应时,XHR 对象就会触发 timeout 事件。
overrideMimeType()方法
overrideMimeType()方法用于重写 XHR 响应的 MIME 类型。响应返回的 MIME 类型决定了 XHR 对象如何处理响应。比如服务器实际发送了 XML 数据,但响应头设置的 MIME 类型是 text/plain。此时浏览器拿到的是文本,就当成文本来处理了。可以设置为
xhr.overrideMimeType("text/xml");
将其设置为XML处理。
# 进度事件
有 6 个进度相关的事件。
loadstart:在接收到响应的第一个字节时触发。
progress:在接收响应期间反复触发。
error:在请求出错时触发。
abort:在调用 abort()终止连接时触发。
load:在成功接收完响应时触发,用于替代readystatechange事件。
loadend:在通信完成时,且在 error、abort 或 load 之后触发。
# 跨源资源共享
即CORS 背后的基本思路就是使用自定义的 HTTP 头部允许浏览器和服务器相互了解,以确实请求或响应
应该成功还是失败。
如果服务器决定响应请求,那么应该发送 Access-Control-Allow-Origin 头部,包含相同的源; 或者如果资源是公开的,那么就包含"*"。
如果没有这个头部,或者有但源不匹配,则表明不会响应浏览器请求。
在跨域的时候,默认情况下:
- 不能使用 setRequestHeader()设置自定义头部。
- 不能发送和接收 cookie。
但实际是可以支持的,浏览器会通过OPTIONS请求进行验证。
OPTIONS请求包含以下头部:
Origin:与简单请求相同。
Access-Control-Request-Method:请求希望使用的方法。
Access-Control-Request-Headers:(可选)要使用的逗号分隔的自定义头部列表。
服务器响应头可以包含以下信息:
Access-Control-Allow-Origin:与简单请求相同。
Access-Control-Allow-Methods:允许的方法(逗号分隔的列表)。
Access-Control-Allow-Headers:服务器允许的头部(逗号分隔的列表)。
Access-Control-Max-Age:缓存预检请求的秒数。
跨域携带cookie,可以withCredentials 属性设置为 true 来表明请求会发送凭据。如果服务器允许带凭据的请求,那么可以在响应中包含如下 HTTP 头部:
Access-Control-Allow-Credentials: true
其他可以跨域的手段有:图片探测、JSONP等。
# Fetch API
# 基本用法
fetch(url,init)
url: 资源URL
init: 请求配置,包括请求头,请求体等,字段有
body:是 Blob、BufferSource、FormData、URLSearchParams、ReadableStream 或 String 的
实例
cache:可能的值有default、no-store、reload、no-cache、force-cache、only-if-cached。
credentials:omit、same-origin(默认)、include。
headers:指定请求头部。
method:请求方法。
mode:可能的值有cors、no-cors、same-origin。
其他字段,就不一一列举了。
返回一个Promise实例。
只要服务器返回了响应,fetch() 都会resolved。
违反 CORS、无网络连接、HTTPS 错配及其他浏览器/网络策略问题都会rejected。
fetch('https://qux.com').then((response) => console.log(response.url,response.status));
# 常见 Fetch 请求模式
- 发送 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
3
4
5
6
7
8
9
10
11
12
- 在请求体中发送参数
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,
})
2
3
4
5
6
7
8
9
10
- 发送文件
let imageFormData = new FormData()
let imageInput = document.querySelector("input[type='file'][multiple]")
for (let i = 0; i < imageInput.files.length; ++i) {
imageFormData.append('image', imageInput.files[i])
}
fetch('/img-upload', {
method: 'POST',
body: imageFormData,
})
2
3
4
5
6
7
8
9
10
- 中断请求
let abortController = new AbortController();
fetch('wikipedia.zip', { signal: abortController.signal })
.catch(() => console.log('aborted!');
// 10 毫秒后中断请求
setTimeout(() => abortController.abort(), 10);
// 已经中断
2
3
4
5
6
# Headers 对象
Headers和Map很像
let h = new Headers()
let m = new Map()
// 设置键
h.set('foo', 'bar')
m.set('foo', 'bar')
// 检查键
console.log(h.has('foo')) // true
console.log(m.has('foo')) // true
console.log(h.has('qux')) // false
console.log(m.has('qux')) // false
// 获取值
console.log(h.get('foo')) // bar
console.log(m.get('foo')) // bar
// 更新值
h.set('foo', 'baz')
m.set('foo', 'baz')
// 取得更新的值
console.log(h.get('foo')) // baz
console.log(m.get('foo')) // baz
// 删除值
h.delete('foo')
m.delete('foo')
// 确定值已经删除
console.log(h.get('foo')) // undefined
console.log(m.get('foo')) // undefined
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
并且Headers可以初始化对象。
let seed = {foo: 'bar'};
let h = new Headers(seed);
console.log(h.get('foo')); // bar
2
3
一个 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
6
# Request 对象
Request 对象是获取资源请求的接口。可以通过构造函数的方式创建Request对象。
let r = new Request('https://foo.com');
console.log(r);
2
有2种方式克隆Request对象。
- 将 Request 实例作为 input 参数传给 Request 构造函数
- 使用 clone()方法
# Response 对象
Response 对象是获取资源响应的接口。
可以通过构造函数初始化 Response 对象
let r = new Response();
console.log(r);
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: true
// redirected: false
// status: 200
// statusText: "OK"
// type: "default"
// url: ""
// }
2
3
4
5
6
7
8
9
10
11
12
13
# Beacon API
许多分析工具收集页面信息后,一般是在用户离开页面时进行发送,也就是监听unload事件。但实际上,unload意味着页面销毁,之后的异步请求会被浏览器取消,导致信息发送失败。
为此,W3C引入了Beacon API 。它是POST的语法糖,拥有以下特性:
sendBeacon()并不是只能在页面生命周期末尾使用,而是任何时候都可以使用。
调用 sendBeacon()后,浏览器会把请求添加到一个内部的请求队列。浏览器会主动地发送队
列中的请求。
浏览器保证在原始页面已经关闭的情况下也会发送请求。
状态 码、超时和其他网络原因造成的失败完全是不透明的,不能通过编程方式处理。
-信标(beacon)请求会携带调用 sendBeacon()时所有相关的 cookie。
# Web Socket
Web Socket(套接字)的目标是通过一个长时连接实现与服务器全双工、双向的通信。
协议为ws://和 wss://。ws可以跨域。
# 连接
let socket = new WebSocket("ws://www.example.com/server.php");
ws创建连接后可以通过readyState 属性获取当前连接状态:
WebSocket.OPENING(0):连接正在建立。
WebSocket.OPEN(1):连接已经建立。
WebSocket.CLOSING(2):连接正在关闭。
WebSocket.CLOSE(3):连接已经关闭。
# 关闭
可以调用close()关闭连接。
socket.close();
# 发送消息
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);
2
3
4
5
6
# 接收消息
socket.onmessage = function(event) {
let data = event.data;
// 对数据执行某些操作
};
2
3
4
# 事件
open:在连接成功建立时触发。
error:在发生错误时触发。连接无法存续。
close:在连接关闭时触发。
# 安全
CSRF攻击,是指未授权系统可以访问资源。未授权系统会按照处理请求的服务器的要求伪装自己。
如何验证请求发送者拥有对资源的访问权限:
要求通过 SSL 访问能够被 Ajax 访问的资源。
要求每个请求都发送一个按约定算法计算好的令牌(token)。
以下手段对防护 CSRF 攻击无效:
要求 POST 而非 GET 请求(很容易修改请求方法)。
使用来源 URL 验证来源(来源 URL 很容易伪造)。
基于 cookie 验证(同样很容易伪造)。