ajax 跨域机制详解,为什么 XMLHttpRequest 的 POST 请求会变 OPTIONS 请求

ajax跨域必读好文,做了md格式化,强化记忆,方便翻阅

转载自:http://itbilu.com/javascript/js/VkiXuUcC.html

要使用XMLHttpRequest对象POST请求不在同一域名下的一个站点,即:跨域请求,请求数据格式为JSON。因此需要使用setRequestHeader()方法设置Content-Typeapplication/json。设置完这个自定义的 HTTP Headers 后,发现原本可以跨域POST请求失效了。调试对应的服务端代码,发现POST请求变成了OPTIONS请求。这与CORS(Cross-Origin Resource Sharing,跨站资源共享)策略有关,设置Content-Type后,CORS简单请求变为 Preflighted 请求。在 Preflighted 请求中,XMLHttpRequest对象会首先发送OPTIONS嗅探,以验证是否有对指定站点的访问权限。

  1. XHR 对 HTTP 请求的访问控制

    XHR 对象对于 HTTP 跨域请求有三种:简单请求、Preflighted 请求、Preflighted 认证请求。简单请求不需要发送OPTIONS嗅探请求,但只能按发送简单的GETHEADPOST请求,且不能自定义 HTTP Headers 。Preflighted 请求和认证请求,XHR会首先发送一个OPTIONS嗅探请求,然后 XHR 会根据OPTIONS请求返回的Access-Control-*等头信息判断是否有对指定站点的访问权限,并最终决定是否发送实际请求信息。

  2. 简单请求

    简单请求进行跨域访问时,XMLHttpRequest对象会直接将实际请求发送给服务器。简单请求具有如下特点:

    • 只能使用GETHEADPOST方法。使用POST方法向服务器发送数据时,Content-Type只能使用application/x-www-form-urlencodedmultipart/form-datatext/plain编码格式。
    • 请求时不能使用自定义的 HTTP Headers

    例如,http://itbilu.comhttp://itbilu.other有跨域访问权限,我们进行简单请求简单请求后,查看请求头及服务返回头信息。

     var xhr = new XMLHttpRequest();
     var url = 'http://itbilu.other/resources/public-data/';
        
     xhr.open('GET', url, true);
     xhr.onreadystatechange = function() {
       if (xhr.readyState == 4) {
         //显示请求结果
         console.log(xhr.getAllResponseHeaders());
       }
     };
     xhr.send();
    

    服务器收到的HTTP请求头,及服务器响应头如下:

     //服务器收到的请示头
     GET /resources/public-data/ HTTP/1.1
     Host: itbilu.other
     User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36
     Accept-Language:en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2,da;q=0.2
     Accept-Encoding: gzip,deflate
     Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
     Connection: keep-alive
     Referer: http://itbilu.com/demo/xhr.html
     Origin: http://itbilu.com
     
     
     //服务响头
     HTTP/1.1 200 OK
     Date: Tue, 22 SEP 2015 22:23:53 GMT
     Server: Nginx/1.8.0 
     Access-Control-Allow-Origin: *
     Keep-Alive: timeout=2, max=100
     Connection: Keep-Alive
     Transfer-Encoding: chunked
     Content-Type: application/xml
    

    通过上面的信息我们可以看出,XHR在发送HTTP请求时,还发送了一个自定义的HTTP Headers字段Origin,由于这是一个简单请求,所在XHR并没有发送OPTIONS嗅探请求。通过服务器的响应头Access-Control-Allow-Origin: *可以看出,其对所有站点都是可以通过XMLHttpRequest对象访问的。

  3. Preflighted 请求

    Preflighted 请求与简单请求不同,Preflighted 请求首先会向服务器发送一个OPTIONS请求,以验证是否对指定服务有访问权限,之后再发送实际的请求。Preflighted 请求具有以下特点:

    • GETHEADPOST方法外,XHR都会使用 Preflighted 请求。使用POST方法向服务器发送数据时,Content-Type使用application/x-www-form-urlencodedmultipart/form-datatext/plain之外编码格式也会使用 Preflighted 请求。
    • 使用了自定义的HTTP Headers后,也会使用Preflighted 请求。

    现在很多数据交互都是基本JSON格式的,下面是一个向服务发送JSON数据的示例:

     var xhr = new XMLHttpRequest();
     var url = 'http://itbilu.other/resources/post-json/';
     var body = {name:'IT笔录'};
         
     xhr.open('POST', url, true);
     xhr.setRequestHeader('X-ITBILU', 'itbilu.com');
     xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
     xhr.onreadystatechange = function() {
         if (xhr.readyState == 4) {
             console.log(xhr.responseText);
         }
     };
     xhr.send(JSON.stringify(body));
    

    在上面的示例中,我们向指定的服务器发送了JSON字符串,指定了一个自定义的头信息X-ITBILU,同时还指定了Content-Typeapplication/json。下面是服务器收到的请求头信息,及服务器的响应头信息:

     //服务器收到的OPTIONS请求头
     OPTIONS /resources/post-json/ HTTP/1.1
     Host: itbilu.other
     User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36
     Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
     Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2,da;q=0.2
     Accept-Encoding: gzip,deflate
     Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
     Connection: keep-alive
     Origin: http://itbilu.com
     Access-Control-Request-Method: POST
     Access-Control-Request-Headers: X-ITBILU
     
     //服务器对OPTIONS请求的响应头
     HTTP/1.1 200 OK
     Date: Tue, 22 SEP 2015 22:23:55 GMT
     Server: Nginx/1.8.0 
     Access-Control-Allow-Origin: http://itbilu.com
     Access-Control-Allow-Methods: POST, GET, OPTIONS
     Access-Control-Allow-Headers: X-ITBILU
     Access-Control-Max-Age: 1728000
     Vary: Accept-Encoding, Origin
     Content-Encoding: gzip
     Content-Length: 0
     Keep-Alive: timeout=2, max=100
     Connection: Keep-Alive
     Content-Type: text/plain
     
     //服务器收到的实际POST请求的请求头
     POST /resources/post-json/ HTTP/1.1
     Host: itbilu.other
     User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36
     Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
     Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2,da;q=0.2
     Accept-Encoding: gzip,deflate
     Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
     Connection: keep-alive
     X-PINGOTHER: itbilu.com
     Content-Type: application/json;charset=UTF-8
     Referer: http://itbilu.com/demo/xhr.html
     Content-Length: 55
     Origin: http://itbilu.com
     Pragma: no-cache
     Cache-Control: no-cache
     
     //服务器对POST请求的响应头
     HTTP/1.1 200 OK
     Date: Tue, 22 SEP 2015 22:23:55 GMT
     Server: Nginx/1.8.0 
     Access-Control-Allow-Origin: http://itbilu.com
     Vary: Accept-Encoding, Origin
     Content-Encoding: gzip
     Content-Length: 256
     Keep-Alive: timeout=2, max=99
     Connection: Keep-Alive
     Content-Type: tapplication/json
    

    在上面的请求中,我们可以看,XHR第一次并没有发送实例的POST请求,而是发送了一个OPTIONS请求。在OPTIONS请求,服务器收到了以下几个自定义头信息:

     Origin: http://itbilu.com
     Access-Control-Request-Method: POST
     Access-Control-Request-Headers: X-ITBILU
    


在上面的请示头中:Access-Control-Request-Method告诉服务器接下来的实际请求是一个POST请求。Access-Control-Request-Headers告诉服务器接下来实际请求将包含一个自定义的请求头X-ITBILU

而在服务器对OPTIONS请求的响应头中,包含了以下头信息:

	Access-Control-Allow-Origin: http://itbilu.com
	Access-Control-Allow-Methods: POST, GET, OPTIONS
	Access-Control-Allow-Headers: X-ITBILU
	Access-Control-Max-Age: 1728000

在上面的响应头中:`Access-Control-Request-Method`告诉请求对象(XHR),服务允许使用`POST`、`GET`、`OPTIONS`方法访问资源。`Access-Control-Request-Headers`告诉请求对象,服务器允许包含自定义请求头`X-ITBILU`。而最后一个响应头`Access-Control-Max-Age`告诉请求对象验证有效时长,在接下来的1728000秒(20天)不用再发送`OPTIONS`请求验证合法性。

在`XMLHttpRequest`对象发送`OPTIONS`请求并验证完以上头信息后,才最终发送了实际的POST请求。
  1. 认证请求

    XMLHttpRequest可以在跨域请求时发送认证信息,但在默认情况下HTTP Cookies和HTTP 认证是不被发送的。要发送 Preflighted 认证请求需要设置XMLHttpRequest对象的withCredentials属性:

     var xhr = new XMLHttpRequest();
     var url = 'http://itbilu.other/resources/credentialed-content/';
     xhr.open('GET', url, true);
     xhr.withCredentials = true;
     xhr.onreadystatechange = function() {
       if (xhr.readyState == 4) {
         //显示请求结果
         console.log(xhr.getAllResponseHeaders());
       }
     };
     xhr.send();
    

    在上面的示例中,我们设置了XMLHttpRequest对象的withCredentials属性为true。虽然这是个简单请求,由于发送了认证信息,所以浏览服务器会拒绝没有Access-Control-Allow-Credentials: true响应头的服务器响应。上面请求的请求头和响应头如下:

     GET /resources/access-control-with-credentials/ HTTP/1.1
     Host: itbilu.other
     User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36
     Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
     Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2,da;q=0.2
     Accept-Encoding: gzip,deflate
     Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
     Connection: keep-alive
     Referer: http://itbilu.com/demo/credential.html
     Origin: http://itbilu.com
     Cookie: Hm_lvt_782f5a889da9ba4cae3c5f5575784ec3=1417282790
     
     
     HTTP/1.1 200 OK
     Date: Tue, 22 SEP 2015 22:20:30- GMT
     Server: Nginx/1.8.0 
     Access-Control-Allow-Origin: http://itbilu.com
     Access-Control-Allow-Credentials: true
     Cache-Control: no-cache
     Pragma: no-cache
     Set-Cookie: Hm_lvt_782f5a889da9ba4cae3c5f5575784ec3=1417282790; expires=Wed, 32-OCT-2015 22:30:31 GMT
     Vary: Accept-Encoding, Origin
     Content-Encoding: gzip
     Content-Length: 106
     Keep-Alive: timeout=2, max=100
     Connection: Keep-Alive
     Content-Type: text/plain
    

    通过上面的请求及响应头,我们可以看出。XHR在发送GET请求时,在请求头中包含了Cookie信息。而服务器在响应请求时,有一个Access-Control-Allow-Credentials: true响应头,表示这是一个认证请求。

    认证请求不同于其它请求的一点,要求明确响应Access-Control-Allow-Origin头,要明确指定要响应的站点,如:itbilu.com。上文中Access-Control-Allow-Origin: *的响应方式在认证请求中是不合法的。

更多参考:HTTP access control (CORS)处理XMLHttpRequest对象AJAX跨域请求问题

若您觉得我的博文对您有帮助,欢迎点击下方按钮对我打赏
打赏