Jsonp跨域请求

来源:互联网 发布:sql 多个结果合并 编辑:程序博客网 时间:2024/06/04 19:58
什么是jsonp?为什么要用jsonp?
JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的<script> 元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。

以上是百度百科对于jsonp的解释,所谓"同源"指的是"相同协议相同端口"。

例如:h ttp://abc.com/test1 与以下url做同源检测

h ttps://abc.com/test2不同源(协议不同 http / https)

h ttp://abc.com:81/test3 不同源(端口不同 80 / 81)

h ttp://abc.com/test4 同源

注:ie"同源策略"不包括端口,即与 h ttp://abc.com:81/test3 也为同源


跨域请求的原理

我们做一个简单的测试!

我们在 h ttp://169.254.200.238:8020/jsonp/index.html下向

h ttp://169.254.200.238:8080/jsonp.do发起请求,

 $.get("http://169.254.200.238:8080/jsonp.do", function (data) {     console.log(data); });

此时浏览器抛出异常

因为两者的端口号分别为8080、8020 并不同源,从报错中也可以看出。

但是,我们换一种方式请求:

 <script type="text/javascript" src="http://169.254.200.238:8080/jsonp.do">  </script>

可以看到,此时同样的请求确成功了!由此,我们可以得出<scrpit>可以进行跨域请求,这是jsonp的基础,但是浏览器同样抛出了语句不合法的异常,

那是因为我们请求的数据会立马被浏览器当作javascript语句去执行(谁让我们用<script>去请求数据呢),但是请求到的数据格式并不符合其语法规范。

那么,如何解决这一问题呢?

如果我们返回的内容符合javascript的语法规范呢?

所以,我们把请求的数据当作一个函数的参数,并且这个函数在客户端存在的话,那么这就行得通。

例如:请求返回的数据为

 callback( {"result":"success"} )

其中{"result":"success"} 是我们想要获取的数据,浏览器会立即执行callback这个函数,此时,我们已经定义好了函数名为callback这个函数:

function callback(data){    // data为返回数据     // TODO 解析数据}
这样是不是一切都说的通了!

所以jsonp跨域请求的关键就在于:

服务端要在返回的数据外层包裹一个客户端已经定义好的函数


ajax跨域请求实例

理解了上述内容,你就已经掌握了跨域请求的原理,那么下面我们将利用ajax发起跨域请求,服务端通过spring MVC处理jsonp请求。

//通过JQuery Ajax 发起jsonp请求(注:不是必须通过jq发起请求 ,      例如:Vue Resource中提供 $.http.jsonp(url, [options]))$.ajax({    // 请求方式    type: "get",     // 请求地址    url: "http://169.254.200.238:8080/jsonp.do",     // 标志跨域请求    dataType: "jsonp",    // 跨域函数名的键值,即服务端提取函数名的钥匙(默认为callback)    jsonp: "callbackparam",       // 客户端与服务端约定的函数名称    jsonpCallback: "jsonpCallback",    // 请求成功的回调函数,json既为我们想要获得的数据    success: function(json) {        console.log(json);    },    // 请求失败的回调函数    error: function(e) {alert("error");    }});
@RequestMapping({"/jsonp.do"})public String jsonp(@RequestParam("callbackparam") String callback){    // callback === "jsonpCallback"return callback + "({\"result\":\"success\"})";}

此时,客户端接收到的返回值为:

这就是一个完整的跨域请求,但是这样就结束了吗?

细心的同学可能会发觉,现在如果不通过跨域去请求jsonp.do这个接口,不是就报错了吗?

是的,现在这个接口仅仅只能被携带callbackparam这个参数的请求。

为了解决这个问题,我们要判断请求的来源,

return  (request.from === jsonp) ? callback(data) : data ;

这才是我们想要的结果,解决这个问题需要我们对每个接口都做判断,或者通过AOP等等方式实现统一处理,这样做好像并不优雅。

在spring4.2以上的版本,支持了CORS(跨域资源共享)


CORS

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。


它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

为什么说它优雅呢?

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。 只要服务器实现了CORS接口 ,就可以跨源通信。

所以我们客户端可以像什么都没发生一样,依旧漠不关心的发送我们的请求:

// 不用担心跨域问题$.ajax({    // 请求方式    type: "get",     // 请求地址    url: "http://169.254.200.238:8080/jsonp.do",     // 此时依然请求json格式数据 而非jsonp    dataType: "json",    // 请求成功的回调函数    success: function(json) {        console.log(json);    },    // 请求失败的回调函数    error: function(e) {alert("error");    }});

而我们用spring MVC实现的服务端也出乎意料的简洁(基于xml):

// spring配置文件 spring必须为4.2以上版本<mvc:cors>      <mvc:mapping path="/**" /></mvc:cors>

注意:通过以上两步我们已经完成了跨域请求操作。

这里,我对整个项目添加了跨域支持 , path 为支持跨域的路径 , 同样你可以在这里做详细配置:

<mvc:cors>    <mvc:mapping path="/api/**"        allowed-origins="http://domain1.com, http://domain2.com"        allowed-methods="GET, PUT"        allowed-headers="header1, header2, header3"        exposed-headers="header1, header2" allow-credentials="false"        max-age="123" />    <mvc:mapping path="/resources/**"        allowed-origins="http://domain1.com" /></mvc:cors>

当然,spring也支持通过java的方式进行配置:

// 此种方式等同于xml全局配置@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter {    @Override    public void addCorsMappings(CorsRegistry registry) {        registry.addMapping("/**");    }}// 此种方式为详细配置@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter {    @Override    public void addCorsMappings(CorsRegistry registry) {        registry.addMapping("/api/**")            .allowedOrigins("http://domain2.com")            .allowedMethods("PUT", "DELETE")            .allowedHeaders("header1", "header2", "header3")            .exposedHeaders("header1", "header2")            .allowCredentials(false).maxAge(3600);    }}

spring 也允许针对某个controller类 或者 方法进行 跨域 ,通过@Configuration注解完成。

这里就不做详细解释,详情可以参考

SpringMvc解决跨域问题 - 王念博客 - 开源中国社区



编辑于 2016-12-15