本地HTML訪問REST服務的實現
1 前言
最近一段時間在研究如何實現跨平台應用,其中的一個關鍵技術點就是本地HTML頁面如何訪問遠程服務。經過探索,終於解決了碰到的各種問題,做出了一個Demo.
本文就Demo所用的技術架構做了一個簡介,並分析了實現中碰到的問題及相應的解決方法。一來以此作為這段時間工作的一個技術總結,二來希望能對其他同行有所幫助。
2 技術架構
Demo 本身的功能很簡單,
1) 本地HTML 發出 “POST” 命令,發送一段數據到 Server端
2) Server 端存儲收到的數據
3) 本地HTML 發出工“GET” 命令,取回數據
Demo分為兩部分。Client端比較簡單,就是HTML+JQuery. Server端則用REST服務, 是用JAVA寫的,具體方案是glasshfish+ Jersey. 兩者這間用Json格式通信。
3 主要問題的解決方法:
3.1 Same origin policy
3.1.1 問題
這是整個Demo中耗時最多的一個問題。問題的表象是 從Chrome發出HTTP命令后,Chrome console中報 “Origin null is not allowed by Access-Control-Allow-Origin”錯誤。
這是由於Browser的same origin policy 限制的緣故。簡單來說,從HTML中發出XMLHttpRequest 請求時,Browser會做檢查,如果發現Response中沒有Access-Control-Allow-Origin Header或Access-Control-Allow-Origin Header Header的值與 HTML的 orgin 不同時,Browser會拒接絕該Response,Javascript就收不到該Response。 本地HTML的Origin是 null, 而Server端沒有發出Access-Control-Allow-Origin Header Header給Browser, 所以會有了“Origin null is not allowed by Access-Control-Allow-Origin”錯誤。
3.1.2 解決方法
事實上有一個W3C標准,Cross Origin Resource Sharing (CORS) 專門用來解決這個問題的。目前的主流Browser也有支持。CORS 在HTTP Message 加入幾個Header, Browser和 Server可以利用這些Header來判斷對方是否是安全,是否可以通信。
具體來說,CORS包括兩方面。 Server端和Browser端。
3.1.2.1 Server端
以Demo中的Server端為例,解決方法就是在 Jersey Servlet中加入一個Filter, 在 Filter中修改給Browser的Response, 共有兩步
1) 配置Web.xml
在Jersey Servlet中加入init-param
<servlet>
<servlet-name>ServletAdaptor</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>jasontesting.ResponseCorsFilter</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
2) ResponseCorsFilter
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package jasontesting;
/**
*
* @author jianl
*/
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
public class ResponseCorsFilter implements ContainerResponseFilter {
@Override
public ContainerResponse filter(ContainerRequest req, ContainerResponse contResp) {
ResponseBuilder resp = Response.fromResponse(contResp.getResponse());
resp.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Max-Age", 1728000)
.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
String reqHead = req.getHeaderValue("Access-Control-Request-Headers");
if(null != reqHead && !reqHead.equals(null)){
resp.header("Access-Control-Allow-Headers", reqHead);
}
contResp.setResponse(resp.build());
return contResp;
}
}
3.1.2.2 Browser端
如前所述,Browser已經實現了CORS,CORS對應用開發人員是透明的。為了方便,公布Demo的Browser端源代碼如下。
<script src="jquery-latest.js"></script>
<script src="jquery.json-2.3.js"></script>
<script type="text/javascript">
var url= "http://localhost:8080/WebApplication3/resources/contact";
var postobject={id:2,name:"Bob9",addresses:[{street:"Long Street 1",town:"Short Village"}],id_attribute:888};
jQuery.ajax({
url: url,
type: "POST",
data: $.toJSON(postobject),
dataType: "json",
contentType:"application/json",
success: function(result) {
//Write your code here
//alert(result);
$.get(
url,
function(data, textStatus, jqXHR) {
alert(data);
},
"json"
);
}
});
</script>
3.2 Accept and Content-Type Header
這個問題本身並不復雜,但由於之前對HTTP協議並不十分了解,還是花了大半天時間。問題核心是要在Request中要加入 Accept 與 Content-Type Header,指定可接愛的和發送的MIME 類型。具體的解決例子可參考Browser端的代碼。
4 總結
經過這些天的摸索,終於做出這個Demo,弄清楚了本地HTML訪問REST服務可如何實現,心中很是欣慰。要繼續努力,深化在移動開發方面的技術積累。
5 參考
http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/
http://www.html5rocks.com/en/tutorials/cors/
http://blog.usul.org/cors-compliant-rest-api-with-jersey-and-containerresponsefilter/