本地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/