那时候刚好下着雨,柏油路面湿冷冷的,还闪烁着青、黄、红颜色的灯火。



Ajax跨域访问


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_methodOPTIONS 的话,咱们设置一下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 可以很有效的解决这类问题,具体的我也没实验过,有机会再写吧,先这样。

上面还有忘写的吗?中间被打断过多次...


 标签 , TAG , 啦啦