jsonp详解
1. 什么是jsonp?
JSONP(JSON with Padding)是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。
2.JSONP有什么用?
由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而可以解决跨域的数据请求。
3. jsonp是怎么产生的?
一个众所周知的问题,Ajax直接请求普通文件存在跨域无权限访问的问题,甭管你是静态页面、动态网页、web服务、WCF,只要是跨域请求,一律不准;
不过我们又发现,Web页面上调用js文件时则不受是否跨域的影响(不仅如此,我们还发现凡是拥有”src”这个属性的标签都拥有跨域的能力,比如<script>、<img>、<iframe>);
于是,我们可以判断,当前阶段如果想通过纯web端(ActiveX控件、服务端代理、属于未来的HTML5之Websocket等方式不算)跨域访问数据就只有一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理;
恰巧,我们已经知道有一种叫做JSON的纯字符数据格式可以简洁的描述复杂数据,更妙的是JSON还被js原生支持,所以在客户端几乎可以随心所欲的处理这种格式的数据;
这样子解决方案就呼之欲出了,web客户端通过与调用脚本一模一样的方式,来调用跨域服务器上动态生成的js格式文件(一般以JSON为后缀),显而易见,服务器之所以要动态生成JSON文件,目的就在于把客户端需要的数据装入进去。
客户端在对JSON文件调用成功之后,也就获得了自己所需的数据,剩下的就是按照自己需求进行处理和展现了,这种获取远程数据的方式看起来非常像AJAX,但其实并不一样。
为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
4. Jsonp原理:
1)首先在客户端注册一个callback, 然后把callback的名字传给服务器;
2)服务器先生成 json 数据;
3)服务端以 javascript 语法的方式,生成一个function , function 名字就是参数 jsonp中定义的名字;
4)服务端最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端;
5)客户端浏览器解析script标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里(动态执行回调函数)。
5. jsonp用法:
先来看几个示例:
示例1:
客户端jsonp1.html(所有测试文件都放在根目录WEB-INF下):
<!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></title> <script type="text/javascript" src="http://172.19.137.52:8080/rm-admin/remote1.js"></script> </head> <body> </body> </html>服务端remote1.js(远程服务端的url:http://172.19.137.52:8080/rm-admin)
alert("我是远程服务器");运行结果:
在客户端浏览器中弹出提示框:我是远程服务器。
示例2:
客户端jsonp2.html:
<!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"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <head> <title></title> <script type="text/javascript"> var localHandler = function(data){ alert("本地函数远程调用remote2.js,获取到的数据是:"+data.result); } </script> <!-- 注意:JavaScript的链接,必须在function的下面。 --> <script type="text/javascript" src="http://172.19.137.52:8080/rm-admin/remote2.js"></script> </head> <body> </body> </html>
服务端remote2.js
localHandler({"result" : "我是远程服务器的数据"});
运行结果:
在客户端浏览器中弹出提示框:我是远程服务器的数据。
示例3(jsonp的原生写法):
客户端jsonp3.html:
<!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"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <head> <title></title> <script type="text/javascript"> var flightHandler = function(data){ alert("查询到的航班结果是:票价为"+data.price+"元,余票为"+data.tickets+"张。"); } // 动态生成调用服务端的js脚本 // 远程服务端url var url = "http://172.19.137.52:8080/rm-admin/flight/result.htm?code=CA1401&callback=flightHandler"; // 创建scrip标签,并设置src属性 var script = document.createElement("script"); //script.type = "text/javascript"; //script.src = url; // 也可以这么写: script.setAttribute('type', "text/javascript"); script.setAttribute('src', url); // 将script标签加入到header document.getElementsByTagName('head')[0].appendChild(script); </script> </head> <body> </body> </html>
服务端FlightController.java:
package com.cnsuning.rm.admin.web.controller; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; /** * 航班管理 Controller * * @author guwq */ @Controller @RequestMapping("/flight") public class FlightController extends BaseController { @RequestMapping(value = "/result", method = RequestMethod.GET) @ResponseBody public void result(HttpServletRequest request, HttpServletResponse response) { StringBuffer result = new StringBuffer(); // 打印获取到的参数 String code = request.getParameter("code"); String callback = request.getParameter("callback"); System.out.println("code==="+code); System.out.println("callback==="+callback); // 拼接jsonp返回结果,在实际情况中,返回结果由后台获取 //result = "flightHandler({'price' : '1000', 'tickets' : '50'});"; result.append("flightHandler"); result.append("({"); result.append("\"price\":").append("1000").append(","); result.append("\"tickets\":").append("50"); result.append("})"); // 注意ContentType类型一定要是application/x-javascript response.setContentType("application/x-javascript;charset=UTF-8"); PrintWriter out = null; try { out = response.getWriter(); out.println(result); out.flush(); out.close(); } catch (IOException e) { logger.error("setResponse IOException" + e.getMessage(), e); } finally { if(out != null){ out.close(); } } } }运行结果:
在客户端浏览器中弹出提示框:查询到的航班结果是:票价为1000元,余票为50张。
示例4(jsonp的ajax写法):
客户端jsonp4.html:
<!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"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <head> <title></title> <script type="text/javascript" src="http://code.jquery.com/jquery-1.8.2.js"></script> <script type="text/javascript"> $(function(){ // 使用ajax来调用jsonp $.ajax({ type: "get", //jsonp默认为get请求,即使写post也会转换成get方式 async: false, // jsonp默认为false,即使写true也会转换成false url: "http://172.19.137.52:8080/rm-admin/flight/result.htm", // 服务端地址 data: {"code" : "CA1405"}, // 入参 dataType: "jsonp", // jsonp调用固定写法 jsonp: "callback", // 传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)。即:?callback=xxx中的callback部分 jsonpCallback:"flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据。即:?callback=xxx中的xxx部分 success: function(data){ // 调用成功之后的方法 alert("查询到的航班结果是:票价为"+data.price+"元,余票为"+data.tickets+"张。"); }, error: function(){ // 调用失败之后的方法 alert('error'); } }); }); </script> </head> <body> </body> </html>
注解:
注解1:参数jsonp: "callback" 不是必须的,如果该参数为空,则 默认为:jQuery+一个随机字符串,例如:jQuery182025588105828501284_1453863135570,并且执行结果和加上该参数一样。
注解2:参数jsonpCallback:"flightHandler"是必须的,如果该参数为空,则调用失败。
注解3:如果自定义回调函数(即jsonpCallback参数)的函数体不为空,则优先执行该自定义函数,然后执行success中的代码:
客户端jsonp5.html:
<!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"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <head> <title></title> <script type="text/javascript" src="http://code.jquery.com/jquery-1.8.2.js"></script> <script type="text/javascript"> $(function(){ // 使用ajax来调用jsonp $.ajax({ type: "get", //jsonp默认为get请求,即使写post也会转换成get方式 async: false, // jsonp默认为false,即使写true也会转换成false url: "http://172.19.137.52:8080/rm-admin/flight/result.htm", // 服务端地址 data: {"code" : "CA1405"}, // 入参 dataType: "jsonp", // jsonp调用固定写法 jsonp: "callback", // 传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)。即:?callback=xxx中的callback部分 jsonpCallback:"flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据。即:?callback=xxx中的xxx部分 success: function(data){ // 调用成功之后的方法 alert("success"); }, error: function(){ // 调用失败之后的方法 alert('error'); } }); }); // 自定义回调函数 function flightHandler(data){ alert("(普通写法)查询到的航班结果是:票价为"+data.price+"元,余票为"+data.tickets+"张。"); } // 或用原生写法 var flightHandler = function(data){ alert("(原生写法)查询到的航班结果是:票价为"+data.price+"元,余票为"+data.tickets+"张。"); } </script> </head> <body> </body> </html>
服务端同示例3中的服务端,运行结果也同示例3的运行结果。