在介绍jsonp之前,先来了解下jsonp诞生的起因,而这不得不提到AJAX的跨域问题。什么是跨域呢?先来看一个简单的例子:
假如我们建了一个建立的简单的html文件,它在页面加载的最后,通过AJAX去请求一个JSP页面(可以把这个JSP页面认为是一个server),代码如下所示:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
</body>
<script type="text/javascript" src="../js/util/domHelp.js"></script>
<script type="text/javascript" src="../js/ajax/sendToJsp.js"></script>
</html>
这个HTML文件引用了2个JS文件,内容分别如下所示:
一:domHelp.js
var domHelp = {
//创建xhr对象 createXHR:function(){ var xhr=null; if(window.XMLHttpRequest){ //非IE浏览器 xhr=new XMLHttpRequest(); }else if(window.ActiveXObject){ try{ //IE6.0+及以上版本 xhr=new ActiveXObject("Msxml2.XMLHTTP"); }catch(error){ //IE5.0+版本 xhr=new ActiveXOjbect("Microsoft.XMLHTTP"); } }else{ throw new Error("XMLHttpRequest can't be supported"); } return xhr; } };
二:sendToJsp.js
(function(){
var xhr=domHelp.createXHR(); xhr.onreadystatechange=function(){ //这里state等于4表示已经收到服务器全部响应,并且已经可以使用 if(xhr.readyState==4){ //这里大于等于200,表示成功响应,304表示请求的资源未被变更,可以使用浏览器的缓存 if((xhr.status>=200&&xhr.status<300)||(xhr.status==304)){ var responseText=xhr.responseText; if(responseText=="success"){ alert("get server.jsp successfullly"+responseText); }else{ alert("get server.jsp error--"+responseText); } } } }; xhr.open("get","http://localhost:8080/server.jsp?name=sweet",false); xhr.send(null); })();
server端JSP页面如下所示:
将server.jsp放置到Tomcat的webapp/root目录下,启动Tomcat,试着访问下这个页面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
</body>
<%
String name=request.getParameter("name");
System.out.println("name:"+name);
if(name!=null&&name.equals("sweet")){
out.println("suceess");
}else{
out.println("error");
}
%>
</html>
在浏览器里输入:http://localhost:8080/server.jsp?name=sweet,显示success,GOOD!
一切看上去都没问题,按照逻辑,当打开我们的ajaxTest.html的时候,Ajax请求就会顺利到达我们的server.jsp页面,并获取页面返回值,是这样吗?我们来看一下:
在firefox浏览器里输入我们的html在本地的文件目录地址,看下效果:
//firefox下提示
uncaught exception: [Exception...
"Component returned failure code: 0x80004005 (NS_ERROR_FAILURE)" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: file:///data/jsStudySpace/TestJS/WebContent/js/ajax/sendToJsp.js :: <TOP_LEVEL> :: line 35" data: no]
在chrome浏览器里输入我们的html在本地的文件目录地址,看下效果:
XMLHttpRequest cannot load http://127.0.0.1:8080/server.jsp?name=sweet.
Origin null is not allowed by Access-Control-Allow-Origin.
以上2个错误,如果你仔细去搜寻下答案,就会发现他们其实源自一个问题--浏览器的同源策略,什么是同源策略?可以参考一下这篇文章:
http://apps.hi.baidu.com/share/detail/24455399
简单点就是说浏览器不允许一个域名下面的页面去另一个域名下获取文件的信息,接受请求的URL域必须与当前所在的域相同。我们的页面是一个本地文件file:///data/.....,如果我们的JSP服务页面位于和我们的HTML页面同一个目录,就可以理解为两者是同一个域,但现在JSP服务页面位于我们本地的一个tomcat服务器的web目录,即使两者位于同一台机器上,它们之间也是跨域,所以跨域不是跨机器。那么这个问题怎么解决呢?目前广泛采用的是在请求发到另外一个域名的服务器之前,先经过中间服务器分发下,但是目前更为流行的方案是采用JSONP的方式来解决,在看JSONP之前,先看下JSONP的基本原理,如下所示:
刚才说浏览器都有自己的同源策略,但浏览器并不阻止将动态脚本插入到文档中,也就是说浏览器允许插入来自不同域的脚本文件。看一个简单的例子:
建立一个简单的HTML文件jsonpTest.html,引入一个本地的JS文件:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
</body>
<script type="text/javascript" src="../js/ajax/onload.js"></script>
</html>
引入的JS文件如下所示:
var url="file:///data/jsStudySpace/TestJS/WebContent/js/ticket.js";
var script=document.createElement("script");
script.setAttribute("src", url);
document.getElementsByTagName("head")[0].appendChild(script);
请求的JS文件如下所示:
function show(data){
alert("name:"+data.name+",value:"+data.value); } show({"name":"chenwu","value":10});
在浏览器里输入jsonpTest.html这个页面的本地文件目录地址:
页面弹出:name:chenwu,value:10
换不同的浏览器,这个操作都是OK的,好吧,现在我们回到本文的最开始讨论的问题,该如何利用浏览器允许引用不同域的JS文件来解决我们的跨域问题。解决方案(利用jquery自带的jsonp支持)先抛出来,原理下期再讲:
一:将服务器的JSP页面换成一个java Servlet,代码如下所示:
package Ajax;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class NameServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public NameServlet(){
super();
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
printParmeters(request.getParameterMap());
String name = request.getParameter("name");
System.out.println("name:" + name);
if (name == null) {
throw new IllegalArgumentException("name is null");
}
String data = getDataAsJson(name);
String output = request.getParameter("callback") + "(" + data + ");";
response.setCharacterEncoding("UTF-8");
response.setContentType("text/javascript");
PrintWriter writer = response.getWriter();
writer.println(output);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// post请求也转化为get请求处理
doGet(request, response);
}
private void printParmeters(Map<String,String[]> params){
System.out.println("begin to output param");
Set<Map.Entry<String,String[]>> set=params.entrySet();
for(Map.Entry<String,String[]> entry:set){
System.out.println("key:"+entry.getKey()+",value:"+Arrays.toString(entry.getValue()));
}
System.out.println("end to output param");
}
private String getDataAsJson(String name) {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append("\"");
sb.append("result");
sb.append("\"");
sb.append(":");
sb.append("\"");
if (name.equals("chenwu")) {
sb.append("success");
} else {
sb.append("failure");
}
sb.append("\"");
sb.append("}");
return sb.toString();
}
}
二:利用Jquery的所带的支持jsonp的方式来改变Ajax请求的方式:
首先在页面端引入Jquery文件:
<script type="text/javascript" src="../js/util/jquery-1.7.2.js"></script>
其次改变JS文件:
var param={
name:'chenwu' }; var url="http://localhost:8080/Test/nameServlet.do"; $.ajax(url,{ dataType:'jsonp', data:param, success:function(msg){ if(msg.result){ console.log("result:"+msg.result); } }, error:function(o){ alert("error"); console.log(o); } });
这次再利用不同的浏览器来测试,结果都是OK的。