原文地址:https://docs.webplatform.org/wiki/tutorials/using_cors

总结

  一篇对"Cross Origin Resource Sharing"(CORS)的简介.

简介

  APIS是让你能将富文本网页关联在一起的工具,但是将这个方式应用在浏览器上是很困难的,像JSONP(基于安全考虑被限制)和设置代理(设置繁琐并且维护困难)这样的跨域请求是被浏览器限制的。

  Cross-Origin-Resource-Sharing(CORS) 是W3C用于浏览器跨域请求的规范,通过配置XmlHttpRequest对象,CORS允许开发者像使用同源请求一样执行跨域请求的操作.

  使用CORS是很简单的,想象一下一个站点alex.com中的部分数据bob.com想去访问这些数据,这种类型的请求在传统的请求因为同源策略的限制是不可能的,然而,由于有CORS得支持,alex.com站点的服务器可以设置一些特殊的响应同步让bob.com访问到这些数据.

  在这篇文章你将了解到,CORS的实现需要服务器和客户端协作,幸运的是,如果作为一个客户端开发者,实现的具体细节对你来讲是不可见的,下面将介绍客户端如何发起一个跨域请求以及服务器如何配置以支持跨域请求.

1 客户端发起跨域请求

  本节将介绍如何使用JavaScript发起一个跨域请求

 1.1 创建XmlHttpRequest对象

   首先,你需要创建一个合适的请求对象.

 function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// for others browers
// Check if the XMLHttpRequest object has a "withCredentials" property.
// "withCredentials" only exists on XMLHTTPRequest2 objects.
xhr.open(method, url, true); } else if (typeof XDomainRequest != "undefined") {
// for IE
// Otherwise, check if XDomainRequest.
// XDomainRequest only exists in IE, and is IE's way of making CORS requests.
xhr = new XDomainRequest();
xhr.open(method, url); } else {
// Otherwise, CORS is not supported by the browser.
xhr = null;
}
return xhr;
} var xhr = createCORSRequest('GET', url);
if (!xhr) {
throw new Error('CORS not supported');
}

1.2 事件处理

  原始的XmlHttpRequest对象只有onreadystatechange一个事件,在这个事件中可以处理所有的服务器响应,虽然在XmlHttpRequest2中依然可以使用它,但是却增加了一些新的事件处理,如下列表所示:

 在大多数情况下,你至少需要处理onload和onerror这两个事件.

 xhr.onload = function() {
var responseText = xhr.responseText;
console.log(responseText);
// process the response.
}; xhr.onerror = function() {
console.log('There was an error!');
};

  浏览器在onerror事件中对错误信息的报告不是很完善,浏览器会报告这个错误,错误却不能被JavaScript访问到,你知道错误发生了,但是不知道更多的信息.

1.3 withCredentials

  在客户端标准的CORS请求默认不会发送或者设置任何cookie,为了将cookie作为请求的一部分,你需要将XmlHttpRequest的withCredentials属性设置为true.

xhr.withCredentials = true

  在服务器端你必须通过设置"Access-Control-Allow-Credentials"头部为true才能启用Credentials功能.

Access-Control-Allow-Credentials: true;

  在一个请求中withCredentials属性可以包含来自远端域名的任何cookie,并且还可以设置这些cookie,值得注意的是这些cookie任然遵循同源策略,因此你的javascript代码不可以通过domian.cookie或者请求的头部去访问这些cooike的,它们只能被远端域名服务器操作.

1.4 发起请求

  现在你的客户端已经都配置好了,通过调用send()函数发送请求.

xhr.send(null);

  如果请求中要包含数据体,可以作为send()函数的参数进行传递.

  假设你的服务端已经做好了响应CORS请求的准备,客户端的onload事件将与同源请求的XmlHttpRequest一样了.

1.5 完整例子

  这是一个完整的客户端CORS请求例子

 // Create the XHR object.
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// XHR for Chrome/Safari/Firefox.
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// XDomainRequest for IE.
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
// CORS not supported.
xhr = null;
}
return xhr;
} // Helper method to parse the title tag from the response.
function getTitle(text) {
return text.match('<title>(.*)?</title>')[1];
} // Make the actual CORS request.
function makeCorsRequest() {
// bibliographica.org supports CORS.
var url = 'http://bibliographica.org/';
var xhr = createCORSRequest('GET', url);
if (!xhr) {
alert('CORS not supported');
return;
} // Response handlers.
xhr.onload = function() {
var text = xhr.responseText;
var title = getTitle(text);
alert('Response from CORS request to ' + url + ': ' + title);
}; xhr.onerror = function() {
alert('Woops, there was an error making the request.');
};
xhr.send();
}

2 服务端配置支持CORS请求

  大部分繁重的工作被浏览器和服务器处理了.在CORS工作期间,浏览器会自动补充一些头部信息,有时候甚至会自动发起一些必要的请求,这些对于客户端开发者来讲是不可见的,浏览器帮你做了这些工作(当然可以通过wireshark获得这些细节).

  浏览器厂商负责实现这些细节,下面将描述如何配置服务器端头部以支持CORS的请求.

2.1 CORS请求的类型

  跨域请求分为以下两种:

  1:简单请求

  2:非简单请求(我自己总结的)

  "简单请求"的标准如下:

  • HTTP请求方式如下(区分大小写)

    • HEAD
    • GET
    • POST
  • HTTP头部如下(不区分大小写)
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type(只包括下面几种)
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain

  "简单请求"本身的特点是它已经被浏览器组织好了,并不需要使用CORS.比如JSONP用于跨域请求使用的GET方式.

  任何不符合上面标准的请求就是"非简单请求",这个请求在客户端和浏览器之间做了一些额外的通信(叫做"preflight request"),下面详细讲解.

2.2 处理一个简单请求

  从客户端的一个"简单请求"开始,下面的JavaScript代码是发送一个跨域"简单请求"的代码,以及浏览器实际发送的"简单请求"的头部信息.

JavaScript:

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

HTTP Request:

 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头部,这个Origin头部是浏览器自动添加的,而且不能被JavaScript控制,它的值一般是协议名称(比如http),域名(比如api.bob.com),端口(没有特别说明默认为80)的组合,如 Origin: http://api.bob.com
  下面是一个合法的服务端的响应结构:

 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(必须的):这个头部必须包含在响应报文中;缺少这个头部将造成CORS请求失败,它的值可以是请求报文中的origin的值(表示此站点的跨域请求被允许)或者是一个符号"*"(表示允许任何站点的跨域请求).

  Access-Control-Allow-Credentials(可选项):默认情况下,cookie是不包含在CORS的请求报文中的.使用这个头部就表示cookie是应该包含在CORS的请求报文中的,这个头部的合法值是true(小写),服务器不需要cookie就没必要返回这个头部(或者返回时它的值为false),这个头部的工作和客户端的.withCredentials属性值联系在一起的,两个的值都为true CORS请求才会成功。值得注意的是,除非你确定cookie包含在请求报文中,否则不要设置这两个量.

  Access-Control-Expose-Headers(可选项):XmlHttpRequest2对象有一个getResponseHeader()方法可以返回详细的HTTP头部信息,在CORS请求中,getResponseHeader()方法只能获得简单的响应头部信息,简单响应头如下:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

  如果你想在客户端访问除上以外的其他头部详细,你就需要使用Access-Control-Expose-Headers头部,这个头部的值是一个逗号分隔的列表,这个列表会暴露给客户端.

  顺便说一下,大多数的浏览器实现的getResponseHeader()方法都是有bug的,会导致即使服务器设置了Access-Control-Expose-Headers头部客户端的getResponseHeader()函数也有可能会访问不到.

2.3 处理一个非简单请求

  讲了处理简单的GET请求,但是如果你想做更多的事情该怎么做呢?也许你想是否能支持其他类型的HTTP请求,比如PUT or DELETE,或者是你想使用Content-Type:application/json头来支持请求json数据,这就是所谓的处理非简单的请求.

  非简单请求表面上看起来是一个客户端向服务器的单一请求,但是实际上是包含了两个请求.浏览器首先向服务器发送一个预先请求(prelight request),目的是询问服务器我是否有发送实际请求的权限,一旦服务器回应了准许,浏览器才会发出实际请求.浏览器透明地处理这两个请求,预先请求(prelight request)会被缓存下来,因此没有必要每次请求前都发送预先请求.

  下面是一个非简单请求的实例:

JavaScript:

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

预先请求发送的报文头部:

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...

  预先请求头部和简单请求头部一样,浏览器都自动添加了Origin头部,预先请求使用的是HTTP 的OPTIONS请求方式(要确保服务器可以响应这个请求方式),下面还有一些额外的请求头部:

  Access-Control-Request-Method:实际的请求方法,这个请求头部始终是包含了的,就算是一些简单请求的请求方法(像POST,GET,HEAD).

  Access-Control-Request-Headers:包含在请求头部中的用逗号分隔的一个列表.

  预先请求的作用是为实际请求发出之前询问权限的,服务器应该检验上面的两个头部和在请求头部中的HTTP方法是否支持和接受.如果HTTP方法和头部都是合法并被服务器接受的话就会返回下面的报文:

  预先请求报文:

 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-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(必须的):和简单请求的一样,必须包含在响应头部中,如果没有CORS就会失败.

  Access-Control-Allow-Methods(必须的):用逗号隔开的服务器支持的HTTP方法的列表,值得注意的是,虽然预先请求只是为单个HTTP方法(例子为PUT方法)询问是否有权限,但是服务器还是会将服务器所有支持的HTTP方法(支持GET,POST,PUT)以列表的方式返回.这是很有帮助的,因为预先请求会被缓存,所以一个单一方法的预先请求的返回信息就包含了其它HTTP方法的支持与否.

  Access-Control-Allow-Headers(如果请求报文头部包含了Access-Control-Request-Headers就是必须要存在的):值为用逗号分隔的所有支持的请求头列表,和Access-Control-Allow-Methods一样,它包含了服务器支持的所有头部.

  Access-Control-Allow-withCredentials:和上面简单请求的一样.

  Access-Control-Max-Age(可选的):在每个实际请求之前都发起一个预先请求是很昂贵的,因为这样每个客户端的请求将会请求两次,所以这个头部就告诉浏览器,在第一次发起预先请求之后的多少秒之内的请求是不需要发送预先请求来询问权限的.

  如果服务器想终止CORS的请求,这时候可以返回一个普通的响应(HTTP 200),但是却没有任何CORS头部(Access-Control-开头的)信息,如下:

  预先请求:

 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...

  预先请求响应:

// ERROR - No CORS headers, this is an invalid request!
Content-Type: text/html; charset=utf-8

  浏览器会打出下面的log:

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

  预先请求一旦获得了响应,接下来就会发起实际请求.

  实际请求:

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

  实际请求响应:

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

Using CORS(译)的更多相关文章

  1. CORS详解[译]

    介绍 由于同源策略的缘故,以往我们跨域请求,会使用诸如JSON-P(不安全)或者代理(设置代理和维护繁琐)的方式.而跨源资源共享(Cross-Origin Resource Sharing)是一个W3 ...

  2. REST API设计指导——译自Microsoft REST API Guidelines(四)

    前言 前面我们说了,如果API的设计更规范更合理,在很大程度上能够提高联调的效率,降低沟通成本.那么什么是好的API设计?这里我们不得不提到REST API. 关于REST API的书籍很多,但是完整 ...

  3. 你不可不知的WEB安全知识(第一部分:HTTPS, TLS, SSL, CORS, CSP)

    译   原文地址:https://dev.to/ahmedatefae/web-security-knowledge-you-must-understand-it-part-i-https-tls-s ...

  4. CORS跨源资源共享概念及配置(Kubernetes Ingress和Spring Cloud Gateway)

    我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 跨源资源共享CORS 跨源资源共享 (CORS) (或通俗地译为跨域资源共享)是一种基于HTTP 头的机制,该机制通过 ...

  5. RxJS + Redux + React = Amazing!(译一)

    今天,我将Youtube上的<RxJS + Redux + React = Amazing!>翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: https:/ ...

  6. Entity Framework 6 Recipes 2nd Edition 译 -> 目录 -持续更新

    因为看了<Entity Framework 6 Recipes 2nd Edition>这本书前面8章的翻译,感谢china_fucan. 从第九章开始,我是边看边译的,没有通读,加之英语 ...

  7. ASP.NET Web API 跨域访问(CORS)

    一.客户端用JSONP请求数据 如果你想用JSONP来获得跨域的数据,WebAPI本身是不支持javascript的callback的,它返回的JSON是这样的: {"YourSignatu ...

  8. RxJS + Redux + React = Amazing!(译二)

    今天,我将Youtube上的<RxJS + Redux + React = Amazing!>的后半部分翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: ht ...

  9. ASP.NET Core CORS 简单使用

    CORS 全称"跨域资源共享"(Cross-origin resource sharing). 跨域就是不同域之间进行数据访问,比如 a.sample.com 访问 b.sampl ...

随机推荐

  1. python内建模块Collections

    # -*- coding:utf-8 -*- # OrderedDict可以实现一个FIFO(先进先出)的dict, # 当容量超出限制时,先删除最早添加的Key: from collections ...

  2. CsvHelper文档-2读

    CsvHelper文档-2读 这个库默认不需要做任何设置就可以很容易的使用它.如果你的类属性名称直接匹配csv的标题名称,那么可以按照下面的实例来用: (以下所有的代码都需要引用using csvhe ...

  3. C#二次封装虹软arc研究

    相信很多用C#又想用虹软的SDK的童鞋要花很多心思去研究怎么转换,所以写了一篇文章和一个demo方便用C#的童鞋方便调用虹软的接口, 文章的地址是:https://blog.xgcos.com/sho ...

  4. 四:HDFS Snapshots

    1.介绍 HDFS快照保存某个时间点的文件系统快照,可以是部分的文件系统,也可以是全部的文件系统.快照用来做数据备份和灾备.有以下特点: 1.快照几乎是实时瞬间完成的 2.只有在做快照时文件系统有修改 ...

  5. vue移动音乐app开发学习(二):页面骨架的开发

    本系列文章是为了记录学习中的知识点,便于后期自己观看.如果有需要的同学请登录慕课网,找到Vue 2.0 高级实战-开发移动端音乐WebApp进行观看,传送门. 完成后的页面状态以及项目结构如下: 一: ...

  6. 如何做好FAE工作及FAE职位发展

    此文较长,是作者对于半导体FAE职业的一些总结,码字不容易,耐心的阅读,欢迎点赞. 曾经认识一位做电源研发的工程师,转行在一家代理商做FAE,做了一年半以后,就提出了离职请求,他老板问他是什么原因,他 ...

  7. Hadoop出现错误:WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable,解决方案

    安装Hadoop的时候直接用的bin版本,根据教程安装好之后运行的时候发现出现了:WARN util.NativeCodeLoader: Unable to load native-hadoop li ...

  8. windows批处理学习(字符换操作)---04

    转自:https://www.cnblogs.com/DswCnblog/p/5432326.html 1.截取字符串 截取字符串可以说是字符串处理功能中最常用的一个子功能了,能够实现截取字符串中的特 ...

  9. 【Docker 命令】- build命令

    docker build 命令用于使用 Dockerfile 创建镜像. 语法 docker build [OPTIONS] PATH | URL | - OPTIONS说明: --build-arg ...

  10. Combobox的使用,日期选择器

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...