XMLHttpRequest的跨域请求

时间:2022-08-29 09:50:53

缘起

由于浏览器的同源策略,非同源不可请求。

但是,在实践当中,经常会出现需要跨域请求资源的情况,比较典型的例如某个子域名向负责进行用户验证的子域名请求用户信息等应用。

以前要实现跨域访问,可以通过JSONP、Flash或者服务器中转的方式来实现,但是现在我们有了CORS。

CORS与JSONP相比,无疑更为先进、方便和可靠。

1、 JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
2、 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
3、 JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS

JSONP的最基本的原理

动态添加一个<script>标签,而script标签的src属性是没有跨域的限制的。这样说来,这种跨域方式其实与ajax XmlHttpRequest协议无关了。

这样其实"jQuery AJAX跨域问题"就成了个伪命题,jquery $.ajax方法名有误导人之嫌。

如果设为dataType: 'jsonp',这个$.ajax方法就和ajax XmlHttpRequest没什么关系了,取而代之的则是JSONP协议。JSONP是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问。

JSONP即JSON with Padding。由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源。如果要进行跨域请求, 我们可以通过使用html的script标记来进行跨域请求,并在响应中返回要执行的script代码,其中可以直接使用JSON传递 javascript对象。 这种跨域的通讯方式称为JSONP。

Cross-Origin Resource sharing

这是W3C  新出的一个标准,简单的讲就是通过服务器/客户端 一些Headers的设置及确认 来实现跨域请求,这些包头有

  Cross-Origin Resource sharing

这是W3C  新出的一个标准,简单的讲就是通过服务器/客户端 一些Headers的设置及确认 来实现跨域请求,这些包头有

 Syntax

  1. 5.1 Access-Control-Allow-Origin Response Header
  2. 5.2 Access-Control-Allow-Credentials Response Header
  3. 5.3 Access-Control-Expose-Headers Response Header
  4. 5.4 Access-Control-Max-Age Response Header
  5. 5.5 Access-Control-Allow-Methods Response Header
  6. 5.6 Access-Control-Allow-Headers Response Header
  7. 5.7 Origin Request Header
  8. 5.8 Access-Control-Request-Method Request Header
  9. 5.9 Access-Control-Request-Headers Request Header

   例如

  • Access-Control-Allow-Origin: http://www.test.com
  • Access-Control-Allow-Methods: POST, GET, OPTIONS  
  • Access-Control-Allow-Headers: POWERED-BY-MENGXIANHUI  
  • Access-Control-Max-Age: 30

可以参考 http://www.w3.org/TR/access-control/#access-control-allow-origin-response-header  w3c的网站

Access-Control-Allow-Origin: 允许跨域访问的域,可以是一个域的列表,也可以是通配符"*"。这里要注意Origin规则只对域名有效,并不会对子目录有效。
即http://www.test/test/是无效的。但是不同子域名需要分开设置,这里的规则可以参照那篇同源策略
Access-Control-Allow-Credentials: 是否允许请求带有验证信息,这部分将会在下面详细解释
Access-Control-Expose-Headers: 允许脚本访问的返回头,请求成功后,脚本可以在XMLHttpRequest中访问这些头的信息(貌似webkit没有实现这个)
Access-Control-Max-Age: 缓存此次请求的秒数。在这个时间范围内,所有同类型的请求都将不再发送预检请求而是直接使用此次返回的头作为判断依据,非常有用,大幅优化请求次数
Access-Control-Allow-Methods: 允许使用的请求方法,以逗号隔开
Access-Control-Allow-Headers: 允许自定义的头部,以逗号隔开,大小写不敏感

Access-Control-Allow-Credentials

在跨域请求中,默认情况下,HTTP Authentication信息,Cookie头以及用户的SSL证书无论在预检请求中或是在实际请求都是不会被发送的。

但是,通过设置XMLHttpRequest的credentials为true,就会启用认证信息机制。

虽然简单请求还是不需要发送预检请求,但是此时判断请求是否成功需要额外判断Access-Control-Allow-Credentials,如果Access-Control-Allow-Credentials为false,请求失败。

十分需要注意的的一点就是此时Access-Control-Allow-Origin不能为通配符"*"(真是便宜了一帮偷懒的程序员),如果Access-Control-Allow-Origin是通配符"*"的话,仍将认为请求失败

即便是失败的请求,如果返回头中有Set-Cookie的头,浏览器还是会照常设置Cookie

客户端页面test.php

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">  
<head>  
<title>crossDomainRequest</title>  
</head>  
<body>  
<input type='button' value='开始测试' onclick="crossDomainRequest()"  />  
<div id="content"></div>  

<script type="text/javascript"> 
function createXHR(){
	return window.XMLHttpRequest?
	new XMLHttpRequest():
	new ActiveXObject("Microsoft.XMLHTTP");
}
function getappkey(url){
	xmlHttp = createXHR();
	xmlHttp.open("GET",url,false);
	xmlHttp.send();
	result = xmlHttp.responseText;
	return result;
}
function crossDomainRequest(){
	var content =getappkey('http://127.0.0.10/gettest.php');
	document.getElementById("content").innerHTML=content;
}
</script>  
</body>  
</html>  

服务端页面gettest.php

<?php
header("Access-Control-Allow-Origin:  http://127.0.0.1");
echo "test success!";
?>

 XMLHttpRequest的跨域请求

 

如果不允许的话

XMLHttpRequest的跨域请求

 

XMLHttpRequest的跨域请求


 

所以跨域有以下几种方法

通过webserver【nginx】配置来跨域

#
# Wide-open CORS config for nginx
#
location / {
     if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        #
        # Om nom nom cookies
        #
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        #
        # Custom headers and headers various browsers *should* be OK with but aren't
        #
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        #
        # Tell client that this pre-flight info is valid for 20 days
        #
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;
        return 204;
     }
     if ($request_method = 'POST') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
     }
     if ($request_method = 'GET') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
     }
}

示例代码

location /{
add_header 'Access-Control-Allow-Origin' 'http://www.test.com';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET';
 

...
...
 
}

第一条指令:授权从http://www.test.com的请求

第二条指令:当该标志为真时,响应于该请求是否可以被暴露

第三天指令:指定请求的方法,可以是GET,POST等

如果需要允许来自任何域的访问,可以这样配置

Access-Control-Allow-Origin: *

通过后端程序来跨域

<?php
header("Access-Control-Allow-Origin:http://www.test.com");
header("Access-Control-Allow-Origin:*");

echo json_encode($_POST);

?>
<script type="text/javascript">
$("#ajax").click(function(){
    $.ajax({
        type: "POST",
        url: "http://www.test.com/test2.php",
        data: 'name=test',
        dataType:"json",
        success: function(data){
            $('#Result').text(data.name);
        }
    });
});
</script>

JSONP

<?php
if(isset($_GET['name']) && isset($_GET['callback']))  
 //callback根js端要对应,不然会报错的
{
    echo $_GET['callback']. '(' . json_encode($_GET) . ');
}
?>

<script type="text/javascript">
$("#jsonp").click(function(){
    $.ajax({
            url: 'http://www.test.com/test1.php',
            data: {name: 'jsonp'},
            dataType: 'jsonp',
            jsonp: 'callback',      //为服务端准备的参数
            jsonpCallback: 'getdata',   //回调函数
            success: function(){
                alert("success");
            }
    });
});

function getdata(data){
	$('#Result').text(data.name);
}
</script> 

getJSON

<script type="text/javascript">
$("#getjson").click(function(){
    $.getJSON('http://www.test.com/test1.php?name=getjson&callback=?', function(data){
	//没有回调函数,直接处理
        $('#Result').text(data.name);
    })
})
</script> 

getScript

<script type="text/javascript">
$("#getscript").click(function(){
    $.getScript('http://www.test.com/test1.php?name=getscript&callback=getdata');
});
</script>