关于Ajax跨域问题之JSONP

如果服务端部署在 foo.com 域名下,而客户端部署在 bar.com 域名下,此时从 bar.com 发出一个 AJAX 的请求到 foo.com,就会出现报错:

No 'Access-Control-Allow-Origin' header is present on the requested resource.
或者

Permission denied to call method XMLHttpRequest.open
为什么会出现以上错误呢?

这是因为所有支持Javascript的浏览器都会使用同源策略这个安全策略。

同源策略:

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

跨域一般分为两种:

  1. 同域名不同端口
  2. 不同域名

下面我们来演示一下最简单的跨域:同域名不同端口

第一步:创建两个不同的项目(本例在eclipse中创建),将其部署在tomcat服务器中。

将两个项目部署在不同的端口上,需要做一下操作:

(1)、修改tomcat配置文件server.xml

修改前配置文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  
  <Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener"/>
  <Listener className="org.apache.catalina.core.JasperListener"/>
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
  <GlobalNamingResources>
    <Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
  </GlobalNamingResources>

  <Service name="Catalina">

    <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>

    <Engine defaultHost="localhost" name="Catalina">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
      </Realm>

      <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
        </Host>

    </Engine>
  </Service>
</Server>
修改后配置文件内容,增加另外一个service标签(配置几个项目,可以增加几个service标签)

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  
  <Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener"/>
  <Listener className="org.apache.catalina.core.JasperListener"/>
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
  <GlobalNamingResources>
    <Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
  </GlobalNamingResources>

  <Service name="Catalina">

    <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>

    <Engine defaultHost="localhost" name="Catalina">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
      </Realm>

      <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
        </Host>

    </Engine>
  </Service>
  <Service name="Catalina1">
    <Connector connectionTimeout="20000" port="8090" protocol="HTTP/1.1" redirectPort="8443"/>
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>
    <Engine defaultHost="localhost" name="Catalina1">

      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
      </Realm>

      <Host appBase="webapps1" autoDeploy="true" name="localhost" unpackWARs="true">
        </Host>

    </Engine>
  </Service>

</Server>
注意:service name,第一个connector port,engine name,host appBase的配置。

(2)、创建新的部署相关目录

在tomcat根目录下,创建host appBase所指定的目录(webapps1)。

在tomcat根目录下,conf目录下,创建engine name所指定的目录(Catalina1/localhost)

(3)、将需要部署的项目复制到host appBase所指定的目录(webapps1)下。

(4)、启动tomcat,并访问如下路径

由于第一个项目部署在8080端口,因此访问:http://localhost:8080/ajaxTest/index.jsp

第二个项目部署在8090端口,访问:http://localhost:8090/ajaxTest2/index.jsp

第二步、使第一个项目(ajaxTest)访问第二个项目(ajaxTest2)
在ajaxTest2项目中创建test.js文件,内容如下:
$(function(){
	alert("呵呵呵");
});
文件目录如下:

在ajaxTest项目中,创建index.jsp,文件中引用ajaxTest2的js文件,文件内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="http://localhost:8080/ajaxTest/script/jquery.js"></script>
</head>
<body>
<script type="text/javascript" src="http://localhost:8090/ajaxTest2/script/test.js"></script>
<p>
	哈哈哈
</p>
</body>
</html>
最后,启动tomcat,访问ajaxTest项目:http://localhost:8080/ajaxTest/index.jsp,页面显示结果如下:

这就是一个简单的跨域访问(跨相同域名,不同的端口),那么为什么能够访问呢?
因为像拥有src属性的标签(img,script,iframe)等等,是没有同源策略限制的,因此是可以访问的。

解决方案:使用JSONP解决AJAX跨域问题
首先我们来看一下什么是JSONP呢?
JSONP是JSON with Padding的略称。它是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。

JSONP解决使用GET请求提交数据的跨域问题,因此,如果使用POST请求提交数据,那么就要使用下一篇讲的CORS。

我们使用Jquery的ajax请求,来实现ajax跨域访问,来看如下代码:
是ajaxTest项目访问ajaxTest2项目中的servlet。
<script type="text/javascript">
	$(function(){
		var url = "http://localhost:8090/ajaxTest2/test";
		$.ajax({
			url:url,
			dataType:"jsonp",
			processData:false,
			type:"get",
			success:function(data){
				alert(data.name);
			},
			error:function(XMLHttpRequest,textStatus,errorThrown){
				alert(XMLHttpRequest.status);
				alert(XMLHttpRequest.readyState);
				alert(textStatus);
			}
			
		});
	});
</script>
两个项目中的servlet是相同的,ajaxTest调用本项目中的servlet是正常显示数据,当调用ajaxTest2的servlet时,状态显示“200”,表示服务器执行成功,并返回数据,但是客户端提示“parseerror”的错误,即为解析错误。
原来,json与jsonp的格式是存在差异的。
json格式数据:
{"id":1,"name":"小强","age":23}
jsonp格式数据:
callback({{"id":1,"name":"小强","age":23}})
因此,当你在前端设置为jsonp,服务器传送json格式的数据,因此出现解析错误。
接下来,我们修改服务器代码,使其返回jsonp格式数据:
@Override
	public void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		
		Person person = new Person();
		person.setId(1L);
		person.setName("小强");
		person.setAge(23);
		
		Gson gson = new Gson();
		String json = gson.toJson(person);
		
		String callback = req.getParameter("callback");
		
		System.out.println(callback);
		
		resp.setCharacterEncoding("UTF-8");
		resp.setContentType("text/html");
		resp.getWriter().write(callback+"("+json+")");
	}
在Jquery中,客户端传送到服务器的callback函数名每次请求都是不同的,形如
jQuery191012974677915190092_1480570734367
jQuery191042678534055955564_1480571648774
因此,我们首先需要通过执行request.getParameter("callback")获得callback函数名,然后将数据转化成json格式,最后将数据通过callback函数封装,发送给客户端,这样客户端才能够正确的解析。

Jquery插件介绍:jquery-jsonp ,下载地址:https://github.com/jaubourg/jquery-jsonp

在之前的例子基础之上,将客户端引用json插件,然后修改调用代码如下:
$(function(){
		var url = "http://localhost:8090/ajaxTest2/test";
		$.jsonp({
			url:url,
			callbackParameter:"callback",
			success:function(data,textStatus){
				alert(data.name);
				alert(textStatus);
			},
			error:function(msg,textStatus){
				alert(msg);
				alert(textStatus);
			}
		});
	});
必须要指定callbackParameter,值可以随便取,在这儿设置的名字,仅仅是用来服务器端调用request.getParameter("callback")取回调函数的值,具体值的设置由插件内部进行设置,在我的测试过程中为:_jqjsp
data为返回的json格式数据,textStatus如果是成功值为success。






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值