瓜籽儿的Blog

专注于JavaScript技术!努力用最简单的办法去解决最复杂的问题!

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

AJAX跨域请求之JSONP

Posted on 2010-05-01 11:45  瓜籽  阅读(2697)  评论(0编辑  收藏  举报

     AJAX自2004年前后兴盛起来一直就被大家所追捧着,生命力极其旺盛!AJAX的兴起也带了来一次互联网的革命,众多基于AJAX的网站一夜间都纷纷冒了出来。以GOOGLE为首的各大网站均开始了新一轮的技术竞争,GMIAL的出现使大家感受到了AJAX所带来的好处,尝到了甜头。他的异步请求,无页面刷新可以很直观的被大家看到操作后的一些效果;再也不用等在电脑前看着空白页面发呆了。俗话说的好:人无完人!何况是由人所开发的东西即是如此。AJAX也有一些自身的不足之处,大家可以随便从网上搜一下就可以看到很多关于AJAX的一些缺点,如:不能后退,请求进跨域等等。所谓网络上所说的AJAX七综罪!

     关于AJAX的事事非非,我不去评论。什么事情都有好的一面和坏的一面,这就要看大家去怎么理解和使用了!现在我要说的是关于AJAX跨域时所出现的问题。现在我们解决AJAX跨域时所用到的一些方法有:iframe,JSONP(script标签请求),flash,window.name属性进行跨域(我没有测试过,如果感兴趣可以看一下克军写的一个例子)。我接触的也不是很多,如果有哪里说的不全请大家多多谅解!

     下面就来说一下关于JSONP的跨域。JSONP即JSON with Padding。由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源。如果要进行跨域请求,我们可以通过使用html的script标记来进行跨域请求,并在响应中返回要执行的script代码,其中可以直接使用JSON传递javascript对象。这种跨域的通讯方式称为JSONP。很明显,JSONP是一种脚本注入(Script Injection)行为,需要特别注意其安全性。

     如果我们要用JSONP做一个跨域请求,需求如下:

     1、现在有两个站点,分别是:http://www.google.com/ 和 http://www.sina.com.cn

     2、现在我们要从google中访问sina中的某个请求

     客户端代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
<html xmlns="http://www.w3.org/1999/xhtml" >  
<head>  
    <title>Test Jsonp</title>  
    <script type="text/javascript">
	   // 事先定义好的回调函数  
            function jsonpCallback(result)  
            {  
            	alert(result.msg);  
            }  
        </script>  
    <script type="text/javascript" src="http://www.sina.com.cn/jsonServerResponse?jsonp=jsonpCallback"></script>  
</head>  
<body>  
</body>  
</html>

 

     此段代码中标签script是注入标签,其目的就是当<script>加入到页面后便开始利用其src属性对sina服务器端发起请求。其中请求地址中的“jsonp=jsonpCallback”就是回调函数(callback),当服务器端获取到请求地址信息后再将其参数“jsonp=”后的回调函数原封不动的写回页面的<script>标签中,但此时可以在回调函数中将其所需要的参数传入(json或是其他形式)。但是在回写此函数之前,这个回调函数要先被定义于页面否则会报错误!

     如果每次做AJAX跨域请求时都要这样做的话,那就也太麻烦了。能不能将这个回调函数一起写入某个JS文件呢?当在执行请求时,动态的在页面上进行如此的操作呢?答案是YES!下面就来看一个定义的JSONP方法:

// 建立JSON请求
var setJSONRequest = function(){
	var head = document.getElementsByTagName('head')[0];
	var script = document.createElement('script');
	var fun = this.setRandomFun();	// 获取随机函数名
	var _this = this;
	var param = '';

	// 拼接请求信息
	for(var i in this.param){
		if(param == ''){
			param = i+'='+this.param[i];
		}else{
			param+= '&'+i+'='+this.param[i];
		}
	}

	script.type = 'text/javascript';
	script.charset = 'utf-8';

	if(head){
		head.appendChild(script);
	}else{
		document.body.appendChild(script);
	}

	// data:为回调函数所需要传入的参数
	// 定义页面中的回调函数,如例子中的“jsonpCallback()”方法
	window._$JSON_callback[fun.id] = function(data){
		_this.callback.success(data);

		// 隔100毫秒后删除随机函数和script标签
		setTimeout(function(){
			delete window._$JSON_callback[fun.id];
			script.parentNode.removeChild(script);
		}, 100);
	};

	// 页面中载入些标签后,便开始通过src的属性向服务器端进行请求操作
	script.src = this.url+'?callback='+fun.name+'&'+param;
};

// 生成随机JSONP回调函数
var setRandomFun = function(){
	var id = '';

	do{
		id = '$JSON'+Math.floor(Math.random()*10000);
	}while(window._$JSON_callback[id])

	return{
		id : id,
		name : 'window._$JSON_callback.'+id
	}
};

 

     当执行JSONP请求时,此方法会自动在页面中生成一个用以请求服务的script标签;服务端接到此请求信息后再将callback回调函数写到页面并传入相应的参数,便完成了一次AJAX的跨域请求。同时又将刚才所定义的函数和script请求标签删除。这样的话,以后如果再需要JSONP请求时就不需要都在页面中定义N多的回调函数了,可以省去了不好体力活儿。每次请求时只需要将随机生成的回调函数以请求地址的形式一起传入服务器端,服务器端通过判断来再将回调写入页面来完成请求的回调。

     下面是一下相对完整的AJAX请求,其中包括了XHR和JSONP的请求判断:

// 定义AJAX跨域请求的JSON
(function(){
	if(typeof window.$JSON== 'undefined'){
		window.$JSON= {};
	};

	$JSON._ajax = function(config){
		config = config[0] || {};
		this.url = config.url || '';
		this.type = config.type || 'xhr';
		this.method = (this.type == 'json') ? 'GET' : config.method.toUpperCase() || 'GET';
		this.param = config.param || null;
		this.callback = config.callback || {};
		this.XHR = null;

		if(typeof window._$JSON_callback == 'undefined'){
			window._$JSON_callback = {};
		}

		this._createRequest();
	};

	$JSONP._ajax.prototype = {

		// 缓存XHR请求,再次再调用时不再进行判断
		_createXHR : function(){
			var methods = [
				function(){ return new XMLHttpRequest(); },
				function(){ return new ActiveXObject('Msxml2.XMLHTTP'); },
				function(){ return new ActiveXObject('Microsoft.XMLHTTP'); }
			];
			for(var i = 0, l = methods.length; i < l; i++){
				try{
					methods[i]();
				}catch(e){
					continue;
				}
				this._createXHR = methods[i];
				return methods[i]();
			}
		},

		// 建立XHR请求
		_createRequest : function(){
			return (this.type == 'json') ? this._setJSONRequest() : this._setXHRRequest();
		},

		_setXHRRequest : function(){
			var _this = this;
			var param = '';

			for(var i in this.param){
				if(param == ''){
					param = i+'='+this.param[i];
				}else{
					param+= '&'+i+'='+this.param[i];
				}
			}

			this.XHR = this._createXHR();
			this.XHR.onreadystatechange = function(){
				if(_this.XHR.readyState == 4 && _this.XHR.status == 200){
					_this.callback.success(_this.XHR.responseText);
				}else{
					_this.callback.failure('重新操作');
				}
			};

			this.XHR.open(this.method, this.url, true);
			this.XHR.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=utf-8");
			this.XHR.send(param);
		},

		// 建立JSON请求
		_setJSONRequest : function(){
			var head = document.getElementsByTagName('head')[0];
			var script = document.createElement('script');
			var fun = this._setRandomFun();
			var _this = this;
			var param = '';

			for(var i in this.param){
				if(param == ''){
					param = i+'='+this.param[i];
				}else{
					param+= '&'+i+'='+this.param[i];
				}
			}

			script.type = 'text/javascript';
			script.charset = 'utf-8';
			if(head){
				head.appendChild(script);
			}else{
				document.body.appendChild(script);
			}

			// data:为回调函数所需要传入的参数
			// 定义页面中的回调函数,如例子中的“jsonpCallback()”方法
			window._$JSON_callback[fun.id] = function(data){
				_this.callback.success(data);
				setTimeout(function(){
					delete window._$JSON_callback[fun.id];
					script.parentNode.removeChild(script);
				}, 100);
			};

			script.src = this.url+'?callback='+fun.name+'&'+param;
		},

		// 生成随机JSON回调函数
		_setRandomFun : function(){
			var id = '';
			do{
				id = '$JSON+Math.floor(Math.random()*10000);
			}while(window._$JSON_callback[id])
			return{
				id : id,
				name : 'window._$JSON_callback.'+id
			}
		}
	};
	window.$JSON.ajax = function(){
		return new $JSON._ajax(arguments);
	}
})();

 

     在调用此函数进行执行时,只需new一个ajax()函数出来并传入相应参数即可。

var ajax = new $JSON.ajax({
	url : 'http://www.sina.com.cn/xxx.jsp',
	type : 'json',
	method : 'get',
	callback : {
		success : function(info){
			alert(info);
		},

		failure : function(error){
			alert(errow);
		}
	}
});

 

      通过初始化“$JSON.ajax()”函数便完成了一个JSONP的异步跨域请求,这只是一个简单的实现。更复杂的内容就需要大家来进行挖掘了。