【问题标题】:How do you integrate GWT with a JAX-RS/RESTEasy server using Errai?如何使用 Errai 将 GWT 与 JAX-RS/RESTEasy 服务器集成?
【发布时间】:2012-06-06 07:10:19
【问题描述】:

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

【问题讨论】:

    标签: gwt jboss resteasy errai


    【解决方案1】:

    我们都喜欢 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”标头添加到任何简单请求的响应中(“简单”请求不设置自定义标头,请求正文仅使用纯文本)。

    第二种方法处理 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 会抱怨 Jackson 的注解,而 Jackson 不能使用 Errai 的注解。

    简单的解决方案是使用一组简单的 POJO 和你的 rest 接口。简单来说,我的意思是类没有参数构造函数,并且只有 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 的内容感到惊讶:日期格式、克隆数组、将字符串转换为字节 [ ]... 名单还在继续。因此,最好坚持 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 调试您的应用程序。根据我自己的经验,即使将一个小文件编码为一个字节 [] 并通过网络发送它也会在 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 集成,这是必须的。杰克逊编组可以使用

    RestClient.setJacksonMarshallingActive(true);
    

    <script type="text/javascript">
      erraiJaxRsJacksonMarshallingActive = true;
    </script>
    

    您需要为高级功能创建单独的 JAX-RS 接口

    如果您的 JAX-RS REST 接口返回高级对象,例如 ATOM(或者更重要的是,导入 org.jboss.resteasy.plugins.providers.atom.Feed 等类),您需要拆分您的REST 接口跨越两个 Java 接口,因为 Errai 不知道这些对象,并且这些类可能不处于可以轻松导入 GWT 的状态。

    一个接口可以保存普通的旧 JSON 和 XML 方法,而另一个可以保存 ATOM 方法。这样您就可以避免在 GWT 应用程序中使用未知类引用接口。

    注意

    Errai only supports JSON mashalling at this point,尽管将来您可能能够定义自定义编组器。

    【讨论】:

    • 这是一个非常完整的答案!
    【解决方案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";
    }
    

    就是这样!

    --A

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-06-27
      • 2012-04-20
      • 2018-05-07
      • 2023-04-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-08
      • 2014-02-03
      相关资源
      最近更新 更多