2012-06-06 23 views

Trả lời

26

Chúng ta đều yêu thích REST. Đó là nhà cung cấp, nền tảng và ngôn ngữ trung lập; thật đơn giản để gỡ lỗi, triển khai và truy cập; và nó cung cấp một kết thúc vững chắc cho các ứng dụng đám mây, trình duyệt, thiết bị di động và máy tính để bàn của bạn.

Nhà phát triển Java có thể sử dụng thư viện hỗ trợ JAX-RS, như RESTEasy, để khởi động và chạy máy chủ REST chỉ trong vài phút. Sau đó, using JAX-RS clients, các máy chủ REST JAX-RS này có thể được gọi từ các ứng dụng máy khách Java chỉ với một vài dòng mã.

Nhưng mặc dù thực tế rằng GWT có nhiều điểm chung với Java, việc gọi các dịch vụ REST từ GWT có thể là một trải nghiệm đau đớn. Sử dụng lớp RequestBuilder liên quan đến việc chỉ định phương thức HTTP chính xác, mã hóa URL của bạn và sau đó giải mã phản hồi hoặc tạo các đối tượng Overlay để thể hiện dữ liệu được máy chủ REST gửi lại. Đây có thể không phải là một chi phí lớn để gọi một hoặc hai phương thức REST, nhưng nó đại diện cho rất nhiều công việc khi tích hợp GWT với một dịch vụ REST phức tạp hơn.

Đây là nơi Errai có sẵn. Errai là một dự án JBoss, trong số những thứ khác, implements the JAX-RS standard within GWT. Về lý thuyết, điều này có nghĩa là bạn có thể chia sẻ giao diện JAX-RS của bạn giữa các dự án Java và GWT của bạn, cung cấp một nguồn duy nhất xác định chức năng của máy chủ REST của bạn.

Gọi máy chủ REST từ Errai chỉ liên quan đến một vài bước đơn giản. Đầu tiên, bạn cần giao diện REST JAX-RS. Đây là giao diện Java có chú thích JAX-RS xác định các phương thức sẽ được cung cấp bởi máy chủ REST của bạn. Giao diện này có thể được chia sẻ giữa các dự án Java và GWT của bạn.

@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); 
} 

Giao diện REST sau đó được đưa vào lớp khách hàng GWT của bạn.

@Inject 
private Caller<CustomerService> customerService; 

Trình xử lý đáp ứng được xác định.

RemoteCallback<Long> callback = new RemoteCallback<Long>() { 
    public void callback(Long id) { 
    Window.alert("Customer created with ID: " + id); 
    } 
}; 

Và cuối cùng phương thức REST được gọi.

customerService.call(callback).listAllCustomers(); 

Khá đơn giản huh?

Bạn có thể được tin tưởng từ ví dụ này rằng Errai sẽ cung cấp giải pháp cho cơ sở hạ tầng JAX-RS hiện tại của bạn, nhưng tiếc là ví dụ đơn giản này không chạm vào một số biến chứng mà bạn có thể thấy khi cố gắng kết hợp cơ sở mã GWT và Java REST của bạn. Dưới đây là một số điều cần biết khi sử dụng Errai và JAX-RS.

Bạn sẽ cần phải thực hiện CORS

Thông thường khi thực hiện một khách hàng GWT JAX-RS, bạn sẽ được gỡ lỗi ứng dụng GWT của bạn chống lại một máy chủ REST của bên ngoài. Điều này sẽ không hoạt động trừ khi bạn triển khai CORS, vì theo mặc định, trình duyệt lưu trữ ứng dụng GWT sẽ không cho phép mã JavaScript của bạn liên hệ với máy chủ không chạy trong cùng một miền. Trong thực tế, bạn thậm chí có thể chạy máy chủ REST trên máy tính phát triển cục bộ của bạn và vẫn chạy vào các vấn đề miền chéo này, bởi vì các cuộc gọi giữa các cổng khác nhau cũng bị hạn chế.

Nếu bạn đang sử dụng RESTEasy, việc triển khai CORS có thể được thực hiện bằng hai phương pháp. Việc đầu tiên được thực hiện bằng giao diện MessageBodyInterceptors. Bạn cung cấp phương thức write() và chú thích lớp của bạn bằng các chú thích @Provider và @ServerInterceptor. Phương thức write() sau đó được sử dụng để thêm tiêu đề "Access-Control-Allow-Origin" để phản hồi cho bất kỳ yêu cầu đơn giản nào (yêu cầu "đơn giản" không đặt tiêu đề tùy chỉnh và nội dung yêu cầu chỉ sử dụng văn bản thuần túy). Phương thức thứ hai xử lý các yêu cầu preflight CORS (đối với các phương thức yêu cầu HTTP có thể gây ra các tác dụng phụ trên dữ liệu người dùng - đặc biệt, đối với các phương thức HTTP khác với GET hoặc cho việc sử dụng POST với các kiểu MIME nhất định). Các yêu cầu này sử dụng phương thức HTTP OPTIONS và mong nhận được các tiêu đề “Access-Control-Allow-Origin”, “Access-Control-Allow-Methods” và “Access-Control-Allow-Headers” trong thư trả lời. Điều này được thể hiện trong phương thức handleCORSRequest() bên dưới.

Note

Các giao diện REST dưới đây cho phép bất kỳ và tất cả các yêu cầu CORS, mà có thể không phù hợp từ một quan điểm bảo mật. Tuy nhiên, nó là không khôn ngoan để giả định rằng ngăn chặn hoặc hạn chế CORS ở cấp độ này sẽ cung cấp bất kỳ mức độ bảo mật, như setting up a proxy để thực hiện các yêu cầu này thay mặt cho khách hàng là khá tầm thường.

@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(); 
    } 

} 

Với hai phương pháp này tại chỗ, bất kỳ cuộc gọi đến máy chủ REST của bạn sẽ cung cấp những câu trả lời thích hợp để cho phép các yêu cầu nguồn gốc chéo.

Bạn sẽ cần phải chấp nhận và đối phó với các POJO đơn giản

Việc giới thiệu minh họa một giao diện REST đơn giản mà đáp lại bằng một Long. Cả hai triển khai Java và GWT của JAX-RS đều biết cách tuần tự hóa và deserialize các lớp nguyên thủy và các lớp đơn giản như các bộ sưu tập java.util.

Trong ví dụ thế giới thực, giao diện REST của bạn sẽ phản hồi với các đối tượng phức tạp hơn. Đây là nơi các triển khai khác nhau có thể xung đột.

Để bắt đầu, JAX-RS và Errai sử dụng các chú thích khác nhau để tùy chỉnh việc sắp xếp các đối tượng giữa các đối tượng JSON và Java. Errai có các chú thích như @MapsTo và @Portable, trong khi RESTEasy (hoặc Jackson, the JSON marshaller) sử dụng các chú thích như @JsonIgnore và @JsonSerialize. Các chú thích này loại trừ lẫn nhau: GWT sẽ khiếu nại về chú thích Jackson và Jackson không thể sử dụng chú thích Errai.

Giải pháp đơn giản là sử dụng bộ POJO đơn giản với giao diện còn lại của bạn. Theo đơn giản, tôi có nghĩa là các lớp không có các hàm tạo không có arg, và chỉ có các phương thức getter và setter có liên quan trực tiếp đến các thuộc tính sẽ có mặt trong đối tượng JSON khi nó di chuyển qua dây. Các POJO đơn giản có thể được trộn lẫn bởi cả Errai và Jackson với các thiết lập mặc định của chúng, loại bỏ sự cần thiết phải sắp xếp các chú thích không tương thích.

Errai và Jackson cũng cung cấp tên cho các thuộc tính kết quả trong chuỗi JSON từ các vị trí khác nhau. Jackson sẽ sử dụng tên của các phương thức getter và setter, trong khi Errai sẽ sử dụng tên của các biến cá thể. Vì vậy, hãy chắc chắn rằng các biến cá thể của bạn và các tên phương thức getter/setter giống hệt nhau.Đây là ok:

public class Test 
{ 
    private int count; 
    public int getCount() {return count;} 
    public void setCount(int count) {this.count = count;} 
} 

Điều này sẽ gây ra vấn đề:

public class Test 
{ 
    private int myCount; 
    public int getCount() {return myCount;} 
    public void setCount(int count) {this.myCount = count;} 
} 

Thứ hai, đó là hấp dẫn để thêm các phương pháp bổ sung cho các đối tượng dữ liệu REST để thực hiện một số chức năng kinh doanh. Tuy nhiên, nếu bạn thực hiện điều này, sẽ không mất nhiều thời gian trước khi bạn thử và sử dụng một lớp không được GWT hỗ trợ, và bạn có thể ngạc nhiên với những gì GWT doesn’t support: định dạng ngày, nhân bản mảng, chuyển đổi chuỗi thành byte []. .. Danh sách cứ kéo dài. Vì vậy, tốt nhất bạn nên tuân theo những điều cơ bản trong các đối tượng dữ liệu REST của bạn và thực hiện bất kỳ logic nghiệp vụ nào hoàn toàn bên ngoài cây thừa kế đối tượng dữ liệu REST bằng cách sử dụng một cái gì đó giống như thành phần hoặc thiết kế dựa trên thành phần.

Note

Nếu không có chú thích @Portable, bạn sẽ cần phải manually list any classes used Errai when calling the REST interface in the ErraiApp.properties file.

Note

Bạn cũng sẽ muốn tránh xa Maps. Xem this bug để biết chi tiết.

Note

Bạn không thể sử dụng các kiểu tham số lồng nhau trong hệ thống phân cấp đối tượng được trả về bởi máy chủ JSON của bạn. Xem this bugthis forum post để biết chi tiết.

Note

Errai có vấn đề với byte []. Sử dụng Danh sách thay thế. Xem this forum post để biết thêm chi tiết.

Bạn sẽ cần phải gỡ lỗi với Firefox

Khi nói đến việc gửi một lượng lớn dữ liệu qua một giao diện REST sử dụng GWT, bạn sẽ phải gỡ lỗi ứng dụng của bạn với Firefox. Theo kinh nghiệm của riêng tôi, mã hóa ngay cả một tệp nhỏ thành một byte [] và gửi nó qua mạng đã gây ra tất cả các lỗi trong Chrome. Một loạt các ngoại lệ khác nhau sẽ được ném khi bộ mã hóa JSON cố gắng xử lý dữ liệu bị hỏng; các ngoại lệ không được nhìn thấy trong phiên bản đã biên dịch của ứng dụng GWT hoặc khi gỡ lỗi trên Firefox. Không may là Google đã không quản lý để giữ cho các plugin Firefox GWT của họ được cập nhật với các chu kỳ phát hành mới của Mozilla, nhưng bạn thường có thể tìm thấy không chính thức được phát hành bởi Alan Leung trong các diễn đàn GWT Google Groups. This link có một phiên bản của các plugin GWT cho Firefox 12, và this link có một phiên bản dành cho Firefox 13.

Bạn sẽ cần phải sử dụng Errai 2.1 hoặc mới hơn

Only Errai 2.1 or later will produce JSON that is compatible with Jackson, đó là điều bắt buộc nếu bạn là cố gắng tích hợp GWT với RESTEasy.Jackson marshalling thể được kích hoạt sử dụng

RestClient.setJacksonMarshallingActive(true); 

hoặc

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

Bạn sẽ cần phải tạo ra riêng biệt JAX-RS giao diện cho các tính năng tiên tiến

Nếu giao diện lợi nhuận JAX-RS REST của bạn tiến các đối tượng, như ATOM (hoặc nhiều hơn đến điểm, các lớp nhập khẩu như org.jboss.resteasy.plugins.providers.atom.Feed), bạn sẽ cần phải tách giao diện REST của bạn trên hai giao diện Java, bởi vì Errai không biết về các đối tượng này và các lớp có thể không ở trạng thái có thể dễ dàng được nhập vào GWT.

Một giao diện có thể chứa các phương thức JSON và XML cũ đơn giản của bạn, trong khi giao diện kia có thể chứa các phương pháp ATOM. Bằng cách đó bạn có thể tránh phải tham chiếu giao diện với các lớp không xác định trong ứng dụng GWT của bạn.

Note

Errai only supports JSON mashalling at this point, mặc dù trong tương lai bạn có thể xác định marshallers tùy chỉnh.

+0

Đó là câu trả lời thực sự hoàn chỉnh! – jonasr

2

Để thực hiện CORS (vì vậy tôi có thể nhận được yêu cầu chéo, tất nhiên), vì tôi đang sử dụng RESTEasy, tôi theo các lớp được gợi ý bằng câu trả lời được chấp nhận và cần thay đổi một chút để làm việc. Đây là những gì tôi đã sử dụng:

//@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(); 
    } 
} 

Chăm sóc, vì nó sẽ cho phép yêu cầu từ bất kỳ nguồn gốc (ACCESS_CONTROL_ALLOW_ORIGIN_HEADER được thiết lập để "*" trong cả hai phương pháp).

Các giá trị đối với những hằng số như sau:

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 nó!

--A

Các vấn đề liên quan