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的运行结果。