CORS全称是Cross-Origin Resource Sharing(跨域资源共享),是W3C推荐的跨域请求方案。
同时也需要服务器端程序的配合。
作用主要是以下这些跨站http请求:
1、跨域的AJAX请求。
2、网络字体(Web Font),就是css里面 @font-face定义的字体,可以在服务器端配置哪些域允许跨站载入这些TrueType字体。
3、WebGL的纹理(texture)文件。
4、用HTML5的drawImage函数画到canvas里面的图片。
场景示例
简单的请求
一个简单的跨域请求像下面描述的那样:
1、只使用了GET
, HEAD
或者POST
。假如POST用来向服务器发送数据的话,HTTP头的Content-Type
字段值是application/x-www-form-urlencoded
, multipart/form-data
,或者text/plain
三个中的一个。
2、没有设置自定义的HTTP头,就是哪种以X-开头的,比如X-Modified。
例如域http://localhost
的需要请求http://www.example.com
的内容 :
我们可以很容易的在本地通过host文件来构造两个不同的域。
var xhr = new XMLHttpRequest(); var url = 'http://www.example.com/cors.php'; xhr.open('GET', url, true); xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } }; xhr.send();
浏览器发出和服务器响应的http头如下:
GET /cors.php HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Referer: http://localhost/cors.html Origin: http://localhost Connection: keep-alive HTTP/1.1 200 OK Date: Thu, 23 Apr 2015 13:41:02 GMT Server: Apache/2.4.9 (Win32) OpenSSL/1.0.1g PHP/5.5.11 X-Powered-By: PHP/5.5.11 Access-Control-Allow-Origin: http://localhost Content-Length: 55 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: application/xml
请求头的Origin
字段是说明当前请求是来自那个域。
响应头里面有个 Access-Control-Allow-Origin
字段,Origin
头和 Access-Control-Allow-Origin
头展示了访问控制协议最简单的使用。
服务器返回 Access-Control-Allow-Origin: *
意思是这台服务器上面的资源可以被任意域请求。
像上面那样,如果http://www.example.com
只允许http://localhost
跨域请求资源的话,要这样响应头:
Access-Control-Allow-Origin: http://localhost
还有一点就是Access-Control-Allow-Origin
的值必须是请求头里Origin
的值。
对应的服务器端代码如下(PHP为例):
<?php if( $_SERVER['HTTP_ORIGIN'] == 'http://localhost' ) { header('Access-Control-Allow-Origin: http://localhost'); header('Content-type: application/xml'); echo '<?xml version="1.0"?><person><name>Arun</name></person>'; }
Preflighted请求
和上面的简单请求不同,Preflighted请求是先用OPTIONS
方法发送一个HTTP请求到目标域,目的是为了确保实际的请求可以安全发送。
一个Preflighted满足下列条件:
1、使用了GET
, HEAD
和POST
之外的HTTP方法。同样的,假如POST
用来发送数据到服务器,请求头的Content-Type的
值是application/x-www-form-urlencoded
, multipart/form-data
, 和text/plain
三个之外的值。
比如用application/xml
或者text/xml
发送数据到服务器,那么这个请求就是Preflighted的。
2、请求设置了自定义的HTTP头。
一个示例:
var xhr = new XMLHttpRequest(); var url = 'http://www.example.com/cors.php'; var body = "<?xml version=\"1.0\"?><person><name>Arun</name></person>"; xhr.open('POST', url, true); xhr.setRequestHeader("X-PINGOTHER", "pingpong"); xhr.setRequestHeader("Content-Type", "application/xml"); xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } }; xhr.send(body);
上面的代码创建了一个包含xml数据,使用POST发送的请求,设置了一个自定义的请求头X-PINGOTHER: pingpong
。
因为这个使用了一个Content-Type
值为application/xml
的头,并且设置了一个自定义的请求头,所以这个请求是Preflighted的。
客户端和服务器通信如下:
OPTIONS /cors.php HTTP/1.1 Host: www.example.com Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Access-Control-Request-Method: POST Origin: http://localhost User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36 Access-Control-Request-Headers: x-pingother, content-type Accept: */* Referer: http://localhost/cors.html Accept-Encoding: gzip, deflate, sdch Accept-Language: zh-CN,zh;q=0.8 HTTP/1.1 200 OK Date: Thu, 23 Apr 2015 14:10:21 GMT Server: Apache/2.4.9 (Win32) OpenSSL/1.0.1g PHP/5.5.11 X-Powered-By: PHP/5.5.11 Access-Control-Allow-Origin: http://localhost Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 1728000 Content-Length: 0 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/plain POST /cors.php HTTP/1.1 Host: www.example.com Connection: keep-alive Content-Length: 55 Pragma: no-cache Cache-Control: no-cache X-PINGOTHER: pingpong Origin: http://localhost User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36 Content-Type: application/xml Accept: */* Referer: http://localhost/cors.html Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.8 HTTP/1.1 200 OK Date: Thu, 23 Apr 2015 14:10:22 GMT Server: Apache/2.4.9 (Win32) OpenSSL/1.0.1g PHP/5.5.11 X-Powered-By: PHP/5.5.11 Access-Control-Allow-Origin: http://localhost Content-Length: 74 Keep-Alive: timeout=5, max=99 Connection: Keep-Alive Content-Type: text/plain
上面第一个请求头是使用了OPTONS
方法的preflight请求。浏览器是根据上面的那段js代码来决定是否需要发送这样一个请求的,服务器响应告诉给浏览器是否可以接受实际的请求。
第一个请求头里面还有另外两个头:
Access-Control-Request-Method: POST Access-Control-Request-Headers: x-pingother, content-type
Access-Control-Request-Method
头是通知服务器实际请求是一个POST
请求。
Access-Control-Request-Headers
头是通知服务器实际请求有一个X-PINGOTHER
的自定义头。
第二个响应头是告诉浏览器POST
请求和X-PINGOTHER
头是可接受的。
看这段:
Access-Control-Allow-Origin: http://localhost Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER Access-Control-Max-Age: 1728000
Access-Control-Allow-Methods
是说POST
,GET
和OPTIONS
都是有效的可用来请求资源的HTTP方法。
Access-Control-Allow-Headers
是说X-PINGOTHER
是在实际的请求中可被接受的HTTP头。
Access-Control-Max-Age
表示这次响应的数据在发送下一个不同的preflight请求前可以被缓存多久,单位是秒,这里1728000秒,在发送下一次不同的preflight请求之前可以被缓存20天。
我这里测试的结果时chrome41没有缓存,每次刷新都有OPTIONS
发出,而firefox37是有缓存OPTIONS
响应的,第一次发送过后,再刷新页面就不会再发OPTIONS
请求了。
对应的服务器端代码(PHP为例):
if($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { if($_SERVER['HTTP_ORIGIN'] == "http://localhost"){ header('Access-Control-Allow-Origin: http://localhost'); header('Access-Control-Allow-Methods: POST, GET, OPTIONS'); header('Access-Control-Allow-Headers: X-PINGOTHER, Content-Type'); header('Access-Control-Max-Age: 1728000'); header("Content-Length: 0"); header("Content-Type: text/plain"); } else{ header("HTTP/1.1 403 Access Forbidden"); header("Content-Type: text/plain"); echo "You cannot repeat this request"; } } elseif($_SERVER['REQUEST_METHOD'] == "POST"){ if($_SERVER['HTTP_ORIGIN'] == 'http://localhost'){ $postData = file_get_contents('php://input'); //$document = simplexml_load_string($postData); // do something with POST data $ping = $_SERVER['HTTP_X_PINGOTHER']; header('Access-Control-Allow-Origin: http://localhost'); header('Content-Type: text/plain'); echo 'Processed,',$ping,',',$postData; } else{ die("POSTing Only Allowed from http://localhost"); } } else { die("No Other Methods Allowed"); }
需要凭证的请求
默认情况下,跨域ajax请求浏览器是不会设置凭证信息的,当发起一个需要凭证的请求时,需要设置一个特殊的标志。
同样的http://localhost
用一个带cookie的简单的GET请求跨域请求http://www.example.com
域的资源。
js代码如下:
var xhr = new XMLHttpRequest(); var url = 'http://www.example.com/cors.php'; xhr.open('GET', url, true); xhr.withCredentials = true; xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } }; xhr.send();
上面设置属性withCredentials
为true
就是确保这个请求是带cookie的。默认的话是不带cookie的。
下面是浏览器服务器的请求响应头:
GET /cors.php HTTP/1.1 Host: www.example.com Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Origin: http://localhost User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36 Accept: */* Referer: http://localhost/cors.html Accept-Encoding: gzip, deflate, sdch Accept-Language: zh-CN,zh;q=0.8 Cookie: pageAccess=2 HTTP/1.1 200 OK Date: Thu, 23 Apr 2015 14:32:28 GMT Server: Apache/2.4.9 (Win32) OpenSSL/1.0.1g PHP/5.5.11 X-Powered-By: PHP/5.5.11 Set-Cookie: pageAccess=3; expires=Sat, 23-May-2015 14:32:28 GMT; Max-Age=2592000 Access-Control-Allow-Origin: http://localhost Access-Control-Allow-Credentials: true Cache-Control: no-cache Pragma: no-cache Content-Length: 109 Keep-Alive: timeout=5, max=99 Connection: Keep-Alive Content-Type: text/plain
请求头带了cookie,响应头里面包含Access-Control-Allow-Credentials: true
。
还有一点就是,这种请求服务器必须指定一个允许请求的域名,不能是*
,否则会失败。
比如上面这个响应头, Access-Control-Allow-Origin: *
不能是这样,而必须是Access-Control-Allow-Origin: http://localhost
。
第一次请求是没有cookie的,服务器会设置一个cookie,之后的请求全都带有cookie。
对应的服务器端代码(PHP为例):
if($_SERVER['REQUEST_METHOD'] == 'GET'){ if (!isset($_COOKIE["pageAccess"])) { setcookie("pageAccess", 1, time()+2592000); header('Access-Control-Allow-Origin: http://localhost'); header('Cache-Control: no-cache'); header('Pragma: no-cache'); header('Access-Control-Allow-Credentials: true'); header('Content-Type: text/plain'); echo 'I do not know you or anyone like you so I am going to mark you with a Cookie :-)'; } else{ $accesses = $_COOKIE['pageAccess']; setcookie('pageAccess', ++$accesses, time()+2592000); header('Access-Control-Allow-Origin: http://localhost'); header('Access-Control-Allow-Credentials: true'); header('Cache-Control: no-cache'); header('Pragma: no-cache'); header('Content-Type: text/plain'); echo 'Hello -- I know you or something a lot like you! You have been to ', $_SERVER['SERVER_NAME'], ' at least ', $acesses-1, ' time(s) before!'; } } elseif($_SERVER['REQUEST_METHOD'] == "OPTIONS") { // Tell the Client this preflight holds good for only 20 days if($_SERVER['HTTP_ORIGIN'] == 'http://localhost'){ header('Access-Control-Allow-Origin: http://localhost'); header('Access-Control-Allow-Methods: GET, OPTIONS'); header('Access-Control-Allow-Credentials: true'); header('Access-Control-Max-Age: 1728000'); header("Content-Length: 0"); header("Content-Type: text/plain"); } else{ header("HTTP/1.1 403 Access Forbidden"); header("Content-Type: text/plain"); echo "You cannot repeat this request"; } } else{ die("This HTTP Resource can ONLY be accessed with GET or OPTIONS"); }
下面分别介绍一下CORS里面可用的头
响应头
1、Access-Control-Allow-Origin
语法:
Access-Control-Allow-Origin: <origin> | *
这个头是指定可访问资源的URL。浏览器必须强制执行。
对于不需要认证的请求,服务器可以指定一个通配符*,意味着可以被任何人访问。
还可以设置为指定的域名。
2、Access-Control-Expose-Headers
这个头是服务器告诉浏览器那些头是可以被放在请求资源的头里面的。
就是服务器的一个头白名单。
比如:
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
允许the X-My-Custom-Header 头 X-Another-Custom-Header放在浏览器的请求头里面。
3、Access-Control-Max-Age
这个头的意思是一个preflight请求可以被缓存多久,单位是秒,如果在缓存过期之前又发起了一个另一个preflight请求,之前被缓存的请求也会过期。
4、Access-Control-Allow-Credentials
语法:
Access-Control-Allow-Credentials: true | false
用来确定响应是否有效,当作为preflight请求的响应时,可以用来确定实际的请求是否可以使用凭证。
一个非preflighted的简单请求,加入请求带了凭证,当时响应头里面没有Access-Control-Allow-Credentials头,响应会被浏览器忽略,并且没有返回内容。
5、Access-Control-Allow-Methods
用于指定可以使用的HTTP方法,这个头用在preflight请求的响应里面。
6、Access-Control-Allow-Headers
用在preflight请求的响应里面,声明在实际请求里面可以使用的HTTP头。
请求头
1、Origin
指明请求的来源。这个头可以设置为空字符串,但是在访问控制请求里面,这个头的值永远都需要设置。
2、Access-Control-Request-Method
这个头用在preflight请求里面,告诉服务器实际的请求是用的什么HTTP方法。
3、Access-Control-Request-Headers
也是用在preflight请求里面,告诉服务器实际的请求会使用什么HTTP头。
最后就是CORS的浏览器兼容性了,大部分的桌面浏览器和移动浏览器都是支持的。
遇到不支持的那就只能用其他方法了。
参考资料:HTTP access control
本来链接:https://360us.net/article/31.html