目录
前言
随着web2.0的发展,ajax变的越来越流行,并且已经深入到了许多web应用中。web2.0带来的还不仅仅是技术上和用户体验上的革新,更重要的 是他使互联网的信息更为开放,内容提供商们把私有的数据和服务通过web service(SOAP,REST)的方式暴露给他们的用户,用户可以遵循一定限制的基础上的对这些数据进行重新编排并展示。
ajax跨域问题描述
当数据位于同一个域内的时候ajax可以很容易的获取数据,如图一所示。但是很多人在尝试使用ajax去获取位于不同域的数据(例如yahoo的web service)会遇到了ajax不能跨域访问的问题,如图二所示。
图一 在同一个域中ajax获取服务器端数据没有问题
图二 ajax无法跨域获取位于yahoo域内的数据
其实并不是ajax无法实现跨域访问,而是出于安全性的考虑,一般的浏览器都禁止了这个危险的操作。其实在新兴的RIA应用框架中,诸如adobe的air就可以通过ajax进行跨域访问(当然必须在air的sandbox限制之下)。
AJAX跨域问题的解决方法
解决方案一: 编写ajax web代理程
Ajax web代理即是在服务器端写一个代理程序将客户端请求重新打包再发送到目标服务器获取数据,如图三所示:
图三 ajax代理原理图
图四 ajax路由四种http请求原理图
名词解释:
客户端:ajax请求的发起端。
客户端请求方式:主要有GET,POST,PUT,DELETE四种基本HTTP请求方式
请求参数:对于GET,PUT,DELETE请求,请求参数直接以query字段的形式写在URL里,POST的请求参数写在请求的body体中
Ajax代理: ajax代理必须在ajax可以访问的域中。
目标服务器:AJAX真实想访问的网址所在的服务器。
目标服务器响应编码:即被请求的目标服务器响应时的编码方式。
应用场景 :
用户需要在自己的应用中使用yahoo的news rss service,根据不同的查询,订阅新闻。
yahoo rss webservice的目标地址为http://news.search.yahoo.com/news/rss
查询参数为
p=查询参数,如“奥运”
ei=结果编码,如“GBK”
fl=0
x=wrt
使用ajax代理后的请求步骤:
(1)客户端请求AJAX代理
客户端请求Ajax代理时,需要给出请求的目标地址和请求参数。
对于get,put,delete方式的请求,请求参数是可以直接附着在url上的,示例代码如下,这里queryPath如果含有中文参数的话需要用 encodeURIComponent重新编码两次(至于为什么我也不太清楚,大家可以查看相关资料),否则ajax代理无法正确读取中文参数,出现乱码。
- //构造查询连接字符串
- var targetUrl = "http://news.search.yahoo.com/news/rss"
- var queryPath = "p=奥运&ei=GBK&fl=0&x=wrt";
- var url = "http://localhost/ajaxproxy?proxyReqUrl ="+targetUrl+"?"+encodeURIComponent(encodeURIComponent(queryPath));
- //打开连接
- xmlHttp.open("GET", url, true);
- //设置回调函数
- xmlHttp.onreadystatechange = callback();
- //发送请求
- xmlHttp.send(null);
对于post方式的请求,请求参数写在请求体中。请求目标地址是 http://news.search.yahoo.com/news ,请求体中的请求参数是 p=奥运&ei=GBK&fl=0&x=wrt 。
- //构造查询连接字符串
- var targetUrl = "http://news.search.yahoo.com/news/rss"
- var queryPath = "p=奥运&ei=GBK&fl=0&x=wrt";
- var url = "http://localhost/ajaxproxy?proxyReqUrl="+targetUrl
- //打开连接
- xmlHttp.open("post", url, true);
- //设置回调函数
- xmlHttp.onreadystatechange = updatePage;
- //设置服务器响应请求体参数
- xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;");
- //发送请求
- xmlHttp.send(queryPath);
(2)Ajax代理请求目标服务器
ajax代理(通常是一个servlet)程序获取客户端请求的目标地址proxyReqUrl,请求参数,以及客户端的请求方式 (GET,POST,PUT,DELETE),并根据客户端请求方式将请求路由到目标服务器(有一些ajax代理不根据用户的请求方式,统一采用GET或者POST的方式提交用户请求,这样做会因为目标服务器无法接受这种请求获取不到数据,例如REST应用就是根据不同的请求方式来决定做什么操作返回什么样的数据的,因此请求方式决定了restful webservice的行为方式)。
以下程序是一个典型的ajax代理实现:
- package ajax.proxy.web;
- import java.io.IOException;
- import java.io.PrintWriter;
- import java.net.URLDecoder;
- import java.net.URLEncoder;
- import java.util.Enumeration;
- import java.util.HashMap;
- import java.util.Map;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
- import org.apache.commons.httpclient.HttpClient;
- import org.apache.commons.httpclient.HttpException;
- import org.apache.commons.httpclient.HttpStatus;
- import org.apache.commons.httpclient.methods.GetMethod;
- import org.apache.commons.httpclient.params.HttpMethodParams;
- import ajax.proxy.*;
- public class AjaxProxyServlet extends HttpServlet {
- /**
- * Processes requests for both HTTP <code>GET</code> and <code>POST</code> and <code>PUT</code> and <code>DELETE</code> methods.
- * @param request servlet request
- * @param response servlet response
- */
- protected void processRequest(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- //请求参数
- Map<String,String> params = new HashMap<String,String>();
- //客户端请求方式
- String method = request.getMethod();
- //目标服务器响应的编码
- String encoding = "UTF-8";
- //目标服务器地址
- String url = null;
- //获取参数键值名称
- Enumeration<String> enu = request.getParameterNames();
- while (enu.hasMoreElements()) {
- // 取得参数名称列表
- String paramName=(String)enu.nextElement();
- // 处理本请求参数以及发送给第三方服务器的参数
- if(paramName.equals("ajaxReqUrl")){
- try {
- // 获取目标服务器地址,并对目标服务器中的中文进行重新编码
- url=ProxyUtils.rebuildURL(URLDecoder.decode(request.getParameter(paramName),"utf-8"), "gbk");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }else{
- String aParamValue = request.getParameter(paramName);
- params.put(paramName, aParamValue);
- }
- }
- //取得ajax代理
- AjaxProxy proxy = ProxyFactory.getProxyInstance(method,url,params,encoding);
- //获取ajax代理响应
- AjaxResponse resp = proxy.getAjaxResponse();
- //输出必须用UTF-8编码
- response.setCharacterEncoding("UTF-8");
- PrintWriter out = response.getWriter();
- System.out.println(url);
- out.println(resp.getContent());
- }
- /**
- * Handles the HTTP <code>GET</code> method.
- * @param request servlet request
- * @param response servlet response
- */
- protected void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- processRequest(request, response);
- }
- /**
- * Handles the HTTP <code>POST</code> method.
- * @param request servlet request
- * @param response servlet response
- */
- protected void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- processRequest(request, response);
- }
- protected void doDelete(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- processRequest(request, response);
- }
- protected void doPut(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- processRequest(request, response);
- }
- /**
- * Returns a short description of the servlet.
- */
- public String getServletInfo() {
- return "Short description";
- }
- }
程序开始时候先获得用户请求方式method, 请求目标地址url,请求参数params,再根据目标地址的响应编码(一般都预先得知),通过这四个参数从proxyFactory中获取合适的proxy,并调用getAjaxResponse方法来获得目标地址响应。
- //取得ajax代理
- AjaxProxy proxy = ProxyFactory.getProxyInstance(method,url,params,encoding);
- //获取ajax代理响应
- AjaxResponse resp = proxy.getAjaxResponse();
- GetProxy
- PostProxy
- PutProxy
- DeleteProxy
- package ajax.proxy;
- import java.io.IOException;
- import java.util.*;
- public abstract class AjaxProxy {
- //请求目标地址
- protected String url;
- //请求参数
- protected Map<String,String> params;
- //目标地址返回响应的编码
- protected String encoding;
- public AjaxProxy(String url, Map<String,String> params,
- String encoding) {
- super();
- this.url = url;
- this.params = params;
- this.encoding = encoding;
- }
- /*
- * 抽象方法,GetProxy,PostProxy,PutProxy,DeleteProxy各自实现这个方法,
- * 返回的是AjaxResponse对象
- */
- public abstract AjaxResponse getAjaxResponse();
- public String getEncoding() {
- return encoding;
- }
- public void setEncoding(String encoding) {
- this.encoding = encoding;
- }
- }
- package ajax.proxy;
- import java.util.Map;
- public class ProxyFactory {
- public static AjaxProxy getProxyInstance(String method, String url, Map<String, String> params, String encoding){
- AjaxProxy proxy = null;
- if("GET".equalsIgnoreCase(method)){
- proxy = new GetProxy(url, null, encoding);
- }else if("POST".equalsIgnoreCase(method)){
- proxy = new PostProxy(url, params, encoding);
- }else if("PUT".equalsIgnoreCase(method)){
- proxy = new PutProxy(url, params, encoding);
- }else if("DELETE".equalsIgnoreCase(method)){
- proxy = new DeleteProxy(url, null, encoding);
- }
- return proxy;
- }
- }
以GetProxy为例,他是路由用户的Get请求,代码如下:
- package ajax.proxy;
- import java.io.IOException;
- import java.util.*;
- import org.apache.commons.httpclient.*;
- import org.apache.commons.httpclient.methods.GetMethod;
- import org.apache.commons.httpclient.params.HttpMethodParams;
- public class GetProxy extends AjaxProxy {
- public GetProxy(String url, Map<String, String> params, String encoding) {
- super(url, params, encoding);
- // TODO Auto-generated constructor stub
- }
- public AjaxResponse getAjaxResponse() {
- //构造HttpClient的实例
- HttpClient httpClient = new HttpClient();
- //httpClient.getParams().setUriCharset("GBK");
- //创建GET方法的实例
- GetMethod getMethod = new GetMethod(url);
- //使用系统提供的默认的恢复策略
- getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
- new DefaultHttpMethodRetryHandler());
- try {
- //执行getMethod
- int statusCode = httpClient.executeMethod(getMethod);
- System.out.println(getMethod.getStatusLine());
- if (statusCode != HttpStatus.SC_OK) {
- return new AjaxResponse(statusCode, "");
- }
- //读取内容
- byte[] responseBody = getMethod.getResponseBody();
- //处理内容
- return new AjaxResponse(statusCode, new String(responseBody, encoding));
- } catch (HttpException e) {
- //发生致命的异常,可能是协议不对或者返回的内容有问题
- System.out.println("Please check your provided http address!");
- e.printStackTrace();
- } catch (IOException e) {
- //发生网络异常
- e.printStackTrace();
- } finally {
- //释放连接
- getMethod.releaseConnection();
- }
- return new AjaxResponse(AjaxResponse.EXCEPTIONERROR, "");
- }
- }
全部代码将在本文最后附录中提供下载。
解决方案二:
使用阿帕奇的mod_rewrite
or
mod_proxy
来从你的服务器上转发到另外的服务器
.
在你的客户端代码中你只要做请求就可以了
,
就好象工作在你自己的服务器上-------
不会有浏览器的限制问题
.
然后阿帕奇会神奇的为你请求其他服务器
.
解决方案三:
使用 json 或者动态 <script> 标记来替代 xml 和 XMLHttpRequest. 你可以通过直接在 <script> 标记内发送你的 web 服务请求 , 这样就可以完全绕过浏览器的安全限制 . 你使用的 yahoo 服务能够输出 JSON( 使用 output=json 和 callback=function 参数 ), 当页面加载完成后从 web 服务返回的数据已经被优化为 javascript 对象。