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

如何使用Errai将GWT与JAX-RS/RESTEasy服务器集成?,gwt,jboss,resteasy,errai,Gwt,Jboss,Resteasy,Errai,我想从GWT客户端应用程序调用使用RESTEasy和JAX-RS创建的REST服务。使用Errai为服务器和客户端使用单一代码库的最佳过程是什么?我们都喜欢REST。它与供应商、平台和语言无关;它易于调试、实现和访问;它为您的云、浏览器、移动和桌面应用程序提供了坚实的后端 Java开发人员可以使用支持REST服务器的库,例如,在几分钟内启动并运行REST服务器。然后,只需几行代码就可以从Java客户机应用程序调用这些JAX-RSREST服务器 但是,尽管GWT与Java有很多共同之处,但从GWT

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

我们都喜欢REST。它与供应商、平台和语言无关;它易于调试、实现和访问;它为您的云、浏览器、移动和桌面应用程序提供了坚实的后端

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

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

这就是我们的优势所在。Errai是一个JBoss项目,除其他外。理论上,这意味着您可以在Java和GWT项目之间共享JAX-RS接口,提供一个定义REST服务器功能的源代码

从Errai调用REST服务器只需要几个简单的步骤。首先,您需要RESTJAX-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);
}
很简单吧

从这个示例中,您可能会相信,Errai将为您当前的JAX-RS基础架构提供一个插入式解决方案,但不幸的是,这个简单的示例没有涉及您在尝试组合GWT和Java REST代码库时可能会看到的一些复杂情况。下面是使用Errai和JAX-RS时需要注意的一些问题

您需要实施CORS

通常,在实现GWT JAX-RS客户机时,您将针对外部REST服务器调试GWT应用程序。除非您实现,否则这不会起作用,因为默认情况下,承载GWT应用程序的浏览器将不允许您的JavaScript代码与不在同一域中运行的服务器联系。事实上,您甚至可以在本地开发PC上运行REST服务器,但仍然会遇到这些跨域问题,因为不同端口之间的调用也受到限制

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

第二种方法处理CORS飞行前请求(对于可能对用户数据产生副作用的HTTP请求方法,尤其是对于GET以外的HTTP方法,或者对于某些MIME类型的POST使用)。这些请求使用HTTP OPTIONS方法,并期望在应答中接收“访问控制允许源”、“访问控制允许方法”和“访问控制允许头”头。下面的handleCORSRequest()方法演示了这一点

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

有了这两种方法,对REST服务器的任何调用都将提供适当的响应,以允许跨源请求

您需要接受并用简单的POJO回复

介绍说明了一个简单的REST接口,该接口的响应时间很长。JAX-RS的Java和GWT实现都知道如何序列化和反序列化原语和简单类,如Java.util集合

在一个真实的例子中,您的REST接口将使用更复杂的对象进行响应。这就是不同实现可能发生冲突的地方

首先,JAX-RS和Errai使用不同的注释来定制JSON和Java对象之间的对象编组。Errai有@MapsTo和@Portable之类的注释,而RESTEasy(或Jackson)使用@JsonIgnore和@JsonSerialize之类的注释。这些注释是互斥的:GWT会抱怨Jackson注释,而Jackson不能使用勘误表注释

简单的解决方案是在rest接口中使用一组简单POJO。简单地说,我指的是没有args构造函数的类,只有getter和setter方法,这些方法与JSON对象在传输过程中出现的属性直接相关。简单的POJO可以由Errai和Jackson以其默认设置进行编组,从而消除了处理不兼容注释的需要

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数据对象添加其他方法以实现某些业务功能是很有诱惑力的。但是,如果您这样做,那么您很快就会尝试使用不受支持的类
customerService.call(callback).listAllCustomers();
@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();
    }

}
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;}
}
RestClient.setJacksonMarshallingActive(true);
<script type="text/javascript">
  erraiJaxRsJacksonMarshallingActive = true;
</script>
//@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();
    }
}
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";
}