如果服务端部署在 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页中分别打开来 百度和谷歌的页面当一个百度浏览器执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。
跨域一般分为两种:
- 同域名不同端口
- 不同域名
下面我们来演示一下最简单的跨域:同域名不同端口
第一步:创建两个不同的项目(本例在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)
$(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,页面显示结果如下:
这就是一个简单的跨域访问(跨相同域名,不同的端口),那么为什么能够访问呢?
解决方案:使用JSONP解决AJAX跨域问题
<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”的错误,即为解析错误。
{"id":1,"name":"小强","age":23}
jsonp格式数据:
callback({{"id":1,"name":"小强","age":23}})
因此,当你在前端设置为jsonp,服务器传送json格式的数据,因此出现解析错误。
@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函数封装,发送给客户端,这样客户端才能够正确的解析。
在之前的例子基础之上,将客户端引用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