我想打电话使用的RESTEasy和JAX-RS从GWT客户端应用程序创建一个REST服务。 什么是使用Errai用于服务器和客户端的单一代码库的最佳工艺?
Answer 1:
我们都爱REST。 这是供应商,平台和语言无关; 它是简单的调试,实施和访问; 它提供了坚实的后端到您的云,浏览器,移动和桌面应用程序。
Java开发人员可以使用支持库JAX-RS一样的RESTEasy ,以获得在短短几分钟内一个REST服务器启动和运行。 然后, 使用JAX-RS的客户 ,这些JAX-RS REST服务器可以从Java客户端应用程序,只需几行代码调用。
但是,尽管GWT股多与Java通用,呼吁从GWT REST服务可以是一个痛苦的经历。 使用RequestBuilder类包括指定正确的HTTP方法,编码网址,然后或者进行解码的响应或创建叠加对象以表示数据通过REST服务器被送回。 这可能不是用于调用一个或两个REST方法大的开销,但它确实有一个更为复杂的REST服务于一体GWT当代表了很多工作。
这是Errai进来。Errai是JBoss的项目,除其他事项外, 实现内GWT的JAX-RS标准 。 从理论上讲,这意味着你可以分享你的Java和GWT项目之间的JAX-RS接口,提供限定的REST服务器功能的单一来源。
从调用的Errai REST服务器只涉及几个简单的步骤。 首先,你需要的REST JAX-RS接口。 这是一个JAX-RS注释的Java接口,定义将由您的REST服务器提供的方法。 此接口可以将Java和GWT项目之间共享。
@Path("customers")
public interface CustomerService {
@GET
@Produces("application/json")
public List<Customer> listAllCustomers();
@POST
@Consumes("application/json")
@Produces("text/plain")
public long createCustomer(Customer customer);
}
那么剩下的界面被注入到你的GWT客户端类。
@Inject
private Caller<CustomerService> customerService;
的响应处理程序中定义。
RemoteCallback<Long> callback = new RemoteCallback<Long>() {
public void callback(Long id) {
Window.alert("Customer created with ID: " + id);
}
};
最后的REST方法被调用。
customerService.call(callback).listAllCustomers();
很简单吧?
你可能会导致从这个例子相信Errai将提供解决当前的JAX-RS的基础设施的下降,但不幸的是这个简单的例子并不一些,你是有可能的并发症的触摸看到尝试要结合你的时候GWT和Java REST代码库。 下面是一些陷阱来了解使用Errai和JAX-RS的时候。
你将需要实现CORS
通常,实现GWT JAX-RS客户端时,你会反对外部REST服务器调试GWT应用程序。 这是行不通的,除非你实现CORS ,因为默认情况下托管GWT应用程序不会让你的JavaScript代码来接触未在同一域中运行的服务器浏览器。 事实上,你甚至可以在本地开发计算机上运行的REST服务器仍然会碰到这些跨领域的问题,因为不同的端口之间的电话也被限制。
如果您使用的RESTEasy,实现CORS可以用两种方法完成。 首先是使用进行MessageBodyInterceptors接口。 您提供的write()方法,并与@Provider和@ServerInterceptor注解注释类。 写()方法,然后使用到“访问控制允许来源”标头添加到响应任何简单的请求(“简单”的请求不设置自定义首部,并且该请求体只使用纯文本)。
第二种方法处理CORS预检请求(HTTP请求的方法,可以在用户数据引起的副作用 - 特别是,对于除GET,或用于使用POST与某些MIME类型其他HTTP方法)。 这些请求使用HTTP OPTIONS方法,并期望收到“访问控制允许来源”,“访问控制允许的方法”和“访问控制 - 允许 - 头”报头的答复。 这表现在下面的handleCORSRequest()方法。
注意
下面的REST接口允许任何和所有CORS请求,这可能不适合从安全角度来看。 然而,这是不明智的假设,阻止或限制CORS在这个水平,将提供任何安全程度, 设置代理 ,使代表客户的这些要求是相当琐碎。
@Path("/1")
@Provider
@ServerInterceptor
public class RESTv1 implements RESTInterfaceV1, MessageBodyWriterInterceptor
{
@Override
public void write(final MessageBodyWriterContext context) throws IOException, WebApplicationException
{ context.getHeaders().add(RESTInterfaceV1.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*");
context.proceed();
}
@OPTIONS
@Path("/{path:.*}")
public Response handleCORSRequest(@HeaderParam(RESTInterfaceV1.ACCESS_CONTROL_REQUEST_METHOD) final String requestMethod, @HeaderParam(RESTInterfaceV1.ACCESS_CONTROL_REQUEST_HEADERS) final String requestHeaders)
{
final ResponseBuilder retValue = Response.ok();
if (requestHeaders != null)
retValue.header(RESTInterfaceV1.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders);
if (requestMethod != null)
retValue.header(RESTInterfaceV1.ACCESS_CONTROL_ALLOW_METHODS, requestMethod);
retValue.header(RESTInterfaceV1.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*");
return retValue.build();
}
}
有了这两种方法,你的REST服务器的任何通话将提供适当的响应,以允许跨起源请求。
你需要接受和简单的POJO响应
引进所示,与龙一回答一个简单的REST接口。 这两个JAX-RS的Java和GWT实现知道如何序列化和反序列化原语和简单的类,如java.util中的集合。
在现实世界的例子,你的REST接口会以更复杂的对象作出回应。 这是不同的实现可以发生冲突。
一开始,JAX-RS和Errai使用不同的注解来定制JSON和Java对象之间的对象的编组。 Errai具有像@MapsTo和@Portable注释,而的RESTEasy(或杰克逊, 所述JSON编组器 )使用像@JsonIgnore和@JsonSerialize注释。 这些注解是互斥的:GWT会抱怨杰克逊注释和杰克逊不能使用Errai注解。
简单的解决办法是让使用一套与你的REST接口简单的POJO的。 通过简单的我的意思是具有无参数的构造,并具有唯一的获取和设置方法直接相关的特性,这将是存在于JSON对象当其行进在导线类。 简单的POJO可由两个Errai和杰克逊与它们的默认设置被编组,不再需要兼顾不兼容的注解。
Errai和杰克逊也源名称为来自不同地方的JSON字符串所产生的性能。 杰克逊将使用的getter和setter方法的名称,而Errai将使用实例变量的名称。 因此,请确保您的实例变量和getter / setter方法的名称是完全一样的。 还行吧:
public class Test
{
private int count;
public int getCount() {return count;}
public void setCount(int count) {this.count = count;}
}
这将导致的问题:
public class Test
{
private int myCount;
public int getCount() {return myCount;}
public void setCount(int count) {this.myCount = count;}
}
其次,人们很容易将其他方法添加到这些REST数据对象来实现一些业务功能。 但是,如果这样做,它不会很长,你尝试使用不是由GWT支持的上课前拿的,你可能会在什么惊讶GWT不支持 :日期格式,克隆数组,字符串转换为字节[] ...这样的例子不胜枚举。 所以,最好完全坚持的基本知识在您的REST的数据对象,并且实现任何业务逻辑使用类似成分或基于组件的设计REST数据对象继承树之外。
注意
如果没有@Portable注释,则需要手动列出调用在ErraiApp.properties文件REST接口时使用Errai任何类 。
注意
你也想从地图望而却步。 请参阅此漏洞的详细信息。
注意
你不能在你的JSON服务器返回的对象层次结构中使用嵌套参数化类型。 见这个bug和这个论坛帖子的详细资料。
注意
Errai与字节的问题[]。 使用列表来代替。 请参阅此论坛帖子的更多细节。
你需要使用Firefox进行调试
当涉及到使用GWT通过REST接口发送大量数据,你将不得不调试与Firefox应用程序。 在我自己的经验,甚至编码的小文件到一个byte []和发送它在网络上引起了Chrome的所有错误的方式。 各种不同的异常的将被抛出作为JSON编码器试图处理损坏的数据; 未在GWT应用程序的编译版本看出异常,或调试在Firefox时。
不幸的是谷歌并未设法保持他们的Firefox插件GWT了解最新的Mozilla的新的发布周期,但你往往可以找到非官方由Alan梁在GWT谷歌团体论坛发布。 此链接有一个版本的GWT插件的Firefox 12,而该链接已为Firefox 13版本。
你需要2.1或更高版本使用Errai
只有Errai 2.1或更高版本会产生JSON是与杰克逊兼容 ,这是如果你正试图与GWT集成的RESTEasy一绝。 杰克逊编组可以通过启用
RestClient.setJacksonMarshallingActive(true);
要么
<script type="text/javascript">
erraiJaxRsJacksonMarshallingActive = true;
</script>
你需要创建一个先进的功能独立JAX-RS接口
如果您的JAX-RS REST接口返回先进的物体,如ATOM (或更重要的是,进口类,如org.jboss.resteasy.plugins.providers.atom.Feed),你需要跨越两个Java分割你的REST接口接口,因为Errai不知道这些对象和类可能不是可以很容易地导入到GWT的状态。
一个接口可以牵你的普通旧JSON和XML的方法,而其他可容纳ATOM方法。 这样你可以避免引用接口与未知的类在GWT应用程序。
注意
Errai只支持JSON mashalling在这一点上 ,虽然在未来您可以定义自定义marshallers。
Answer 2:
为了实现CORS(这样我就可以得到跨站点的请求,当然),因为我使用的RESTEasy,我随后接受的答案建议和需要改变它有点工作类。 下面是我用什么:
//@Path("/1")
@Path("/") // I wanted to use for all of the resources
@Provider
@ServerInterceptor
public class RESTv1 implements RESTInterfaceV1, MessageBodyWriterInterceptor
{
/* Enables the call from any origin. */
/* To allow only a specific domain, say example.com, use "example.com" instead of "*" */
@Override
public void write(final MessageBodyWriterContext context) throws IOException, WebApplicationException
{ context.getHeaders().add(RESTInterfaceV1.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
context.proceed();
}
/* This is a RESTful method like any other.
The browser sends an OPTION request to check if the domain accepts CORS.
It sends via header (Access-Control-Request-Method) the method it wants to use, say 'post',
and will only use it if it gets a header (Access-Control-Allow-Methods) back with the intended
method in its value.
The method below then checks for any Access-Control-Request-Method header sent and simply
replies its value in a Access-Control-Allow-Methods, thus allowing any method to be used.
The same applies to Access-Control-Request-Headers and Access-Control-Allow-Headers.
*/
@OPTIONS
@Path("/{path:.*}")
public Response handleCORSRequest(
@HeaderParam(RESTInterfaceV1.ACCESS_CONTROL_REQUEST_METHOD) final String requestMethod,
@HeaderParam(RESTInterfaceV1.ACCESS_CONTROL_REQUEST_HEADERS) final String requestHeaders)
{
final ResponseBuilder retValue = Response.ok();
if (requestHeaders != null)
retValue.header(RESTInterfaceV1.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders);
if (requestMethod != null)
retValue.header(RESTInterfaceV1.ACCESS_CONTROL_ALLOW_METHODS, requestMethod);
retValue.header(RESTInterfaceV1.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
return retValue.build();
}
}
小心,因为这将允许来自任何来源的请求( ACCESS_CONTROL_ALLOW_ORIGIN_HEADER
设置为"*"
在这两种方法)。
对于这些常数的值如下:
public interface RESTInterfaceV1 {
// names of the headers
public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
}
Thatss吧!
- 一个