何璇

1 年前
  • 2326

    浏览
  • 1

    评论
  • 0

    收藏

跨域资源共享的使用

本文作者:IMWeb 何璇 原文出处:IMWeb社区 未经同意,禁止转载

前言

页面中常常会有需要跨域通信的需求实现,我们知道浏览器的同源策略是不允许不同域之间的相互通信的(这里不深究域的定义及如何才算跨域),比如a.com有b.com想要的数据,那么在b.com页面中发送ajax请求到a.com是不允许的,相信大家都知道一些跨域通信的实现方法:

  • JSON-P(安全性不好)
  • window.name + iframe(实现的方式恶心)
  • window.postMessage(HTML5)
  • proxy(麻烦的部署及维护)
  • ...

跨域资源共享(Cross-Origin Resource Sharing)是W3C的一项规定,它规定了在浏览器中,基于XMLHttpRequest对象的跨域请求通信的原理,基本上保持了原有对象的用法。

CORS需要服务器端及客户端双方面的更改支持。本文主要介绍如何发起一个跨域请求和如何在服务器端支持CORS。

兼容性

  • Chrome 3+
  • Firefox 3.5+
  • Opera 12+
  • Safari 4+
  • Internet Explorer 8+

发起一个跨域请求

  1. 第一步新建XMLHttpRequest对象
function get_CORS_XHR(method, url) {
    var xhr = new XMLHttpRequest();
    if ("withCredentials" in xhr) {
        // "withCredentials"属性只存在于XMLHttpRequest2对象中
        // Chrome, Firefox, Opera and Safari
        xhr.open(method, url, true);

    } else if (typeof XDomainRequest != "undefined") {
        // XDomainRequest对象,兼容IE
        xhr = new XDomainRequest():
        xhr.open(method, url);

    } else {
        xhr = null;

    }
    return xhr;
}

var xhr = get_CORS_XHR('GET', 'http://example.com');
  1. 事件处理

XMLHttpRequest2对象新增了许多事件类型,原先的对象只支持onreadystatechange,新增事件有: (*星号代表IE不支持)

  • onloadstart*(请求开始发送)
  • onprogress(加载和发送数据中)
  • onabort*(实例abort方法被调用)
  • onerror(请求失败)
  • onload(请求成功)
  • ontimeout(请求超时)
  • onloadend(请求完成,不管失败还是结束)
// 通常我们处理最多的就是
xhr.onload = function() {
    var responseText = xhr.responseText;
    console.log(responseText);
    // ...
};

xhr.onerror = function() {...};
  1. withCredentials & 发送

默认情况下,标准的CORS请求是不会发送任何cookie信息的。如果你想让请求带上对应域的cookies信息(需要server支持),那么你需要:

xhr.withCredentials = true;
// 响应报文头部加上 Access-Control-Allow-Credentials: true

// handlers ...
xhr.send();

Server跨域请求处理支持

请求分类

可以给跨域请求分个类:

  1. 简单请求

符合下列要求的请求可以说是简单请求:

- HTTP Method
    - HEAD
    - GET
    - POST
- HTTP Headers
    - Accept
    - Accept-Language
    - Content-Language
    - Last-Event-ID
    - Content-Type
        - application/x-www-form-urlencoded
        - multipart/form-data
        - text/plain
  1. 不太简单的请求(A term by Monsur Hossain)

不符合(1)中的条件的请求

浏览器如Chrome, Firefox等会在不太简单的CORS请求发送前,为安全性考虑先发送一条”preflighted”OPTIONS请求

服务器端的处理根据请求的复杂程度处理方式有所不同。

处理简单请求

让我们举个栗子,CORS指定头部为粗体: Javascript:

var url = 'http://api.alice.com/cors';
var xhr = get_CORS_XHR('GET', url);
xhr.send();

HTTP 请求:

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

值得注意的是CORS请求中必定包含Origin头部,但是包含此头部不一定意味着这个请求就是CORS请求。一个成功的跨域请求的响应报文可以是:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

所有CORS相关的头部均以Access-Control-开头,下面是关于各个头部的细节:

  • Access-Control-Allow-Origin(required) 此头部必须添加到响应报文中 ,不然缺省值会导致CORS请求失败。你可以设置*值让所有站点都可以访问你的数据,但最好还是控制一下
  • Access-Control-Allow-Credentials(optional) 设置此头部的值为true,如果你想要请求附带cookies。与上文提到的withCredentials属性协作。若此头部值为true而withCredentials属性为false,会导致请求失败,反之亦然
  • Access-Control-Allow-Expose-Headers(optional) XMLHttpRequest2对象存在getResponseHeader方法,允许访问一些简单的响应头部如:Content-Type,Cache-Control等等。如果想暴露一些特殊的头部,可以在此头部的值设置以逗号分隔的头部名称
处理不太简单的请求

如上文所说,处理不太简单的请求时,浏览器会先发出一次preflighted的请求,得到服务器允许后才执行真正的跨域请求,preflighted请求的结果会被缓存,多条请求同一服务器的跨域请求只会发送一次preflighted请求。举个栗子: Javascript:

var url = 'http://api.alice.com/cors';
var xhr = createCORSRequest('PUT', url);
xhr.setRequestHeader(
    'X-Custom-Header', 'value');
xhr.send();

Preflight请求:

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
  • Access-Control-Request-Method 真实的请求方法,在此为PUT。所有的Preflight请求都应该包含此头部
  • Access-Control-Request-Headers 值是以逗号分隔的头部名称,代表请求附带的其余头部

Preflight响应:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
  • Access-Control-Allow-Origin(同上文)
  • Access-Control-Allow-Credentials(同上文)
  • Access-Control-Allow-Methods(required) 允许跨域请求的请求类型,以逗号分隔。由于preflight响应可能被缓存,所以此头部设置会有所帮助
  • Access-Control-Allow-Headers 当请求中有Access-Control-Request-Headers头部时,此响应头说明服务器支持的头部,以逗号分隔
  • Access-Control-Max-Age(required) 指定preflight响应可以被缓存的时间,单位秒

真实的请求跟响应就可以正常发送接收了。如果服务器对preflight请求直接返回HTTP 200,不包含任何CORS指定的头部,那么这个跨域请求就会失败,触发onerror事件。控制台中会输出类似一下的报错信息:

XMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

Server处理流程图

图片

注: 最好在服务器端的Access-Control-Allow-Methods头部加上OPTIONS方法

Access-Control-Allow-Methods: GET,POST,OPTIONS
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://qunedu.oa.com

本文基本上翻译自http://www.html5rocks.com/en/tutorials/cors/,感谢Monsur Hossain。

1条评论

    您需要 注册 一个IMWeb账号或者 才能进行评论。