JsonP跨域问题研究

网上找了些关于Jsonp的文章看,写的都有点混乱。在这里好好整合下。


一、名词解释

百度百科 同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支JavaScript的浏览器都会使用这个策略。 所谓同源是指,域名,协议,端口相同。当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面当浏览器的百度tab页执行一个脚本的时候会检查这脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。

  • 同源策略限制了我们无法通过原生的XMLHttpRequest()对象获取到json数据。为了突破这个限制,一个有效简单的解决方案就是:jsonp。目前主流网站首选的是jsonp来跨域请求。

  • jsonp并非新的数据格式,而是解决JSON跨域获取的解决方案。通过JSONP获取到得数据已经不是JSON了,而是JS类型的数据(大部分是对象)。给的是一个回调函数,函数里是请求的数据组成的一层层对象。

  • 上网找过很多讲jsonp的文章,大部分都是讲的模模糊糊的。jsonp的原理其实不复杂: 1、浏览器的同源策略把跨域请求都禁止了; 2、HTML的<script>标签是例外,可以突破同源策略从其他来源获取数据;HTML里还有一个图像Ping技术,就是图像加载也是可以跨域的,这个主要是用于广告跟踪等。 3、由上可得,我们可以通过<script>标签引入jsonp文件,然后通过一系列JS操作获取数据。

    上面三点便是JSONP实现跨域的原理。


二、JSONP的原理概述

javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。

  • Jsonp个人的理解是后端向前端传输JSON格式数据一种方式,形如 callback({"name":"Joy","age":"22","gender":"male"})。callback("a") 这种虽然包裹的数据不是JSON格式的,但应该也算吧?

  • 实现JSONP的javascript callback的形式,本地脚本预先定义一个callback(data)函数,然后向支持jsonp的服务发起一个请求,服务器一般会返回callback(data)的形式,这样就变相获得并操纵数据。

  • 一般形式<scripts src="http://www.*.com?callback=callback"></script>

  • callback=?这个是正如其名表示回调函数的名称,也就是将你自己在客户端定义的回调函数的函数名传送给服务端,服务端则会返回以你定义的回调函数名的方法,将获取的json数据传入这个方法完成回调。

//添加<script>标签的方法

function addScriptTag(src) {
    var script = document.createElement('script');
    script.setAttribute("type", "text/javascript");
    script.src = src;
    document.body.appendChild(script);
}

window.onload = function() {
//将自定义的回调函数名result传入callback参数中
        addScriptTag("localhost/bns-relation/index.php?r=BnsRelation/BnsOfProd&callback=result");
}         回调函数的写法,自定义的回调函数result:

        
function result(data) {
//我们就简单的获取数据,然后对数据进行有效的处理
    console.log(data);
}

三、实现原理步骤

原理我们知道了,该怎么实现这些操作呢?上面我使用了原生方法的实现原理 接下来轮到jQuery登场!JQ已经帮我们封装好了 demo:

$.ajax({
    dataType:'jsonp',
    jsonp:'jsonp_callback',
    url:'http://www.baidu.com/xxx.jsonp',
    success:function(){
        //dosomthing
    }
});

以jQuery2.1.3的ajax方法为例

$.ajax({
    url:"",
    dataType:"jsonp",
    data:{
        params:""
        }
}).done(function(data){
    //dosomething..
})
  • 仅仅是客户端使用jsonp请求数据是不行的,因为jsonp的请求是放在script标签中的,和普通请求不同的地方在于,它请求到的是一段js代码,如果服务端返回了json字符串,那么浏览器就会报错。所以jsonp返回数据需要服务端做一些处理。比如找PH语言中处理传过去的数据,再议回调函数的形式返回。

服务端返回数据处理

现在前端写好了,还需要对服务器端程序做一些修改才可正确接收内容。由上面解释可知前端希望服务器端Jsop返回的应该是一段JS代码(JS函数),我们就构造一个JS函数 jsonpCallback(你在前端页面定义的函数)。现在可正确拿到服务端数据

@RequestMapping("rankTest")
    @ResponseBody
    public String rank()
    {
    	 Map map = new HashMap();
    	 map.put("a", 4);
    	 String str = JSON.toJSONString((map));  
   	 return "jsonpCallback(" + str + ")";
    }

 
 
四、每个函数都这样写太不方便,不便于接口的操作。还好Spring为我们提供了方便,修改默认的转换器MappingJackson2HttpMessageConverter 即可。
Step 1 : 添加JSONP转换器

public class JsonpHttpMessageConverter extends MappingJackson2HttpMessageConverter {
    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
        JsonGenerator jsonGenerator = this.getObjectMapper().getFactory().createJsonGenerator(outputMessage.getBody(), encoding);
        try {
            //ConfigContainer.JSONP_CALLBACK 为回调名称,如"callback"
            jsonGenerator.writeRaw(ConfigContainer.JSONP_CALLBACK);
            jsonGenerator.writeRaw('(');
            this.getObjectMapper().writeValue(jsonGenerator, object);
            jsonGenerator.writeRaw(");");
            jsonGenerator.flush();
        } catch (JsonProcessingException ex) {
            throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
        }
    }
}

step 2 : 添加配置

<mvc:annotation-driven>
      <mvc:message-converters register-defaults="true">
          <bean  class="...JsonpHttpMessageConverter" p:supportedMediaTypes="application/jsonp"/>
      </mvc:message-converters>
</mvc:annotation-driven>

前端页面测试:

$.ajax({
        type: <your type>,
        url: <your url>,
        dataType: 'jsonp',
        jsonpCallback: 'JsonpCallback', //这个值要与第一步的ConfigContainer.JSONP_CALLBACK同名
        contentType: 'application/jsonp;charset=UTF-8',
    }).done(function (result) {
        //TODO
    }).fail(function (result, textStatus, info) {
	//TODO
    });
}
    提示:在本地启动两个服务器,端口不一样,可模拟跨域访问。

    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值