跨域资源共享的使用

前言

页面中常常会有需要跨域通信的需求实现,我们知道浏览器的同源策略是不允许不同域之间的相互通信的(这里不深究域的定义及如何才算跨域),比如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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员宝库

Nginx 反向代理解决前后端联调跨域问题

keywords: Nginx反向代理 前后端联调 跨域 ---- 1.什么是跨域 跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏...

73740
来自专栏小巫技术博客

Android多模块构建合并aar解决方案

91430
来自专栏Java学习123

重新设置Tomcat7.0管理员用户名和密码

29950
来自专栏惨绿少年

日志切割之Logrotate

19930
来自专栏IT杂记

Windows下jps, jconsole无法查看本地java进程问题解决

先通过本地java代码运行: System.out.println(System.getProperties()); 查看属性java.io.tmpdir=C:...

32060
来自专栏菩提树下的杨过

hadoop: hbase1.0.1.1 伪分布安装

环境:hadoop 2.6.0 + hbase 1.0.1.1 + mac OS X yosemite 10.10.3 安装步骤: 一、下载解压 到官网 htt...

268100
来自专栏HTML5学堂

PHP的文件上传操作

HTML5学堂:关于文件上传,主要包括“构建基本表单”-“使用AJAX发送请求,上传文件”-“使用PHP获取文件基本信息”-“执行SQL语言,返回基本图片路径”...

57350
来自专栏androidBlog

Git 配置别名 —— 让命令变得更简单

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/de...

15110
来自专栏Django Scrapy

Ubuntu默认防火墙安装、启用、配置、端口、查看状态相关信息

最简单的一个操作: sudo ufw status(如果你是root,则去掉sudo,ufw status)可检查防火墙的状态,我的返回的是:inactive(...

85060
来自专栏乐沙弥的世界

MySQL从库选项log-slave-updates未启用引发的异常

    最近核查一个基于从库复制某张特定的表到另外一个主库调整,未配置log-slave-updates导致表无法正常同步。我们的配置文件中使用了replica...

7210

扫码关注云+社区

领取腾讯云代金券