如何使用Errai将GWT与JAX-RS / RESTEasy服务器集成?

时间:2012-06-06 07:10:20

标签: gwt jboss resteasy errai

我想从GWT客户端应用程序调用使用RESTEasy和JAX-RS创建的REST服务。使用Errai为服务器和客户端使用单个代码库的最佳过程是什么?

2 个答案:

答案 0 :(得分:27)

我们都喜欢REST。它的供应商,平台和语言中立;调试,实现和访问都很简单;它为您的云,浏览器,移动和桌面应用程序提供了可靠的后端。

Java开发人员可以使用支持JAX-RS的库,例如RESTEasy,以便在几分钟内启动并运行REST服务器。然后,using JAX-RS clients,只需几行代码即可从Java客户端应用程序调用这些JAX-RS REST服务器。

尽管GWT与Java有很多共同点,但从GWT调用REST服务可能是一种痛苦的经历。使用RequestBuilder类涉及指定正确的HTTP方法,对URL进行编码,然后解码响应或创建Overlay对象以表示REST服务器发回的数据。调用一个或两个REST方法可能不是一个很大的开销,但在将GWT与更复杂的REST服务集成时,它确实代表了很多工作。

这就是Errai的用武之地.Errai是一个JBoss项目,其中包括implements the JAX-RS standard within GWT。从理论上讲,这意味着您可以在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);
}

然后将REST接口注入到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代码联系未在同一域中运行的服务器。实际上,您甚至可以在本地开发PC上运行REST服务器,但仍会遇到这些跨域问题,因为不同端口之间的调用也受到限制。

如果您使用RESTEasy,可以使用两种方法实现CORS。第一个是使用MessageBodyInterceptors接口完成的。您提供了write()方法,并使用@Provider和@ServerInterceptor注释注释您的类。然后使用write()方法将“Access-Control-Allow-Origin”标头添加到对任何简单请求的响应中(“简单”请求不设置自定义标头,请求主体仅使用纯文本)。 / p>

第二种方法处理CORS预检请求(对于可能对用户数据产生副作用的HTTP请求方法 - 特别是对于GET以外的HTTP方法,或对某些MIME类型的POST使用)。这些请求使用HTTP OPTIONS方法,并期望在回复中接收“Access-Control-Allow-Origin”,“Access-Control-Allow-Methods”和“Access-Control-Allow-Headers”标头。这在下面的handleCORSRequest()方法中进行了演示。

注意

下面的REST接口允许任何和所有CORS请求,从安全角度来看可能不合适。但是,假设在此级别阻止或限制CORS将提供任何程度的安全性是不明智的,因为代表客户端发出这些请求的setting up a proxy非常简单。

@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接口,用Long响应。 JAX-RS的Java和GWT实现都知道如何序列化和反序列化原语和简单类,如java.util集合。

在现实世界的示例中,您的REST接口将响应更复杂的对象。这是不同实现可能发生冲突的地方。

首先,JAX-RS和Errai使用不同的注释来自定义JSON和Java对象之间对象的编组。 Errai有@MapsTo和@Portable等注释,而RESTEasy(或Jackson,the JSON marshaller)使用@JsonIgnore和@JsonSerialize等注释。这些注释是相互排斥的:GWT会抱怨杰克逊注释,杰克逊不能使用埃莱注释。

简单的解决方案是在休息界面中使用一组简单的POJO。简单来说,我指的是具有no-args构造函数的类,并且只有getter和setter方法直接与JSON对象在线路上传输时出现的属性相关。 Errai和Jackson都可以使用默认设置对简单的POJO进行编组,从而无需兼顾不兼容的注释。

Errai和Jackson还从不同的地方为JSON字符串中的结果属性命名。 Jackson将使用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 doesn’t support:日期格式化,克隆数组,将字符串转换为byte [] ...列表继续。因此,最好坚持使用REST数据对象的基础知识,并使用组合或基于组件的设计完全在REST数据对象继承树之外实现任何业务逻辑。

注意

如果没有@Portable注释,则需要manually list any classes used Errai when calling the REST interface in the ErraiApp.properties file

注意

您还希望远离地图。有关详细信息,请参阅this bug

注意

您无法在JSON服务器返回的对象层次结构中使用嵌套的参数化类型。有关详细信息,请参阅this bugthis forum post

注意

Errai遇到byte []问题。请改用列表。有关详细信息,请参阅this forum post

您需要使用Firefox进行调试

使用GWT通过REST接口发送大量数据时,您必须使用Firefox调试应用程序。根据我自己的经验,即使将一个小文件编码为byte []并通过网络发送也会导致Chrome中出现各种错误。当JSON编码器尝试处理损坏的数据时,将抛出各种不同的异常;在GWT应用程序的编译版本中或在Firefox上调试时未见的异常。

不幸的是,Google并没有设法让他们的Firefox GWT插件与Mozilla的新发布周期保持同步,但你经常可以在GWT Google Groups论坛中找到Alan Leung发布的非正式插件。 This link有一个适用于Firefox 12的GWT插件版本,this link有一个适用于Firefox 13的版本。

您需要使用Errai 2.1或更高版本

Only Errai 2.1 or later will produce JSON that is compatible with Jackson,如果您尝试将GWT与RESTEasy集成,这是必须的。可以使用

启用Jackson编组
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 only supports JSON mashalling at this point,虽然将来您可以定义自定义编组程序。

答案 1 :(得分: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";
}

哎呀!

- 甲