chenqunfeng / Blog

个人技术记录博客
6 stars 1 forks source link

XMLHttpResquest详解 #8

Open chenqunfeng opened 7 years ago

chenqunfeng commented 7 years ago

前言

某天直接对一个接口发起跨域post请求时,虽然浏览器正常报跨域请求错误,但是却发现请求的接口居然有正常的返回值,并且是200,但是不会进去success回调而是进入error回调。这与我的认知出现的偏差,故有了这篇文章,多为记录的目的,以防遗忘。

XMLHttpResquest的历史

XMLHttpRequest一开始只是微软浏览器提供的一个接口,后来各大浏览器纷纷效仿也提供了这个接口,再后来W3C对它进行了标准化,提出了XMLHttpRequest标准。XMLHttpRequest标准又分为Level 1和Level 2。

XMLHttpResquest Level 1:

主要属性:

主要缺点:

XMLHttpResquest Level 2:

新功能:

XMLHttpRequest详解

Request Headers的设置和获取

setRequestHeader(header, value)

注意点:

XMLHttpRequest状态追踪

xhr.readyState

  1. 0 Uninitialized 初始化状态。XMLHttpRequest 对象已创建或已被 abort() 方法重置。
  2. 1 Open open() 方法已调用,但是 send() 方法未调用。请求还没有被发送。
  3. 2 Sent Send() 方法已调用,HTTP 请求已发送到 Web 服务器。未接收到响应。
  4. 3 Receiving 所有响应头部都已经接收到。响应体开始接收但未完成。
  5. 4 Loaded HTTP 响应已经完全接收。

XMLHttpRequest超时时间

xhr.timeout
xhr.ontimeout

注意点:

XMLHttpRequest的异步和同步请求

open(method, false) // 异步
open(method, true) // 同步

注意点(当设置为同步请求时,需保证以下几点):

send(data) // data参数的数据类型会影响请求头部content-type的值

注意点:

XMLHttpRequest获取指定类型数据

xhr.overrideMimeType(type) // Level 1 中设置response数据类型
xhr.responseType = type // Level 2 中设置response数据类型

注意,Level 1 不支持设置二进制数据类型,但是可以通过特殊的方式获得

function _arrayBufferToBase64( buffer ) {
    // 如果buffer本身就是无符号整形,则不用经过处理也可以
    // var bytes = buffer;
    var bytes = new Uint8Array( buffer );
    // btoa可以将ascii字符串或二进制数据转换成一个base64编码的字符串,但是不能直接作用在Unicode字符串上,不过这里的图片数据buffer中最大不超过255,故没有问题
    return window.btoa( String.fromCharCode.apply(this, bytes) );
}
var xhr = new XMLHttpRequest();
//向 server 端获取一张图片
xhr.open('GET', '/path/to/image.png', true);

// 这行是关键!
//将响应数据按照纯文本格式来解析,字符集替换为用户自己定义的字符集
xhr.overrideMimeType('text/plain; charset=x-user-defined');

xhr.onreadystatechange = function(e) {
  if (this.readyState == 4 && this.status == 200) {
    //通过 responseText 来获取图片文件对应的二进制字符串
    var binStr = this.responseText;
    var arrayBuffer = [] 
    //然后自己再想方法将逐个字节还原为二进制数据
    for (var i = 0, len = binStr.length; i < len; ++i) {
      // charCodeAt,返回指定位置的字符的Unnicode编码,范围为0-65535
      var c = binStr.charCodeAt(i);
      // 0xff 的二进制表示为 0000000011111111,&操作的情况在都为1的时候才为1,所将byte限制在0-255
      var byte = c & 0xff; 
      arrayBuffer.push(byte)
    }
    // 假设获取的图片格式为jpg,则可以直接将获取的二进制数据转成base64
   base64 = "data:image/jpg;base64," + _arrayBufferToBase64(arrayBuffer)
  }
};

xhr.send();

设置完response数据类型,xhr还提供3个属性来获取请求返回的数据

xhr.response

xhr.responseText

xhr.responseXML

XMLHttpRequest相关事件

// XMLHttpRequest部分实现代码
interface XMLHttpRequestEventTarget : EventTarget {
  // event handlers
  attribute EventHandler onloadstart;
  attribute EventHandler onprogress;
  attribute EventHandler onabort;
  attribute EventHandler onerror;
  attribute EventHandler onload;
  attribute EventHandler ontimeout;
  attribute EventHandler onloadend;
};

interface XMLHttpRequestUpload : XMLHttpRequestEventTarget {

};

interface XMLHttpRequest : XMLHttpRequestEventTarget {
  // event handler
  attribute EventHandler onreadystatechange;
  readonly attribute XMLHttpRequestUpload upload;
};

从代码中可以看到:

所以,xhr共有8个事件,而xhr.upload中有7个事件。

各个xhr事件触发条件说明:

事件触发顺序

当请求一切正常时:

  1. 触发xhr.onreadystatechange(每次readyState变化时,都会触发一次)
  2. 触发xhr.onloadstart
  3. 触发xhr.upload.onloadstart
  4. 触发xhr.upload.onprogress
  5. 触发xhr.upload.onload
  6. 触发xhr.upload.onloadend
  7. 触发xhr.onprogress
  8. 触发xhr.onload
  9. 触发xhr.onloadend

发生absort/timeout/error异常时:

1、一旦发生异常,会立即中止当前请求 2、将readystate置为4,并触发onreadystatechange事件 3、如果还处于上传阶段,则依次触发xhr.upload.onprogress,xhr.upload.[onabort或ontimeout或onerror],xhr.upload.onloadend 4、然后触发下载阶段事件,xhr.onprogress,xhr.[onabort或ontimeout或onerror],xhr.onloadend

理清完这些事件之后,我们可以发现,当请求正常还是不正常,都会触发onreadystatechange事件,而且onreadystatechange的兼容Level 1 和 Level 2,因此将成功回调和失败回调都放在onreadystatechange中是个不错的选择。

xhr.onreadystatechange = function() {
    if (4 === xhr.readyState) {
        var statueCode = xhr.status;
        if (statusCode >= 200 && statusCode <= 300 || statusCode == 304) {
            // 成功回调
        } else {
            // 失败回调
        }
    }
}

XMLHttpRequest跨域请求

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。 CORS需要浏览器和服务器同时支持,目前,几乎所有浏览器都支持该功能,IE浏览器不能低于IE10。 CORS使用目的与JSONP相同,但又不同,JSONP只支持GET请求(如script标签和image标签的使用),而CORS支持所有类型的HTTP请求(GET,HEAD,POST,PUT,DELETE),而且JSONP使用GET请求构造的链接长度有限,而CORS的POST请求可以传递更多数据。

简单请求

需要同时满足以下两个条件,凡是不满足的则属于非简单请求

基本流程:

这个过程有一点需要注意的,因为跨域请求中cookie并不会自动加载request header中,因为浏览器在发送跨域请求时,不能发送认证信息。如果想要打开这个限制,则客户端需要设置xhr.withCredentials =true,服务端也需要在response header中设置字段Access-Control-Allow-Credentials:true,但是如果跨域request中携带了认证信息,则服务端不能将Access-Control-Allow-Origin设置为*,而必须指定域名。

非简单请求

非简单请求一般是对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。当然,还有一种情况会出现“预检”请求,当绑定了xhr.upload事件并且跨域的时候,也一样会发起预检请求。 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为“预检”请求(preflight)。 浏览器(以OPTIONS方法发起请求)会先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP方法和请求头字段,只有在得到肯定答复之后,浏览器才会发出正式XMLHttpRequest请求,否则报错。

参考链接

参考链接1:W3C的xhr 标准 参考链接2:segmentfault,ruoyiqing作者 参考链接3:阮老师的文章