基于JAVA的ajax代理的研究实现

5 篇文章 0 订阅

目录



前言


随着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代理无法正确读取中文参数,出现乱码。
  1. //构造查询连接字符串
  2. var targetUrl = "http://news.search.yahoo.com/news/rss"
  3. var queryPath = "p=奥运&ei=GBK&fl=0&x=wrt";
  4. var url = "http://localhost/ajaxproxy?proxyReqUrl ="+targetUrl+"?"+encodeURIComponent(encodeURIComponent(queryPath));
  5. //打开连接
  6. xmlHttp.open("GET", url, true);
  7. //设置回调函数
  8. xmlHttp.onreadystatechange = callback();
  9. //发送请求
  10. xmlHttp.send(null);

     对于post方式的请求,请求参数写在请求体中。请求目标地址是 http://news.search.yahoo.com/news ,请求体中的请求参数是 p=奥运&ei=GBK&fl=0&x=wrt

    
  1. //构造查询连接字符串
  2. var targetUrl = "http://news.search.yahoo.com/news/rss"
  3. var queryPath = "p=奥运&ei=GBK&fl=0&x=wrt";
  4. var url = "http://localhost/ajaxproxy?proxyReqUrl="+targetUrl
  5. //打开连接
  6. xmlHttp.open("post", url, true);
  7. //设置回调函数
  8. xmlHttp.onreadystatechange = updatePage;
  9. //设置服务器响应请求体参数
  10. xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;"); 
  11. //发送请求
  12. xmlHttp.send(queryPath);
其中 http://localhost/ajaxproxy 为ajax代理,proxyReqUrl为客户端请求的目标地址。

(2)Ajax代理请求目标服务器
     ajax代理(通常是一个servlet)程序获取客户端请求的目标地址proxyReqUrl,请求参数,以及客户端的请求方式 (GET,POST,PUT,DELETE),并根据客户端请求方式将请求路由到目标服务器(有一些ajax代理不根据用户的请求方式,统一采用GET或者POST的方式提交用户请求,这样做会因为目标服务器无法接受这种请求获取不到数据,例如REST应用就是根据不同的请求方式来决定做什么操作返回什么样的数据的,因此请求方式决定了restful webservice的行为方式)。
以下程序是一个典型的ajax代理实现:
  1. package ajax.proxy.web;

  2. import java.io.IOException;
  3. import java.io.PrintWriter;
  4. import java.net.URLDecoder;
  5. import java.net.URLEncoder;
  6. import java.util.Enumeration;
  7. import java.util.HashMap;
  8. import java.util.Map;

  9. import javax.servlet.ServletException;
  10. import javax.servlet.http.HttpServlet;
  11. import javax.servlet.http.HttpServletRequest;
  12. import javax.servlet.http.HttpServletResponse;

  13. import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
  14. import org.apache.commons.httpclient.HttpClient;
  15. import org.apache.commons.httpclient.HttpException;
  16. import org.apache.commons.httpclient.HttpStatus;
  17. import org.apache.commons.httpclient.methods.GetMethod;
  18. import org.apache.commons.httpclient.params.HttpMethodParams;

  19. import ajax.proxy.*;

  20. public class AjaxProxyServlet extends HttpServlet {
  21.        /** 
  22.         * Processes requests for both HTTP <code>GET</code> and <code>POST</code> and <code>PUT</code> and <code>DELETE</code> methods.
  23.         * @param request servlet request
  24.         * @param response servlet response
  25.         */
  26.         protected void processRequest(HttpServletRequest request, HttpServletResponse response)
  27.         throws ServletException, IOException {
  28.               //请求参数
  29.               Map<String,String> params = new HashMap<String,String>();
  30.               //客户端请求方式
  31.               String method = request.getMethod();
  32.               //目标服务器响应的编码
  33.               String encoding = "UTF-8";
  34.               //目标服务器地址
  35.               String url = null;
  36.               //获取参数键值名称 
  37.               Enumeration<String> enu = request.getParameterNames();
  38.     
  39.               while (enu.hasMoreElements()) {
  40.                     // 取得参数名称列表
  41.                     String paramName=(String)enu.nextElement();
  42.                     // 处理本请求参数以及发送给第三方服务器的参数
  43.                     if(paramName.equals("ajaxReqUrl")){
  44.                         try {
  45.                            // 获取目标服务器地址,并对目标服务器中的中文进行重新编码
  46.                            url=ProxyUtils.rebuildURL(URLDecoder.decode(request.getParameter(paramName),"utf-8"), "gbk");
  47.                         } catch (Exception e) {
  48.                            e.printStackTrace();
  49.                         }
  50.                      }else{
  51.                         String aParamValue = request.getParameter(paramName);
  52.                         params.put(paramName, aParamValue);
  53.                     }
  54.              }
  55.               //取得ajax代理
  56.               AjaxProxy proxy = ProxyFactory.getProxyInstance(method,url,params,encoding);
  57.               //获取ajax代理响应
  58.               AjaxResponse resp = proxy.getAjaxResponse();
  59.               //输出必须用UTF-8编码
  60.               response.setCharacterEncoding("UTF-8");
  61.               PrintWriter out = response.getWriter();
  62.               System.out.println(url);
  63.               out.println(resp.getContent());             
  64.         } 

  65.        
  66.         /** 
  67.         * Handles the HTTP <code>GET</code> method.
  68.         * @param request servlet request
  69.         * @param response servlet response
  70.         */
  71.         protected void doGet(HttpServletRequest request, HttpServletResponse response)
  72.         throws ServletException, IOException {
  73.             processRequest(request, response);
  74.         } 

  75.         /** 
  76.         * Handles the HTTP <code>POST</code> method.
  77.         * @param request servlet request
  78.         * @param response servlet response
  79.         */
  80.         protected void doPost(HttpServletRequest request, HttpServletResponse response)
  81.         throws ServletException, IOException {
  82.             processRequest(request, response);
  83.         }
  84.         
  85.         protected void doDelete(HttpServletRequest request, HttpServletResponse response)
  86.         throws ServletException, IOException {
  87.             processRequest(request, response);
  88.         }
  89.         
  90.         protected void doPut(HttpServletRequest request, HttpServletResponse response)
  91.         throws ServletException, IOException {
  92.             processRequest(request, response);
  93.         }

  94.         /** 
  95.         * Returns a short description of the servlet.
  96.         */
  97.         public String getServletInfo() {
  98.             return "Short description";
  99.         }
  100. }


程序开始时候先获得用户请求方式method, 请求目标地址url,请求参数params,再根据目标地址的响应编码(一般都预先得知),通过这四个参数从proxyFactory中获取合适的proxy,并调用getAjaxResponse方法来获得目标地址响应。
  1. //取得ajax代理
  2. AjaxProxy proxy = ProxyFactory.getProxyInstance(method,url,params,encoding);
  3. //获取ajax代理响应
  4. AjaxResponse resp = proxy.getAjaxResponse();
这里proxy主要有四种,本文采用了apache开源项目HttpClient来路由请求。
  • GetProxy
  • PostProxy
  • PutProxy
  • DeleteProxy
他们都继承于AjaxProxy类:
  1. package ajax.proxy;
  2. import java.io.IOException;
  3. import java.util.*;
  4. public abstract class AjaxProxy {
  5.     //请求目标地址
  6.     protected String url;
  7.     //请求参数
  8.     protected Map<String,String> params;
  9.     //目标地址返回响应的编码
  10.     protected String encoding;
  11.     
  12.     public AjaxProxy(String url, Map<String,String> params,
  13.             String encoding) {
  14.         super();
  15.         this.url = url;
  16.         this.params = params;
  17.         this.encoding = encoding;
  18.     }
  19.     /*
  20.      * 抽象方法,GetProxy,PostProxy,PutProxy,DeleteProxy各自实现这个方法,
  21.      * 返回的是AjaxResponse对象
  22.      */
  23.     public abstract AjaxResponse getAjaxResponse();
  24.     public String getEncoding() {
  25.         return encoding;
  26.     }
  27.     public void setEncoding(String encoding) {
  28.         this.encoding = encoding;
  29.     }
  30. }
Proxy工厂类,根据请求方法,请求目标地址,请求参数,目标响应编码来生产不同的代理。
  1. package ajax.proxy;
  2. import java.util.Map;
  3. public class ProxyFactory {
  4.         
  5.     public static AjaxProxy getProxyInstance(String method, String url, Map<String, String> params, String encoding){
  6.         AjaxProxy proxy = null;
  7.         if("GET".equalsIgnoreCase(method)){
  8.             proxy = new GetProxy(url, null, encoding);
  9.         }else if("POST".equalsIgnoreCase(method)){
  10.             proxy = new PostProxy(url, params, encoding);
  11.         }else if("PUT".equalsIgnoreCase(method)){
  12.             proxy = new PutProxy(url, params, encoding);
  13.         }else if("DELETE".equalsIgnoreCase(method)){
  14.             proxy = new DeleteProxy(url, null, encoding);
  15.         }
  16.         return proxy;
  17.     }
  18. }

以GetProxy为例,他是路由用户的Get请求,代码如下:
  1. package ajax.proxy;
  2. import java.io.IOException;
  3. import java.util.*;
  4. import org.apache.commons.httpclient.*;
  5. import org.apache.commons.httpclient.methods.GetMethod;
  6. import org.apache.commons.httpclient.params.HttpMethodParams;
  7. public class GetProxy extends AjaxProxy {
  8.     public GetProxy(String url, Map<String, String> params, String encoding) {
  9.         super(url, params, encoding);
  10.         // TODO Auto-generated constructor stub
  11.     }
  12.     public AjaxResponse getAjaxResponse() {
  13.           //构造HttpClient的实例
  14.           HttpClient httpClient = new HttpClient();
  15.           //httpClient.getParams().setUriCharset("GBK");
  16.           //创建GET方法的实例
  17.           GetMethod getMethod = new GetMethod(url);
  18.           
  19.           //使用系统提供的默认的恢复策略
  20.           getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
  21.             new DefaultHttpMethodRetryHandler());
  22.           try {
  23.            //执行getMethod
  24.            int statusCode = httpClient.executeMethod(getMethod);
  25.            System.out.println(getMethod.getStatusLine());
  26.            if (statusCode != HttpStatus.SC_OK) {
  27.                return new AjaxResponse(statusCode, "");
  28.            }
  29.            //读取内容 
  30.            byte[] responseBody = getMethod.getResponseBody();
  31.            //处理内容
  32.            return new AjaxResponse(statusCode, new String(responseBody, encoding));
  33.           } catch (HttpException e) {
  34.            //发生致命的异常,可能是协议不对或者返回的内容有问题
  35.            System.out.println("Please check your provided http address!");
  36.            e.printStackTrace();
  37.           } catch (IOException e) {
  38.            //发生网络异常
  39.            e.printStackTrace();
  40.           } finally {
  41.            //释放连接
  42.            getMethod.releaseConnection();
  43.           }
  44.           return new AjaxResponse(AjaxResponse.EXCEPTIONERROR, "");
  45.     }
  46. }

全部代码将在本文最后附录中提供下载。

解决方案二

使用阿帕奇的 mod_rewrite   or   mod_proxy 来从你的服务器上转发到另外的服务器 . 在你的客户端代码中你只要做请求就可以了 , 就好象工作在你自己的服务器上------- 不会有浏览器的限制问题 . 然后阿帕奇会神奇的为你请求其他服务器 .

解决方案三:


使用 json 或者动态 <script> 标记来替代 xml XMLHttpRequest. 你可以通过直接在 <script> 标记内发送你的 web 服务请求 , 这样就可以完全绕过浏览器的安全限制 . 你使用的 yahoo 服务能够输出 JSON( 使用 output=json callback=function 参数 ), 当页面加载完成后从 web 服务返回的数据已经被优化为 javascript 对象。

解决方案四

对你的脚本进行数字签名. FireFox , 你可以为你的脚本和那些信任站点的脚本申请一个数字签名 . 这样 FireFox 就会允许你通过 XMLHttpRequest 访问任何的域 . 但是 , 其他浏览器不支持这种方式的脚本签名,所以,这种解决方案会受限.

附录:

本文涉及的ajax代理源代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值