HTTP Request In Browser
日常开发中和服务器进行数据交换我们会发起 HTTP 请求,比较传统的做法是使用 XMLHttpRequest,也出现了很多基于 XMLHttpRequest 封装的比较不错的库如axios,或者使用较为新的 Fetch。本篇文章从 HTTP Messages 出发(关于 HTTP Messages 详见之前的文章),讲述 XMLHttpRequest 和 Fetch 两种方式下 Request 和 Response 组成内容对应的属性或方法,以及两种方式比较重要的配置项,最后探讨一下两种方式的优缺点。
Fetch
Fetch 在 Chrome 浏览器的 40 版本(2015)被引入,至今已经六年时间。作为规范标准,现如今主流浏览器都支持,基本上可以不用引入 polyfill 直接使用。在 TypeScript 中 fetch()
的类型定义为fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
。
Request Messages
在使用 fetch 时可以将 Request 实例作为参数传入,Request 类的使用也很简单:new Request(input[, init])
,input 我们常用的就是目标 url,init 的类型为RequestInit
,和 fetch 的可选 init 一致。RequestInit
的定义为:
1 | interface RequestInit { |
下面我们针对 HTTP Request Messages 的组成来说明一下对应的配置项:
- method: 对应
RequestInit
中的 method - remote-url: 对应
Request(input,[,init])
或fetch(input,[,init])
的 input 入参 - headers: 对应
RequestInit
中的 headers - body: 对应
RequestInit
中的 body
除了我们本身不能定义的 HTTP version,其它的 Messages 都可以自行定义。知道了这些对应的配置项,简单的开发需求也就可以轻松胜任了。但是还有一些配置项是比较重要的:
- mode: 可选值:
"cors" | "navigate" | "no-cors" | "same-origin"
,是否允许跨域请求的相关设置,默认值为cors,即允许跨域请求 - credentials: 影响 cookies, HTTP auth 等凭证的传输和设置行为,可选值:
"include" | "omit" | "same-origin"
,默认值为same-origin
,即允许同源的credentials
传输,在 CORS 跨域传输时需要设置值为include
- integrity: subresource integrity文件完整性验证,目前支持的哈希算法包括:sha256, sha384, 和 sha512。如果对比的结果不一致,则会抛出错误
- keepalive: 可以允许 fetch 请求在关闭页面之后仍然存在。在 window.onunload 中设置 fetch 的 keepalive 为 true,这样离开页面的动作和相关记录仍然可以传到服务器,可以应用于离开页面的相关统计需求。但是 keepalive 请求的 body 限制为 64kb,且是当前所有 keepalive 请求的总和。
- signal: 取消 fetch 请求的标志, 取消请求会返回
AbortError
,示例代码如下:
1 | let controller = new AbortController(); |
Response Messages
Fetch 针对 HTTP 响应有专门的类,相关定义为:
1 | interface Body { |
还是针对 HTTP Response Messages 的组成来说明一下对应的配置项:
- status: 对应
Response Interface
中的 status,此外还有针对 2xx 状态码的 ok - reason-phrase: 对应
Response Interface
中的 statusText - headers: 对应
Response Interface
中的 headers - body: 对应
Body Interface
中的 body,此外还有判断 body 内容是否被获取的 bodyUsed,以及 arrayBuffer(),blob(),formData(),json(),text()方法获取 body 内容。需要注意的是Body Interface
相关方法只有第一次能返回结果,后续调用相关方法则会直接抛出TypeError
错误
XMLHttpRequest
XMLHttpRequest 自从 1999 年被 IE 5 引入之后,陆续被各浏览器支持,并被很多著名应用所使用(例如 Google 的 Gmail)。现在很多网站还是基于 XHR 来进行 HTTP 请求,可见 XHR 的经典之处。XHR 的使用也比较简单,让我们快速回顾一下:
1 | const xhr = new XMLHttpRequest(); |
Request Messages
沿用之前的思路,我们来分析一下 HTTP Request Messages 对应的 XHR 配置项:
- method: 对应
open(method, url[, async[, user[, password]]])
方法中的 method - remote-url: 对应
open(method, url[, async[, user[, password]]])
方法中 url - headers: 可以使用
setRequestHeader(header, value)
来设置需要的 headers - body: 对应
send(body)
中的 body
Response Messages
XHR 的 HTTP Response Messages 的组成对应的配置项为:
- status: 对应 XHR 实例的 status 值
- reason-phrase: 对应 XHR 实例的 statusText 值
- headers: XHR 实例获取 headers 内容有两种方法:
getAllResponseHeaders()
和getResponseHeader(headerName)
。getAllResponseHeaders()
是获取全部 headers 并且用\r\n
来分割每个 header,getResponseHeader(headerName)
则是获取某个 header 对应的值 - body: 对应 XHR 实例的 response 值,此外 responseType 的值为返回内容的类型,可以根据相关返回类型来做进一步处理。
XHR 相比 Fetch 之外有一个比较特殊的属性 readyState,可选范围与意义如下所示:
| Value | State | Description |
| :—- | :—————– | :———————————————————– |
| 0
| UNSENT
| Client has been created. open()
not called yet. |
| 1
| OPENED
| open()
has been called. |
| 2
| HEADERS_RECEIVED
| send()
has been called, and headers and status are available. |
| 3
| LOADING
| Downloading; responseText
holds partial data. |
| 4
| DONE
| The operation is complete. |
LOADING
,DONE
等作为 XMLHttpRequest 的 static properties 是可以直接访问的,我们可以用readyState === XMLHttpRequest.DONE
来判断请求是否完成,好处是有更好的语义化。但是在早期的 IE 浏览器定义不一样,如果要适配 IE 11 以下的浏览器,还是建议以readyState === 4
为请求结束的判断条件。
其它比较重要的属性或者方法为:
- XHR.timeout: 设置超时时间,单位为毫秒
- XHR.withCredentials: 在进行跨域 CORS 请求时,是否带上
credentials
,默认值为false
- XHR.abort(): 取消请求的方法
此外 XHR 还有相关的事件,包括 load,loadstart,loadend,process,abort,timeout,error,每种事件对应 XHR 实例上有相应的on
方法。
Summary
从 XMLHttpRequest 和 Fetch 的整体设计来看,Fetch 将 Request,Response,Header 等划分的更为具体,整体更加的清晰。而 XMLHttpRequest 则将各种方法和属性以及相关的事件糅合在一起,显得较为杂乱无章。而且 Fetch 返回的值是一个 Promise,能更好的避免回调地狱的问题。但是 Fetch 在现阶段对上传过程中的进度处理没有比较好的解决方法,这一点 XMLHttpRequest 则可以监听 process 事件来比较方便的解决上传进度问题。