6

RESTEasy と JAX-RS を使用して作成された REST サービスを GWT クライアント アプリケーションから呼び出したいと考えています。Errai を使用してサーバーとクライアントの両方に単一のコード ベースを使用するための最適なプロセスは何ですか?

4

2 に答える 2

27

私たちは皆 REST が大好きです。ベンダー、プラットフォーム、言語に中立です。デバッグ、実装、アクセスが簡単です。また、クラウド、ブラウザー、モバイル、およびデスクトップ アプリに強固なバックエンドを提供します。

Java 開発者は、 RESTEasyなどの JAX-RSをサポートするライブラリを使用して、わずか数分で REST サーバーを起動して実行できます。次に、JAX-RS クライアントを使用して、これらの JAX-RS REST サーバーを Java クライアント アプリケーションからわずか数行のコードで呼び出すことができます。

しかし、GWT は Java と多くの共通点を共有しているという事実にもかかわらず、GWT から REST サービスを呼び出すのは苦痛な経験になる可能性があります。RequestBuilderクラスを使用するには、正しい HTTP メソッドを指定し、URL をエンコードしてから、応答をデコードするか、REST サーバーから送り返されるデータを表すOverlayオブジェクトを作成する必要があります。これは、1 つまたは 2 つの REST メソッドを呼び出すための大きなオーバーヘッドではないかもしれませんが、GWT をより複雑な REST サービスと統合する場合、多くの作業が必要になります。

ここでErraiの出番です。Errai は JBoss プロジェクトであり、とりわけGWT 内に JAX-RS 標準を実装します。理論的には、これは、REST サーバーの機能を定義する単一のソースを提供して、Java プロジェクトと GWT プロジェクトの間で JAX-RS インターフェースを共有できることを意味します。

Errai から REST サーバーを呼び出すには、いくつかの簡単な手順が必要です。まず、REST JAX-RS インターフェースが必要です。これは、REST サーバーによって提供されるメソッドを定義する JAX-RS アノテーション付き Java インターフェースです。このインターフェースは、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 インフラストラクチャにドロップイン ソリューションを提供すると信じるように導かれるかもしれませんが、残念ながら、この単純な例では、既存の JAX-RS インフラストラクチャを組み合わせようとするときに発生する可能性のあるいくつかの複雑さに触れていません。 GWT および Java REST コード ベース。ここでは、Errai と JAX-RS を使用する際に知っておくべき落とし穴をいくつか示します。

CORSを実装する必要があります

通常、GWT JAX-RS クライアントを実装する場合、GWT アプリケーションを外部 REST サーバーに対してデバッグします。CORSを実装しない限り、これは機能しません。デフォルトでは、GWT アプリケーションをホストするブラウザーは、JavaScript コードが同じドメインで実行されていないサーバーに接続することを許可しないためです。実際、ローカルの開発用 PC で REST サーバーを実行していても、これらのクロス ドメインの問題が発生する可能性があります。これは、異なるポート間の呼び出しも制限されているためです。

RESTEasy を使用している場合、CORS の実装は 2 つの方法で実行できます。1 つ目は、 MessageBodyInterceptorsインターフェイスを使用して行われます。write() メソッドを提供し、クラスに @Provider および @ServerInterceptor アノテーションを付けます。次に write() メソッドを使用して、単純なリクエストへの応答に「Access-Control-Allow-Origin」ヘッダーを追加します (「単純な」リクエストはカスタム ヘッダーを設定せず、リクエストの本文はプレーン テキストのみを使用します)。

2 番目のメソッドは、CORS プリフライト リクエストを処理します (ユーザー データに副作用を引き起こす可能性のある HTTP リクエスト メソッド、特に GET 以外の HTTP メソッド、または特定の MIME タイプでの POST の使用)。これらのリクエストは HTTP OPTIONS メソッドを使用し、応答で「Access-Control-Allow-Origin」、「Access-Control-Allow-Methods」、および「Access-Control-Allow-Headers」ヘッダーを受信することを期待しています。これは、以下の handleCORSRequest() メソッドで示されています。

ノート

以下の REST インターフェイスは、セキュリティの観点からは適切ではない可能性がある、すべての CORS 要求を許可します。ただし、クライアントに代わってこれらの要求を行うプロキシを設定するのは非常に簡単であるため、このレベルで CORS を防止または制限することである程度のセキュリティが提供されると想定するのは賢明ではありません。

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

}

これら 2 つの方法を使用すると、REST サーバーへのすべての呼び出しで適切な応答が提供され、クロス オリジン リクエストが許可されます。

シンプルな POJO で受け入れて応答する必要があります。

導入部では、Long で応答する単純な REST インターフェイスを示しました。JAX-RS の Java 実装と GWT 実装はどちらも、プリミティブと単純なクラス (java.util コレクションなど) をシリアライズおよびデシリアライズする方法を認識しています。

現実の例では、RESTインターフェイスはより複雑なオブジェクトで応答します。

まず、JAX-RS と Errai は異なるアノテーションを使用して、JSON と Java オブジェクト間のオブジェクトのマーシャリングをカスタマイズします。Errai には @MapsTo や @Portable などのアノテーションがあり、RESTEasy (またはJSON マーシャラーのJackson ) は @JsonIgnore や @JsonSerialize などのアノテーションを使用します。これらのアノテーションは相互に排他的です。GWT は Jackson アノテーションについて文句を言い、Jackson は Errai アノテーションを使用できません。

簡単な解決策は、残りのインターフェイスで単純な POJO のセットを使用することです。簡単に言えば、引数のないコンストラクターを持ち、ネットワーク上を移動するときに JSON オブジェクトに存在するプロパティに直接関連する getter メソッドと setter メソッドのみを持つクラスを意味します。単純な POJO は、デフォルト設定で Errai と Jackson の両方でマーシャリングできるため、互換性のないアノテーションを調整する必要がなくなります。

EraiとJacksonは、さまざまな場所からJSON文字列の結果のプロパティの名前を調達しています。ジャクソンはゲッターメソッドとセッターメソッドの名前を使用し、erraiはインスタンス変数の名前を使用します。そのため、インスタンス変数とゲッター/セッターメソッドの名前がまったく同じであることを確認してください。これで結構です:

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 がサポートしていないものに驚くかもしれません: 日付の書式設定、配列の複製、文字列からbyte[]... リストは続きます。したがって、REST データ オブジェクトの基本に固執し、コンポジションやコンポーネント ベースの設計などを使用して、REST データ オブジェクトの継承ツリーの外部に完全にビジネス ロジックを実装するのが最善です。

ノート

@Portable アノテーションがない場合、ErraiApp.properties ファイルで REST インターフェースを呼び出すときに Errai を使用するすべてのクラスを手動でリストする必要があります。

ノート

また、マップには近づかないようにしてください。詳細については、このバグを参照してください。

ノート

JSON サーバーから返されるオブジェクト階層で、ネストされたパラメーター化された型を使用することはできません。詳細については、このバグこのフォーラムの投稿を参照してください。

ノート

Errai には byte[] に関する問題があります。代わりにリストを使用してください。詳細については、このフォーラムの投稿を参照してください。

Firefox でデバッグする必要があります

GWT を使用して REST インターフェイス経由で大量のデータを送信する場合、Firefox でアプリケーションをデバッグする必要があります。私自身の経験では、小さなファイルでも byte[] にエンコードしてネットワーク経由で送信すると、Chrome であらゆる種類のエラーが発生しました。JSON エンコーダーが破損したデータを処理しようとすると、さまざまな例外がスローされます。GWT アプリケーションのコンパイル済みバージョン、または Firefox でのデバッグ時に見られない例外。

残念ながら、Google は Firefox GWT プラグインを Mozilla の新しいリリース サイクルに合わせて最新の状態に保つことができませんでしたが、GWT Google グループ フォーラムで Alan Leung によって非公式にリリースされたプラグインを頻繁に見つけることができます。このリンクには Firefox 12 用の GWT プラグインのバージョンがあり、このリンクには Firefox 13 用のバージョンがあります。

Errai 2.1 以降を使用する必要があります

Errai 2.1 以降のみが Jackson と互換性のある JSON を生成します。これは、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 などのクラスをインポートする)、REST インターフェイスを 2 つの Java に分割する必要があります。 Errai はこれらのオブジェクトについて認識しておらず、クラスはおそらく GWT に簡単にインポートできる状態にないためです。

1 つのインターフェイスはプレーンな古い JSON および XML メソッドを保持でき、もう 1 つのインターフェイスは ATOM メソッドを保持できます。そうすれば、GWT アプリケーションで未知のクラスを持つインターフェースを参照する必要がなくなります。

ノート

Errai は現時点では JSON マシャリングのみをサポートしていますが、将来的にはカスタム マーシャラーを定義できるようになるかもしれません。

于 2012-06-06T07:10:20.217 に答える
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

于 2013-02-06T04:46:22.213 に答える