zoukankan      html  css  js  c++  java
  • Web 跨域请求

    在前端开发过程中,难免和服务端产生数据交互。一般情况我们的请求分为这么几种情况:

      1. 只关注发送,不关注接收
      2.不仅要发送,还要关注服务端返回的信息

          a.  同域请求
          b.  跨域请求

      所谓 跨域,一般情况下为三种情况:跨协议、跨子域、跨域名。下面距离梳理一下这三种情况。

    跨协议:比如说我现在的域名地址是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的值。服务端返回数据形式为:

    <script>window.name={errno:0, errmsg:'ok'}</script>

    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,然后服务端返回类似这样的内容:
    <script>location.href='http://www.12306.cn/proxy.html?func=callback&errno=0&errmsg=ok'</script>

    然后proxy文件,接收到这些参数,进行处理,转化成类似与jsonp的返回值,执行页面上的回调。这种情况是可以发送post请求的。
    proxy文件的内容,如下:

    <!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 i 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的,解决跨域的方法。

    $(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 i 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;
    
    });
    这样我们就可以通过一下方式进行使用:
    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);
    	});
    
    至此就可以做到跨域请求了



  • 相关阅读:
    读书笔记之理想设计的特征
    一些javascript 变量声明的 疑惑
    LINQ 使用方法
    Google MySQL tool releases
    读书笔记之设计的层次
    EF之数据库连接问题The specified named connection is either not found in the configuration, not intended to be used with the Ent
    转载 什么是闭包
    javascript面向对象起步
    Tips
    数据结构在游戏中的应用
  • 原文地址:https://www.cnblogs.com/wisdo/p/4267740.html
Copyright © 2011-2022 走看看