跨域请求解决方案

 在前端开发过程中,难免和服务端产生数据交互。一般情况我们的请求分为这么几种情况:
  1. 只关注发送,不关注接收
  2. 不仅要发送,还要关注服务端返回的信息
    • 同域请求
    • 跨域请求
    上面提到了一个概念,我们这里简单做一下讲解。什么叫做跨域?一般情况下,跨域分为三种情况:跨协议、跨子域、跨域名。下面距离梳理一下这三种情况。
  • 跨协议:比如说我现在的域名地址是http://www.12306.cn,有一些请求需要发送到https://www.12306.cn,此时这个请求相对与http://www.12306.cn来说就是跨协议的请求
  • 跨子域:比如说我现在是http://www.12306.cn,在登录的时候需要请求http://passport.12306.cn这个接口,此时这个请求就是跨子域的请求
  • 跨域名:我们都知道现在的12306添加新的常用联系人的时候需要验证身份证号码,据说校验身份证号和姓名是否匹配只有公安部才有这个接口,并且使用费用相当高(扯远了),此时需要请求公安部的服务接口,此时从12306发出的请求就需要跨域名了
我们需要知道的是,跨域请求只要满足这是三种情况之一就会被认定为跨域请求。
 
    目前流行比较广的跨域请求解决方案有:window.name、document.domain、服务端代理、jsonp、前端代理。
 
    以下介绍的方式只是实现原理,没有过多考虑安全性,可以根据自己的情况进行选择。
 
  • window.name
        前端发送一个请求给隐藏的iframe,然后服务端每次将返回值,以js形式返回,然后iframe的父窗口获取window.name的值。服务端返回数据形式为:
        
1
<script>window.name= '{errno:0, errmsg:' ok '}' </script>

  2014-11-18:

  @pyzy:window.name的值必须是字符串,需要使用JSON.stringify处理传递,获取之后可以用JSON.parse处理进行使用

  • document.domain
        这个使用限制条件较多,必须是不同子域间,协议和端口号必须相同。比如:a.12306.cn和b.12306.cn之间相互操作,可以分别在两个域名下定义:document.domain = '12306.cn'; 这样就实现了跨子域通信。
  • 服务端代理
        这种情况不适合跨协议通信,比较适合跨端口和跨域名。这个前端基本上相当于普通的请求,我们所要访问的接口都经过服务端的代理,我们访问的请求都是本域的。
  • jsonp请求
        我们用的比较多,原理就是在发起请求时,动态的在页面加载一个script标签,因为src可以接收跨域资源,然后这个script标签的资源是执行一个js方法,并且将服务端返回的数据作为参数传递过来。这种情况唯一的缺点就是只能发送get请求,不适用用post请求。jsonp返回的数据格式为:
    callback({errno:0,errmsg:'ok'});
  • 前端代理
        和window.name的实现比较类似,将请求发送到一个隐藏的iframe,然后服务端返回类似这样的内容:
        

  

        然后proxy文件,接收到这些参数,进行处理,转化成类似与jsonp的返回值,执行页面上的回调。这种情况是可以发送post请求的。
 
        proxy文件的内容,如下:
       
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!doctype html>
<html><head><meta http-equiv= "Content-Type"  content= "text/html; charset=utf-8"  /></head><body><script type= "text/javascript" >
( function (){
   var  queryStr = location.search.substring(1).split( '&' ),oneQueryStr,args = {},g = parent,scope = parent ,callback;
   for ( var  in  queryStr){
    oneQueryStr = queryStr[i].split( '=' );
    if (!callback && oneQueryStr[0] ==  'fun' ){
     callback = oneQueryStr[1];
    };
    if (oneQueryStr[0]&&oneQueryStr[1]){
     args[oneQueryStr[0]] = (oneQueryStr[1]|| '' ).replace(/[>< '"{}]/g, ' ');
    }
  
   }
   callback = callback.split(' . ');
  
   if( callback[0] === ' document '
    || callback[0] === ' location '
    || callback[0] === ' alert'){
   } else {
    for ( var  i = 0,len= callback.length;i<len;i++){
     if (i==0 && callback[0]=="parent "){
      g = parent;
      scope = parent;
     }else if(i==0 && callback[0]==" top"){
      g = top;
      scope = top;
     } else {
      if (i<len-1){
       scope = scope[callback[i]];
      }
      g = g[callback[i]];
     }
    }
  
    g.call(scope,args);
   }
  
})();
</script>
</body></html>
 
    通过上面的介绍,简单的知道了处理跨域请求的一些方法,下面整理了一个基于jquery的,解决跨域的方法。
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
$( function () {
     'use strict' ;
 
     /**
      * 交互类
      * @param {object} param 要提交的数据
      * @param {Object} [ajaxOpt] ajax配置
      * @param {boolean} [https=false] 是否使用https协议
      * @constructor
      */
     var  Sync =  function (param, ajaxOpt, https) {
         var  protocol =  this .protocol = https ?  'https' 'http' ;
 
         var  ajaxOptDefault = {
             url: protocol +  '://' +location.host,
             type:  'GET' ,
             dataType:  'jsonp' ,
             timeout: 20000
         };
 
         param = string2JSON(param) || {};
 
         this .protocol = protocol;
         this .param = $.extend({}, param, ajaxOpt);
         this .ajaxOpt = $.extend({data:  this .param}, ajaxOptDefault);
         this .HOST = protocol +  '://' +location.host;
     };
 
     function  string2JSON(str){
         if ($.isPlainObject(str)) {
             return  str;
         }
         var  params = {};
         var  str2Arr = str.split( '&' );
         $.each(str2Arr,  function (i, keyVal) {
             var  arr = keyVal.split( '=' );
             //去除serialize把空格转成+
             params[arr[0]] = decodeURIComponent(arr[1]).replace(/[\+]+/,  '' ).replace(/[\+]+/,  '' );
         });
         return  params;
     }
 
     $.extend(Sync.prototype, {
         /**
          * 通过get方式(jsonp)提交
          * @param {String} [url] 请求链接
          * @return {Object} promise对象
          */
         get:  function (url) {
             var  self =  this ;
             var  send = $.ajax(url,  this .ajaxOpt);
             return  send.then( this .done,  function (statues) {
                 return  self.fail(statues);
             });
         },
         /**
          * 通过post方式提交,依赖psp_jump.html文件
          * @param {String} [url] 请求链接
          * @return {Object} promise对象
          */
         post:  function (url) {
             var  deferred = $.Deferred();
             var  timer =  null ;
             var  guid = parseInt( new  Date().getTime().toString().substr(4), 10);
             var  funName =   'QBXJsonp'  + guid;
             var  formName =  'QBXForm'  + guid;
             var  iframeName =  'QBXIframe'  + guid;
 
             // iframe 不能使用attr('name', xxx) 否则在ie7上添加的不是name属性而是submitName
             var  iframe = $( '<iframe name="'  + iframeName + '">' ).hide();
             var  param = $.extend({},  this .param, {proxy:  this .HOST+ '/proxy.html' , callback: funName});
             var  form = buildForm(param, {name: formName, target: iframeName, url: url ||  this .ajaxOpt.url});
 
             window[funName] =  function  (data){
                 clearTimeout(timer);
                 var  value;
                 for  ( var  in  data) {
                     if  (data.hasOwnProperty(i)) {
                         value = decodeURIComponent(data[i]);
                         if  (value.match(/^(\{.*\})|(\[.*\])$/)) {
                             value = $.parseJSON(value);
                         }
                         data[i] = value;
                     }
                 }
                 deferred.resolve(data);
             };
 
             timer = setTimeout( function (){
                 deferred.reject({
                     errno: 999999,
                     errmsg:  '网络超时,请稍后重试'
                 });
             },  this .ajaxOpt.timeout);
 
             // do some clear
             deferred.always( function (data) {
                 $(iframe).remove();
                 $(form).remove();
                 // IE8以下不支持delete window属性
                 try  {
                     delete  window[funName];
                 catch  (e){
                     window[funName] =  null ;
                 }
             });
             $(document.body).append(iframe).append(form);
             $(form).submit();
             return  deferred.then( this .done);
         },
         /**
          * 收到响应时默认回调
          * @param {Object} data 数据
          * @return {Object}
          */
         done:  function  (data) {
             var  deferred = $.Deferred();
             if  (data.errno == 0) {
                 deferred.resolve(data);
             else  {
                 deferred.reject(data);
             }
             return  deferred.promise();
         },
         /**
          * 未收到响应时默认回调
          * @param {Object} error 错误信息
          * @return {Object}
          */
         fail:  function (error) {
             var  deferred = $.Deferred();
             deferred.reject({
                 errno: 999999,
                 errmsg:  '网络超时,请稍后重试'
             });
             return  deferred.promise();
         }
     });
 
     /**
      * 把数据对象转成form元素
      * @param {Object} data json数据
      * @param {Object} opts form所需参数或其他数据
      * @return {Object}
      */
     function  buildForm(data, opts) {
         if  (opts.url) {
             opts.action = opts.url;
             delete  opts.url;
         }
         opts.method =  'post' ;
         var  $form = $( '<form>' ).attr(opts).hide();
         $.each(data,  function  (name, value) {
             $( '<input>' ).attr({type:  'hidden' , name: name, value: value}).appendTo($form);
         });
         return  $form[0];
     }
     
     window.Sync = Sync;
 
});
 
这样我们就可以通过一下方式进行使用:
1
2
3
4
5
6
7
8
9
10
11
12
var  login =  function (data) {
     var  sync =  new  Sync(data);
     return  sync.post(sync.HOST+ '/login' );
}
  
login({username:  'blackMao' , password:  'blackMao' })
     .done( function () {
         alert( '登陆成功!' );
     })
     .fail( function (error) {
         alert(error.errmsg);
     });

  至此就可以做到跨域请求了~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值