先来看下几个概念 跨域问题:浏览器在请求不同域的资源时,会因为同源策略(SOP)的影响而请求不成功,这就是通常所说的跨域问题。 同源策略:来自wiki的解释

In computing, the same-origin policy is an important concept in the web application security model. Under the policy, a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. An origin is defined as a combination of URI scheme, hostname, and port number.[1][2] This policy prevents a malicious script on one page from obtaining access to sensitive data on another web page through that page's Document Object Model.

对比URL 结果 结果
http://www.example.com/dir/page2.html 同源 相同的协议,主机,端口
http://www.example.com/dir2/other.html 同源 相同的协议,主机,端口
http://username:password@www.example.com/dir2/other.html 同源 相同的协议,主机,端口
http://www.example.com:81/dir/other.html 不同源 相同的协议,主机,端口不同
https://www.example.com/dir/other.html 不同源 协议不同
http://en.example.com/dir/other.html 不同源 不同主机
http://example.com/dir/other.html 不同源 不同主机(需要精确匹配)
http://v2.www.example.com/dir/other.html 不同源 不同主机(需要精确匹配)
http://www.example.com:80/dir/other.html 看情况 端口明确,依赖浏览器实现

常见的跨域解决方式

jsonp

最常用的就是利用$.ajax指定dataType为jsonp,虽然JSONP在跨域ajax请求方面有很强的能力,但是它也有一些缺陷。首先,它没有关于JSONP调用的错误处理,一旦回调函数调用失败,浏览器会以静默失败的方式处理。其次,它只支持GET请求,这是由于该技术本身的特性所决定的。

document.domain

目前,很多大型网站都会使用多个子域名,而浏览器的同源策略对于它们来说就有点过于严格了。如,来自www.a.com想要获取document.a.com中的数据。只要基础域名相同,便可以通过修改document.domain为基础域名的方式来进行通信,但是需要注意的是协议和端口也必须相同。

document.a.com中通过设置

document.domain = 'a.com';

www.a.com中设置:

document.domain = 'a.com';
var iframe = document.createElement('iframe');
iframe.src = 'http://document.a.com';
iframe.style.display = 'none';
document.body.appendChild(iframe);

iframe.onload = function() {
 var targetDocument = iframe.contentDocument || iframe.contentWindow.document;
 //可以操作targetDocument
}

推荐一个使用iframe跨域的库https://github.com/jpillora/xdomain

Nginx反向代理

web2.0时代前后端的分离越来越流行,前后端项目单独部署使得项目更加灵活。举例,前端项目域名为 www.a.com,服务端项目域名为 api.a.com,此时前端请求服务端的时候必然会出现跨域问题,此时可以通过nginx反向代理+域名二级目录的形式解决。

前端设置服务端请求地址为:www.a.com/service/

server {
        listen       80;
        server_name  www.a.com;
        index index.html index.htm index.php;
        root /Data/wwwroot/my-project;

        location /service/ {
                rewrite /service/(.*) /$1 break;
                proxy_pass http://api.a.com;
                proxy_set_header Host $proxy_host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        }
  }

server {
    listen          80;
    server_name     api.a.com;
    root            /Data/wwwroot/my-api-project/public;
    access_log      /usr/local/var/log/nginx/access.log  main;
    index           index.php index.html index.htm;
    location / {
        #autoindex    on;
        include      /usr/local/etc/nginx/conf.d/php-fpm;
        if (-f $request_filename/index.html){
                rewrite (.*) $1/index.html break;
        }
        if (-f $request_filename/index.htm){
                rewrite (.*) $1/index.htm break;
        }
        if (-f $request_filename/index.php){
                rewrite (.*) $1/index.php;
        }
        if (!-f $request_filename){
                rewrite (.*) /index.php;
        }
    }

}

通过nginx的proxy模块,将/service/下的所有请求转发到 api.a.com 上,通过 www.a.com/service/ 隐藏了真正的server地址

跨域资源共享CORS(Cross-origin resource sharing)

CORS的核心思想是通过一系列新增的HTTP头信息来实现服务器和客户端之间的通信, 浏览器发送一个带有Orgin字段的HTTP请求头,用来表明请求来源。服务器的Access-Control-Allow-Origin响应头表明该服务器允许哪些源的访问,一旦不匹配,浏览器就会拒绝资源的访问。

浏览器会将CORS请求分为两种:简单请求、非简单请求 简单请求:

  • 请求方法只允许:GET,HEAD,POST
  • 对于请求头字段有严格的要求,一般情况下不会超过以下几个字段: Accept、Accept-Language、Content-Language、Content-Type
  • 当发起POST请求时,只允许Content-Type为application/x-www-form-urlencoded,multipart/form-data,text/plain。

对于前后端分离的项目,服务端和客户端通过RESUful Api通信时,dataType为json的ajax请求显然不是简单请求(Content-Type为application/json),浏览器对于非简单请求会先发送一个类型为options的预请求,options请求的作用是先测试下接口能否返回200,如果不是,则后面真正的get或post请求会丢弃。

针对非简单请求来说,由于每个请求都会发送预请求,这就导致接口数据的返回会有所延迟,时间被加长。所以,在使用CORS的过程中,可以采用一些方案来优化请求,将非简单请求转换成简单请求,从而提高请求的速度。