Ajax跨域访问
众所周知道,跨域都一个让前端大湿们很头疼的一个问题。JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。
什么是跨域
简单来说就是不同网站之间相互访问,比如A网站通过Ajax
获取B网站的内容,这就算是跨请求。请求子域名也算是跨域请求了,解决方法也有很多。
[TOC]
怎么样才算跨域
下表列出请求方式
比如在当前网站: http://lattecake.com/
下一个js发起请求
| URL | 说明 | 是否可通信 |
|---|---|---|
| http://lattecake.com/learn/ | 同一域下不同目录或文件 | 是 |
| http://www.lattecake.com/learn/ | 这种情况也算是子域名 | 否 |
| http://lattecake.com:8000/learn/ | 同一域下不同端口 | 否 |
| https://lattecake.com/learn/ | 同一域下不同协议 | 否 |
| http://127.0.0.1/learn/ | 域名所对应的ip | 否 |
| http://photo.lattecake.com/learn/ | 子域名 | 否 |
| http://dudulu.me/learn/ | 不同域名 | 否 |
如果前后端分离开发的话以上这些情况前端是无能为力的,为了解决这种问题,请往下面看。
如何解决这类问题
通常咱们为了什么ajax跨域有以下几种方法
- 基于
iframe
来实现(讨厌iframe
所以这里不演示) - 使用
jsonp
的方式 - 通过HTML5里的
window.postMessage
进行传输 - 设置Nginx代理来实现跨域请示允许
web sockes
的方式
Iframe
iframe
比较讨厌,说好的不演示还是写了……
lattecake.com/post/20091
<!DOCTYPE HTML><html><head> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta charset="UTF-8"> <title>post 20091</title></head><body><iframe src="http://photo.lattecake.com/photo/exif/02762910-823-8368-9259-071970843233" id="iframe"></iframe><script>document.domain = 'http://photo.lattecake.com/';var ifr = document.getElementById("iframe");var doc = ifr.contentDocument || ifr.contentWindow.document;console.log(doc);</script></body></html>
不建议这么使用
JsonP
如果确定以后确实是布在不同域下的话可以考虑这种方式,这种方式也比较简单,不过我不太喜欢!推荐指数一般
来看jsonP的方式,以jQuery
为例:
jQuery
$(function(){ $.ajax({ type:'get', url: 'http://photo.lattecake.com/photo/exif/02762910-823-8368-9259-071970843233', dataType: 'jsonp', jsonp: "jsoncallback", success: function(data) { console.log(data); }, error : function() { alert('fail'); } }, 'json'); })
需要服务器返回
jsoncallback
函数,也就是”jsonp” 参数里定义的那个函数。
Ext JS
Ext.Ajax.cors = true; Ext.Ajax.useDefaultXhrHeader = false; Ext.define('User', { extend: 'Ext.data.Model', fields: ['empid', 'name', 'email'] });var myStore = Ext.create('Ext.data.Store', { model: 'User', autoLoad:true, proxy: { type: 'jsonp', url: 'http://photo.lattecake.com/photo/exif/02762910-823-8368-9259-071970843233', }, listeners:{ 'load':function( store, records, successful, eOpts ){ console.log(records); } } });
window.postMessage
具体我也没用过,大概的访问依然是ifram客户端与客户端之间进行通信
参考: http://www.webhek.com/window-postmessage-api
Nginx CORS 代理
当然我觉得还是这种方式最方便,你原来js该咋写现在还咋写,不用变化,跨域问题交给nginx去处理。
下面是nginx的部伪代码
location ~ ^/(app_dev|config)\.php(/|$) { if ( $request_method = OPTIONS) { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Methods' 'OPTION, POST, GET'; add_header 'Access-Control-Allow-Headers' 'X-Requested-With, Content-Type'; add_header 'Access-Control-Allow-Headers' "Authorization"; add_header 'Content-Length' 0; add_header 'Content-Type' text/plain; return 200; } fastcgi_index app_dev.php; fastcgi_split_path_info ^(.+\.php)(/.*)$; try_files $uri $uri/ app_dev.php /app_dev.php$is_args$args; include /etc/nginx/fastcgi_params; fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $request_filename; fastcgi_param APP_ENV dev; }
如果$request_method
为 OPTIONS 的话,咱们设置一下header 头,并返回状态。
也可以 $http_origin ~ <允许的域(正则匹配)>
进行匹配。
我把它定义在app_dev.php
块中,只在当请求访问lattecake.com/app_dev.php
时才会进这个区间,也就是开发的时候走这个区间,产环境会把前端与后端合并,所以生产环境并不存在跨域问题,所以我觉得这种方式比较好。
Access-Control-Allow-Origin
Access-Control-Allow-Origin
这个是设置你允许哪些域下的来源可以通过请求。
关于OPTIONS
ExtJS
的伪代码
var form = this.getView().getForm('auth-dialog');if (!form.isValid()) { Ext.Msg.alert("错误", "请输入用户名密码!"); return; }var me = this; form.submit({ clientValidation: true, method: 'POST', url: 'http://redis.lattecake.local/app_dev.php/login_check', waitMsg: '正在验证...', type: 'json', success: function (form, action) { if (action.response && action.response.responseText) { var response = Ext.decode(action.response.responseText); if (response.success) { me.redirectTo('dashboard', true); } } }, failure: function (form, action) { var message = "请求错误!"; if (action.response && action.response.responseText) { var response = Ext.decode(action.response.responseText); message = response.message; // window.BaseInfo._csrf_token = response.token; } Ext.Msg.alert("错误", message); Ext.toast('可以打开控制台看看啥错误!'); } });
一般前端框架比如”ExtJS“、”AngularJS“, 框架监测到访问的域名可能存在跨域的话会先发送一个OPTIONS
请求,验证是否可进行通信,如果返回可通信才会真正发起一个POST、GET请求。
下图是框架发起的OPTIONS请求,当如果服务器的Nginx并没有设置允许跨域请示的时候,它会返回一个405状态码。

当我们设置了跨域后重新发起请示,框架会发出三个请求,这个”GET
“是什么鬼?我也不知道,按理来说应该只会发一个POST
才对呀!难道是因为我设置了”'Access-Control-Allow-Methods' 'OPTION, POST, GET';
“吗?

下图是第一次发送的”OPTIONS
“请示所返回的header头信息,状态是200

当接收到200状态后,又发起了一次”GET
“请求,返回状态是302

在GET
完事后发送”POST
“请求,并提交相关数据!

这里需要注意
返回的Response
必须加上Access-Control-Allow-Origin
并设置允许的网站,否则会报以下错误。

PHP伪代码
return new JsonResponse($data, 400, ['Access-Control-Allow-Origin' => '*']);
某些情况下推荐,如果生产环境对指定域名比较信任的话可以考虑用这种方式。
web socket
听说使用 web socket 可以很有效的解决这类问题,具体的我也没实验过,有机会再写吧,先这样。
上面还有忘写的吗?中间被打断过多次…




