Newer
Older
notes / REST / jersey2.md
Seiji Sogabe on 14 Aug 2015 16 KB moxy追加

Jersey 2.x

Jersey 2.Xは、Oracleが提供するJAX-RS 2.0に準拠したライブラリで、GlassFishに標準でバンドルされています。

クライアントの実装

JAX-RS 2.0では、JAX-RS 1.1で規定されたサーバの仕様に加えて、クライアントの仕様が規定されています。 JAX-RS 2.0に準拠していれば、基本的なクライアントの機能については実装可能ですが、プロキシーやBASIC認証、個々のパラメータなどについては、JAX-RS 2.0では規定されておらず、独自の実装が必要になるため、注意が必要です。

ライブラリ

Jerseyのクライアントの実装である以下のライブラリが必要です。

    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-client</artifactId>
        <version>2.19</version>
    </dependency>

また、RESTサーバとのインタフェースの形式(XML,JSON)に応じて、以下のライブラリが必要になります。 JSON形式を使用する場合、複数のJSONの実装を使用できますが、JAX-RSとかの話 JSON通信での問題点に記載されている問題もあるため、Jettison以外を使用したほうが無難です。

    <!-- XML -->
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-jaxb</artifactId>
        <version>2.19</version>
    </dependency>
    <!-- JSON (Jackson) -->
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>2.19</version>
    </dependency>
    <!-- JSON (Jettison) -->
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jettison</artifactId>
        <version>2.19</version>
    </dependency>
    <!-- JSON (moxy) -->
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-moxy</artifactId>
        <version>2.19</version>
    </dependency>

RESTサーバとのHTTP接続にApache HTTP Componentを使用する場合は、以下のライブラリも必要です。

    <dependency>
        <groupId>org.glassfish.jersey.connectors</groupId>
        <artifactId>jersey-apache-connector</artifactId>
        <version>2.19</version>
    </dependency>

ファイルアップロードを行う場合は、以下のライブラリが必要です。

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-multipart</artifactId>
        <version>2.19</version>
    </dependency>

基本

http://localhost:8080/api/p にGETでアクセスするには、次のように行います。

    // クライアント生成
    Client client = ClientBuilder.newClient();                             
    // アクセスするエンドポイントURLを設定
    WebTarget target = client.target("http://localhost:8080/api/p");       
    // GETメソッドでリクエストを送信
    // レスポンスをJSON形式で送信するよう、ACCEPTヘッダに"application/json"を指定
    Response response = target.request(MediaType.APPLICATION_JSON).get();  
    try {
        // レスポンスコードがOKの場合  
        if (response.getStatus() == 200) {
            // レスポンスからエンティティを取得
            // Listを取得する場合は、GenericTypeを使用
            List<Plugin> plugins
                    = response.readEntity(new GenericType<List<Plugin>>() {}); 
        }
    } finally {
        // 必ずレスポンス、クライアントを閉じること
        try {
            response.close();
        } finally {
            client.close();
        }    
    }

エンティティをダイレクトに取得

Responseを使用しないで書くこともできます。

  // クライアントの生成
    Client client = ClientBuilder.newClient();
    // エンドポイントURLを設定
    WebTarget target = client.target("http://localhost:8080/api/p");
    try {
        // get()メソッドに、エンティティの型を指定して、ダイレクトにエンティティを取得
        List<Plugin> plugins = target.request(MediaType.APPLICATION_JSON)      
                .get(new GenericType<List<Plugin>>() {});
    } finally {
        client.close();
    }

この場合、レスポンスのステータスコードが2XX以外の場合は、 WebApplicationException(非検査例外)をスローします。

パス・パラメータ

http://localhost:8080/api/p/1 にアクセスする場合、リクエストのURL指定にパス・パラメータを使用することができます。

    // クライアントの生成
    Client client = ClientBuilder.newClient();
    // エンドポイントURLを設定。URLにパス・パラメータ{id}を使用する
    WebTarget target = client.target("http://localhost:8080/api/p/{id}")   
            // パス・パラメータのidに値を設定 
            .resolveTemplate("id", 1);                     
    // リクエスト送信            
    Plugin plugin = target.request(MediaType.APPLICATION_JSON)
            .get(Plugin.class);

クエリー・パラメータ

http://localhost:8080/api/p?name=emma のように、クエリー・パラメータを使用する場合を以下に示します。

    // クライアントの生成
    Client client = ClientBuilder.newClient();
    // エンドポイントURLを設定
    WebTarget target = client.target("http://localhost:8080/api/p")
            // クエリー・パラメータを設定
            .queryParam("name", "emma");
    // リクエスト送信        
    Response response = target.request(MediaType.APPLICATION_JSON).get();

クエリーパラメータのキーと値は、URLエンコーディングされるので、スペースや日本語が含まれていても問題ありません。

POST/PUT/DELETE

新しいPluginの情報をJSON形式でPOSTし登録してみます。

  // 新規に登録するオブジェクトの生成
    Plugin p = new Plugin();
    p.setName("Emma plugin");
    p.setDescription("Coverage Plugin");

    // クライアントの生成
    Client client = ClientBuilder.newClient();
    // エンドポイントURLを設定
    WebTarget target = client.target("http://localhost:8080/api/p");
    // POSTメソッドでリクエストを送信
    Plugin plugin = target.request(APPLICATION_JSON)
            // 第1引数に登録するオブジェクトと形式(JSON)を設定
            // Content-Typeヘッダに、"application/json"が設定される
            .post(Entity.entity(p, MediaType.APPLICATION_JSON), Plugin.class);

非同期

リクエストを非同期で送受信することができます。

    // クライアントの生成
    Client client = ClientBuilder.newClient();
    // エンドポイントURLの設定
    WebTarget target = client.target("http://localhost:8080/api/p/{id}")
            .resolveTemplate("id", 1);
    // リクエスト送信        
    // 送信後すぐに処理が戻り、次の命令を処理します
    Future<Plugin> future = target.request(MediaType.APPLICATION_JSON)
            // 非同期
            .async()
            .get(Plugin.class);
    // エンティティを取得
    // レスポンスを受信するまで待つ
    Plugin plugin = future.get();

コールバック

コールバックを指定することもできます。指定したコールバックは、リクエストを非同期で送信後、レスポンスを受信したら実行されます。

    Future<Plugin> future = target.request(MediaType.APPLICATION_JSON)
            // 非同期
            .async()
            // コールバックを指定
            .get(new InvocationCallback<Plugin>() {
                // リクエストが正常に成功した場合に実行
                @Override
                public void completed(Plugin plugin) {
                    LOG.info("### completed: {}", plugin.toString());
                }
                // リクエストが失敗した場合に実行
                @Override
                public void failed(Throwable throwable) {
                    LOG.warn("### failed: {}", throwable.getCause());
                }
            });
    LOG.info("requested!");

上記を実行すると、次のようなログが出力されます。

Running com.buildria.camel.pluginbot.client.PluginBotTest
11:42:09.440 - requested!
11:42:10.487 - ### completed: Plugin{id=1, name=Mantis Plugin, ... }

フィルタ、インタセプタ

フィルタ

フィルタは、リクエストやレスポンスのヘッダやエンティティ、その他のパラメータを変更する場合に使用します。 フィルタには、次の2種類のフィルタが用意されています。

    @Provider
    public class MyClientRequestFilter implements ClientRequestFilter {
        @Override
        public void filter(ClientRequestContext ctx) throws IOException {
            LOG.info("### MyClientRequestFilter called!");
        }
    }
  • ClientResponseFilter レスポンス受信時に実行されます。
     @Provider
     public class MyClientResponseFilter implements ClientResponseFilter {
         @Override
         public void filter(ClientRequestContext reqc, ClientResponseContext resc) 
                 throws IOException {
             LOG.info("### MyClientResponseFilter called!");
         }
     }

フィルタを実行するには、クライアントに登録します。

    Client client = ClientBuilder.newClient();
    client.register(MyClientRequestFilter.class);
    client.register(MyClientResponseFilter.class);

インタセプタ

インタセプタは、主にエンティティを変更する場合に使用します。

  • WriterInterceptor エンティティがリクエストに出力されるときに実行されます。
    @Provider
    public class MyClientWriterInterceptor implements WriterInterceptor {
        @Override
        public void aroundWriteTo(WriterInterceptorContext ctx) 
                throws IOException, WebApplicationException {
            LOG.info("### MyClientWriterInterceptor called!");
            ctx.proceed();
        }
    }
  • ReaderInterceptor  レスポンスからエンティティを読み込むときに実行されます。
    @Provider
    public class MyClientReaderInterceptor implements ReaderInterceptor {
        @Override
        public Object aroundReadFrom(ReaderInterceptorContext ctx) 
                throws IOException, WebApplicationException {
            LOG.info("### MyClientReaderInterceptor called!");
            return ctx.proceed();
        }
    }

インタセプタを実行するには、クライアントに登録します。

    Client client = ClientBuilder.newClient();
    client.register(MyClientWriterInterceptor.class);
    client.register(MyClientReaderInterceptor.class);

プロキシー

JAX-RS 2.0では、プロキシーを使用したアクセスは規定していないため、実装に依存することになります。

Jerseyでは、HTTP接続部分を ConnectorProvider と呼び、差し替え可能になっています。デフォルトでは、 HttpUrlConnection を利用する HttpUrlConnectorProvider を使用します。 プロキシーを設定するには、プロキシーに対応した ConnectorProvider の実装を生成し、HTTP接続に使用します。

HttpUrlConnectorProvider

プロキシーに対応した HttpUrlConnectorProvider は以下のとおりです。

    // プロキシー
    final Proxy proxy 
            = new Proxy(Type.HTTP, new InetSocketAddress("localhost", 8888));

    // ConnectorProviderの生成
    HttpUrlConnectorProvider cp = new HttpUrlConnectorProvider();
    cp.connectionFactory(new HttpUrlConnectorProvider.ConnectionFactory() {
        @Override
        public HttpURLConnection getConnection(URL url) throws IOException {
            // プロキシーを使用するコネクションを返す
            return (HttpURLConnection) url.openConnection(proxy);
        }
    });

    // クライアントにConnectorProviderを設定
    ClientConfig config = new ClientConfig();
    config.connectorProvider(cp);
    // クライアント生成
    Client client = ClientBuilder.newClient(config);
    :

ApacheConnectorProvider

ConnectorProvider の実装に、Apache HTTP Componentを使用する場合は、以下のとおりです。

    // ConnectorProviderの生成
    ApacheConnectorProvider cp = new ApacheConnectorProvider();
    //. プロキシーの設定
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.PROXY_URI, "http://localhost:8888");
    // クライアントにConnectorProviderを設定
    config.connectorProvider(cp);
    // クライアント生成
    Client client = ClientBuilder.newClient(config);

BASIC認証

    Client client = ClientBuilder.newClient();
    client.register(HttpAuthenticationFeature
            // BASIC認証                              
            .basicBuilder()
            // ノン・プリエンプティブモード
            .nonPreemptive()                         
            // ユーザ名、パスワード
            .credentials("aaaa", "bbbb")
            .build());

ノン・プリエンプティブモードでは、あるURLにアクセスし、401(Unauthorized)が返ってきた場合のみ、ユーザ名、パスワードを再度送信するようにします。 プリエンプティブモードの場合は、URLが保護されているかにかかわらず、ユーザ名、パスワードを送信します。

ファイルアップロード

    // クラアイアンとの生成
    Client client = ClientBuilder.newClient()
            // マルチパート用のフィルタ
            .register(MultiPartFeature.class);

    WebTarget target = client.target("http://localhost:8080/b/upload");

    // マルチパート生成
    MultiPart multiPart = new MultiPart();
    multiPart.setMediaType(MediaType.MULTIPART_FORM_DATA_TYPE);
    // アップロードするストリームの設定
    FormDataBodyPart part = new StreamDataBodyPart(
            "file",
            // ストリーム
            getClass().getResourceAsStream("sample.png"), 
            // ファイル名
            "pom.xml", 
            MediaType.APPLICATION_OCTET_STREAM_TYPE);
    multiPart.bodyPart(part);

    // リクエスト送信
    Response response = target.request()
            .post(Entity.entity(multiPart, multiPart.getMediaType()));

ストリームではなく、ファイルを指定する場合は、 FileDataBoddyPartを使用します。

その他

  • コネクションタイムアウトの設定

     ClientConfig config = new ClientConfig();
     config.property(ClientProperties.CONNECT_TIMEOUT, 10 * 1000);
  • リードタイムアウトの設定

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.READ_TIMEOUT, 1);
  • ユーザエージェントの設定
    Plugin plugin = target.request(MediaType.APPLICATION_JSON)
            .header("User-Agent", "MyAgent")
            .get(Plugin.class);
  • リダイレクト対応RESTサーバが、ステータスコード 301か302を返した場合に、自動的にリダイレクト先に対応します。
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.FOLLOW_REDIRECTS, true);
  • リクエスト・レスポンスのダンプ
    Client client = ClientBuilder.newClient()
            .register(new LoggingFilter(java.util.logging.Logger.getLogger(getClass().getName()), true));