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 事件来比较方便的解决上传进度问题。