2012-06-06 46 views

回答

26

我們都喜歡REST。它的供應商,平臺和語言中立;調試,實現和訪問很簡單;它爲您的雲端,瀏覽器,手機和桌面應用提供了堅實的後端。

Java開發人員可以使用支持JAX-RS的庫(如RESTEasy)在幾分鐘內啓動並運行REST服務器。然後,只需幾行代碼即可從Java客戶端應用程序調用這些JAX-RS REST服務器。

儘管GWT與Java有許多共同之處,但從GWT調用REST服務可能是一件痛苦的事情。使用RequestBuilder類涉及指定正確的HTTP方法,對URL進行編碼,然後解碼響應或創建對象以表示由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時需要注意的一些疑難問題。

實施GWT JAX-RS客戶端通常,當您需要實現CORS

,將要調試針對外部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()方法中得到了證明。

其餘界面下方允許任何和所有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接口。 JAX-RS的Java和GWT實現都知道如何序列化和反序列化基本類和java.util集合等簡單類。

在現實世界的例子中,你的REST接口將會響應更復雜的對象。這是不同實現可能發生衝突的地方。

首先,JAX-RS和Errai使用不同的註釋來自定義JSON和Java對象之間的對象編組。 Errai的註釋類似@MapsTo和@Portable,而RESTEasy(或者Jackson,the JSON marshaller)使用@JsonIgnore和@JsonSerialize之類的註釋。這些註釋是相互排斥的:GWT會抱怨傑克遜註釋,傑克遜不能使用註釋註釋。

簡單的解決方案是使用一套簡單的POJO與您的休息界面。簡單來說,我指的是沒有參數構造函數的類,並且只有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數據對象來實現一些業務功能。但是,如果你這樣做,在嘗試使用GWT不支持的類之前不會花費很長時間,並且您可能會對數字格式化,克隆數組,將字符串轉換爲字節[]等操作感到驚訝。 ..名單繼續。因此,最好堅持使用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具有字節[]的問題。改爲使用列表。有關更多詳細信息,請參閱this forum post

你需要使用Firefox

調試當涉及到使用GWT通過REST接口發送大量數據,你將不得不調試與Firefox應用程序。根據我自己的經驗,即使將一個小文件編碼爲字節[]並通過網絡發送,也會導致Chrome中出現各種錯誤。由於JSON編碼器試圖處理損壞的數據,因此會引發各種不同的異常; GWT應用程序的編譯版本中未顯示的異常或在Firefox上進行調試時發生的異常。

不幸的是,Google並沒有設法通過Mozilla的新發布週期保持Firefox GWT插件的最新狀態,但是您經常會發現Alan Leung在GWT Google Groups論壇上發佈了非官方的非官方版本。 This link有一個版本的GWT插件的Firefox 12,並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,雖然在未來您可以定義自定義marshallers。

+0

這是一個非常完整的答案! – jonasr

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吧!

--A