跨域請求解決方案


    在前端開發過程中,難免和服務端產生數據交互。一般情況我們的請求分為這么幾種情況:
  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的值。服務端返回數據形式為:
        
<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,然后服務端返回類似這樣的內容:
        
<script>location.href='http://www.12306.cn/proxy.html?fun=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);
    });

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM