JS跨域及Ajax实现

JS跨域问题

处理过WEB系统前端Ajax交互同事都会碰到一个问题,跨越问题,也就是说你从一个服务器请求数据,而页面并非来自这里。浏览器认为这是一个安全问题。

比如,如下代码,是我们经常写的Ajax请求数据代码,采用了JQuery框架。

 function showAmount(){

    $.ajax({

       type: "GET",

       url: "http://10.19.125.19:8080/sz/orderForm.do",

       data:{mobileNumber:'<%=mobile%>'},

       success: function(data){

         for(var i=0;i<data.length;i++){  

          var amount = data[i].amount;  

          $('#amount').append("<b>"+amount+"</b>");

          };  

       },

       error: function(){

        $('#amount').append("<b>无数据</b>");

       } 

    }); 

    }

这段程序在你的本地机器Html页面上运行是不会获取到任何数据,但是http://10.19.125.19:8080/sz/orderForm.do这个请求输入到浏览器中是可以返回数据的。同样的这个URL换成域名,比如www.suning.com,结果也是一样。这是为什么呢?如果是初次接触到,肯定会产生很大的疑惑。

其实这是浏览器确实对XMLHttpRequest HTTP请求有一些安全限制,导致了这些问题。这是浏览器策略,如果从某个域提供页面本身,安全策略要求不能从另一个域获取数据。

    JS可以接受的代码行为:

1首先浏览器从域名suning.com做出页面请求

 

 

2页面通过XMlHttpRequest做出对suning.com请求数据

 

 

JS代码不能够接受的行为:

1首先浏览器从域名suning.com做出页面请求

 

2页面通过XMLHttpRequest对另外一个域taobao.com获取数据,显然不能获取数据

 

  

我们现在知道了问题,同时也知道了为什么产生这个问题,那么接下来我们需要去寻找方法解决问题,有哪些方法呢?想到了谷歌和度娘。

通过寻找有两种解决方法,第一;将页面托管到taobao.com服务器当中,比如taobao.com/suning.html,suning.html是我们放入js代码的页面。第二;JSONP方式。第三;重构JqueryAjax代码。

第一种方案,显示不怎么实用,通常也不会采用此方法解决跨域问题。

接下来就是重点关注后两种方案。

什么是JSONP呢?为什么要用JSONP?怎么使用JSONP呢?

认识JSONP

JSONP 是一种基于JSON的方法,称为JSONP,全称为:JSON with PaddingJSONP是一种使用<script>标记获取JSON对象的方法,这也是一种获取数据的方法,可以避免XMLHttpRequest的同源安全问题。

当我们在HTML页面当中,嵌入<script></script>代码,例如:

<html>

<body>

<script src=”http://10.19.125.19:8080/sz/orderForm.do”></script>

</body>

</html>

这个scriptsrc是一个Web服务的URL,这个服务将为我们提供JSON对象。浏览器遇到页面上的<script></script>元素时,会向src指向的URL,发起HTTP请求。服务器会像对待其他的请求一样,处理并响应返回JSONJSON的返回形式是字符串。再由浏览器解析和解释,任何数据类型都会转换成真正的JavaScript对象和值,另外所有的代码被执行。

流程如下:

 

  JSONP最重要的是这个“P”,它可以理解成一个回调,当服务器返回JSON到浏览器,浏览器都会最终转换成JavaScript对象和值来解析和解释,并执行。那么如果服务器将JSON数据包裹到一个JavaScript函数内,作为一个函数参数返回到浏览器,只要页面存在同样的方法,那么浏览器就会去执行这个获取到JSON数据的函数,在函数当中,我们可以获取该JSON数据来处理,完成需要的业务。

流程如下:

 

实现JSONP

JSONP的基本流程了解之后,对于JSONP的实现也就能够得心应手了,目前对于JSONP的实现方式就是一般实现方式和Ajax实现方式。

一般实现方式

前端的JS代码

function jsonpCallback(adJsonDatas) {

loadContent=romancecpc(adJsonDatas);

var adDivObj=document.getElementById("sn_"+100001199);

if(adDivObj!=null){

adDivObj.innerHTML=loadContent;

}

};

function addScriptTag(src){

var script = document.createElement('script');

script.setAttribute("type","text/javascript");

script.src = src;

document.body.appendChild(script);

}

前端Html代码

<div class="navBar" id="sn_100001199"></div>

<script type="text/javascript">

function show(){

loadGoods("100001199");

}

function loadGoods(pid){

var kew = document.getElementById("keyword");

var val = kew.value;

var kw=to_utf8(val);

addScriptTag("http://10.19.250.51:8999/getCpcDatas?keyword="+kw+"&positionID="+pid);

}

</script>

后端代码Lua实现

Local tem= "{".."\"tid\":\""..temObject[1].."\",".."\"sid\":\""..temObject[2]..

"\",".."\"pid\":\""..temObject[3].."\",".."\"adSrc\":\""..temObject[4].."\","..

"\"title\":\""..temObject[5].."\",".."\"clickUrl\":\""..temObject[6].."\","..

"\"cmdCode\":\""..temObject[7].."\",".."\"adType\":\""..temObject[8].."\","..

"\"apsClickUrl\":\""..apsClickUrl.."\",".."\"cmdPrice\":\""..temObject[9].."\"}"

ngx.say("jsonpCallback("..tem..")")

后端代码Java实现:

Local tem= "{".."\"tid\":\""..temObject[1].."\",".."\"sid\":\""..temObject[2]..

"\",".."\"pid\":\""..temObject[3].."\",".."\"adSrc\":\""..temObject[4].."\","..

"\"title\":\""..temObject[5].."\",".."\"clickUrl\":\""..temObject[6].."\","..

"\"cmdCode\":\""..temObject[7].."\",".."\"adType\":\""..temObject[8].."\","..

"\"apsClickUrl\":\""..apsClickUrl.."\",".."\"cmdPrice\":\""..temObject[9].."\"}"

response.getWriter().write(“jsonpCallback(‘+ tem+’)”);

分析:

JS代码中,我们定义了一个jsonpCallback(adJsonDatas)函数,用于回调函数。同时通过函数addScriptTaga(src)动态的创建<script src=”” ></script>。前面提过,这个<script></script>会发起后端请求,获取后端的"jsonpCallback("..tem..")"数据。这个数据到达浏览器后,会作为Js代码来执行,执行的过程其实就是执行了JS代码中定义的jsonpCallback(adJsonDatas),同时参数为JSON格式。

在前端的HTML代码里面,就包含了一个DIV id= sn_100001199,完成向里面填写数据的功能。

在笔者的代码当中省略了callback=?这个请求参数,默认在后台直接确定了jsonpCallback()这个回调函数。

如果回调函数会经常发生变化,那么可以采用添加callback=jsonpCallback请求,后端获取callback的值,直接回写。

AJAX实现方式

前台(JQuery):

$.ajax({

url: this.localUrl,

type: "GET",

data: reDatas,

dataType: "jsonp",

cache:false,

jsonp:"jsonCallback",

jsonpCallback:"jsonpCallback",

success: function(datas,textStatus) {

if(textStatus=="success"){

if(null == datas || ""==datas){

//打印错误

AdManager.showError("get datas is null || \"\":"+datas);

return;

}

//渲染页面

AdManager.romanceAd(datas);

}

},

error:function(XMLHttpRequest, textStatus, errorThrown){

}

});

}

后端代码Lua实现

local tem= "{".."\"tid\":\""..temObject[1].."\",".."\"sid\":\""..temObject[2]..

"\",".."\"pid\":\""..temObject[3].."\",".."\"adSrc\":\""..temObject[4].."\","..

"\"title\":\""..temObject[5].."\",".."\"clickUrl\":\""..temObject[6].."\","..

"\"cmdCode\":\""..temObject[7].."\",".."\"adType\":\""..temObject[8].."\","..

"\"apsClickUrl\":\""..apsClickUrl.."\",".."\"cmdPrice\":\""..temObject[9].."\"}"

ngx.say("jsonpCallback("..tem..")")

分析:

Ajax的方式是我们比较常用的方式,因为目前的WEB系统,大量的采用了异步刷新的功能。同样的为了能够实现异步获取数据的功能,JSONP也采用Ajax的方式。红线部分是非常需要注意dataType: "jsonp", jsonp:"jsonCallback", jsonpCallback:"jsonpCallback", dataType是必须填写项,jsonp,jsonpCallback是可选的。

笔者在运行代码的时候,开始并未添加jsonp:"jsonCallback", jsonpCallback:"jsonpCallback"遇到了如下问题:

parsererror 是解析器错误,jQuery150019…. was not called  jQuery150019...此函数没被执行。那么为什么这个函数没被调用呢?

 jQuery150019…. 这个函数名的来由, 此是在跨域ajax时自动产生的 jsoncallback=?  就是加一个这个,然后后台代码里也返回这个参数的值就行了。

开始以为是JSON对象的格式有问题,但是检查了返回结果

发现返回的数据其实是正确的JSON格式。

后来将添加上了jsonp:"jsonCallback", jsonpCallback:"jsonpCallback"再运行代码显示结果就正常了。

经过了这个异常问题的修改,笔者想到了看JQuery源码,因为其实虽然解决了这个问题,但是知道为什么?


JQuery JSONP源码分析

获取JQuery源码

方式一:

http://jquery.com/

下载

方式二:

https://github.com/jquery/jquery

源码地址

源码的分析其实是一件比较痛苦的体验,很容易一头扎源码堆里面去,笔者选择了直接阅读JQuery Ajax的源码.但是前提条件必须要会使用JQuery。先搞清楚需要研究的部分所处的位置。

也可以参考博客

http://nuysoft.iteye.com/blog/1178483

JQuery的架构

1

JQuery JSONP

从图1中,我们可以看出,JSONP所处的位置在异步请求Ajax模块中。可以直接的找到这块代码阅读。

代码地址【最新的代码】:

https://github.com/jquery/jquery/blob/master/src/ajax/jsonp.js

也可以下载的老的版本阅读,笔者下载了1.7.1的版本阅读。经过阅读总结出程序流程

1,初始化默认的jsonp,jsonpCallback

// Default jsonp settings

jQuery.ajaxSetup({

jsonp: "callback",

jsonpCallback: function() {

return jQuery.expando + "_" + ( jsc++ );

}

});

jsonpCallback的默认的名为:JQuery_时间搓。

2,利用Ajax的前置过滤器,完成JSONP类型的过滤

jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

…..

}

参数 s:代表$.ajax对象

参数 originalSettings:1.7.1当中未用,新的版本用到

参数 jqXHR:JQuery包装的XMLHttpRequest对象

2.1判断该Ajax请求是否为表单请求,并且data属性存在并且有值

var inspectData = s.contentType === "application/x-www-form-urlencoded" &&

( typeof s.data === "string" );

2.2通过三种方式判断该Ajax请求处理是否是JSONP的请求

if ( s.dataTypes[ 0 ] === "jsonp" ||

s.jsonp !== false && ( jsre.test( s.url ) ||

inspectData && jsre.test( s.data ) ) ) {

}

方式一:dataTypes的第一值为JSONP;

方式二:jsonp存在并且url里面包含=?& =?$ 

方式三:该请求是表单请求并且data里面包含=?& =?$ 

2.3在请求的url或者data中添加上JSONP的请求参数callback=?

var responseContainer,

jsonpCallback = s.jsonpCallback =

jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,

previous = window[ jsonpCallback ],

url = s.url,

data = s.data,

replace = "$1" + jsonpCallback + "$2";

if ( s.jsonp !== false ) {

url = url.replace( jsre, replace );

if ( s.url === url ) {

if ( inspectData ) {

data = data.replace( jsre, replace );

}

if ( s.data === data ) {

// Add callback manually

url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;

}

}

}

2.4注册回调函数

// Install callback

window[ jsonpCallback ] = function( response ) {

responseContainer = [ response ];

};

Window[jsonpCallback]相当于在window对象中创建了一个新的属性函数。

当请求结束后,执行回调函数。

// Clean-up function

jqXHR.always(function() {

// Set callback back to previous value

window[ jsonpCallback ] = previous;

// Call if it was a function and we have a response

if ( responseContainer && jQuery.isFunction( previous ) ) {

window[ jsonpCallback ]( responseContainer[ 0 ] );

}

});

2.5转换返回值类型为JSON对象,数据类型为“JSON”,分发器类型为“SCRIPT

// Use data converter to retrieve json after script execution

s.converters["script json"] = function() {

if ( !responseContainer ) {

jQuery.error( jsonpCallback + " was not called" );

}

return responseContainer[ 0 ];

};

// force json dataType

s.dataTypes[ 0 ] = "json";

// Delegate to script

return "script";

3执行script 分发器

绑定script分发器,通过在header中创建script标签异步载入js

3.1动态创建<script></script>

script = document.createElement( "script" );

//设置成了异步请求

script.async = "async";

if ( s.scriptCharset ) {

script.charset = s.scriptCharset;

}

script.src = s.url;

3.2异步请求执行回调函数

script.onload = script.onreadystatechange = function( _, isAbort ) {

if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {

// Handle memory leak in IE

script.onload = script.onreadystatechange = null;

// Remove the script

if ( head && script.parentNode ) {

head.removeChild( script );

}

// Dereference the script

script = undefined;

// Callback if not abort

if ( !isAbort ) {

callback( 200, "success" );

}

}

};

分析:

综上,我们可以看出,其实JQueryJSONP处理,很简单,首先,拼接请求参数?Callback=?

接着,动态注册一个回调函数。然后,动态创建一个<script></script>异步发送请求,完成后,执行回调函数。


猜你喜欢

转载自blog.csdn.net/Cavalier520520/article/details/19703617