CLOVER🍀

That was when it all began.

JavaでのHttpClientサンプル その2

こちらのエントリの、続編的なやつです。

JavaでのHttpClientサンプル
http://d.hatena.ne.jp/Kazuhira/20131026/1382796711

これを書いた後に、いくつか気になるHTTPクライアントライブラリを見てはいたのですが、ずっと触らないままだったのでこれを機にと思いまして。

こちらのエントリに、触発されたというのもあります。

google-http-java-client 入門
http://vividcode.hatenablog.com/entry/java/google-http-java-client

せっかくなので、google-http-java-client含めて試していってみます。

なお、サンプルとしては前回同様

サンプルとしては、

として書いていきます。

また、通信相手のHTTPサーバも同様にGroovyで書いたものを使用しています。これからJavaコードで記述するアクセス先に対して、curlでリクエストを投げるとこういう結果になります。

## GET
$ curl http://localhost:8080/get?param=value
Accessed URL = /get?param=value
Accessed Method = GET
Accessed Date = Sat Nov 15 20:41:08 JST 2014


## POST
$ curl -X POST http://localhost:8080/post -d '<<EOF
POST BODY
Hello Http Server!!
EOF
'
Accessed URL = /post
Accessed Method = POST
Accessed Date = Sat Nov 15 20:45:22 JST 2014
Request Body<<
<<EOF
POST BODY
Hello Http Server!!
EOF
>>

コード例にはテストコードを使用しますので、JUnitとAssertJを使います。

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.assertj</groupId>
      <artifactId>assertj-core</artifactId>
      <version>1.7.0</version>
      <scope>test</scope>
    </dependency>

書くテストコードでは、以下のimport文が入っているものとします。

import static org.assertj.core.api.Assertions.*;

import org.junit.Test;

では、いってみます。

なお、今回ご紹介するHTTPクライアントは、いずれも何らかのHTTP通信ライブラリの上に構築されたものになります。

Async Http Client

今回、1番試してみたかったHTTPクライアントです。他のJVM言語で使われているHTTPクライアントの内部でも使われたりしている、非同期HTTP通信を行うライブラリです。

Async Http Client
https://github.com/AsyncHttpClient/async-http-client
https://asynchttpclient.github.io/async-http-client/

Webサイトのドキュメントは、古い点があるので注意してください。

Maven依存関係。

    <dependency>
      <groupId>com.ning</groupId>
      <artifactId>async-http-client</artifactId>
      <version>1.8.14</version>
    </dependency>

import文。

import java.io.IOException;
import java.util.concurrent.ExecutionException;

import com.ning.http.client.AsyncCompletionHandler;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.Response;

GETの例。

    @Test
    public void testAsynHttpClientGet() {
        try (AsyncHttpClient client = new AsyncHttpClient()) {
            Response response =
                client
                .prepareGet("http://localhost:8080/get?param=value")
                .execute()
                .get();

            assertThat(response.getStatusCode())
                .isEqualTo(200);
            assertThat(response.getResponseBody("UTF-8"))
                .contains("Accessed URL = /get?param=value",
                          "Accessed Method = GET");
        } catch (IOException | InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

POSTの例。

    @Test
    public void testAsynHttpClientPost() {
        try (AsyncHttpClient client = new AsyncHttpClient()) {
            StringBuilder builder = new StringBuilder();
            builder.append("POST Body");
            builder.append("\r\n");
            builder.append("Hello Http Server!!");
            builder.append("\r\n");

            Response response =
                client
                .preparePost("http://localhost:8080/post")
                .setBody(builder.toString())
                .execute()
                .get();

            assertThat(response.getStatusCode())
                .isEqualTo(200);
            assertThat(response.getResponseBody("UTF-8"))
                .contains("Accessed URL = /post",
                          "Accessed Method = POST",
                          "Request Body<<",
                          "POST Body",
                          "Hello Http Server!!",
                          ">>");
        } catch (IOException | InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

いずれも、executeメソッドを呼び出した後にgetメソッドの呼び出しが入っていますが、これはFuture#getになります。なので、いったんFutureで受けて後で結果を取り出すことも可能ということになります。

AsyncHttpClientは、Closeableです。

AsyncHandlerインターフェースというものを使って、コールバックを受けるスタイルで書くこともできます。以下のコードでは、AsyncHandlerインターフェースの拡張である、AsyncCompleteHandlerを使用しています。

    @Test
    public void testAsynHttpClientGetWithHandler() {
        try (AsyncHttpClient client = new AsyncHttpClient()) {
            String responseData =
                client
                .prepareGet("http://localhost:8080/get?param=value")
                .execute(new AsyncCompletionHandler<String>() {
                        @Override
                        public String onCompleted(Response response) throws Exception {
                            return response.getResponseBody("UTF-8");
                        }
                    })
                .get();

            assertThat(responseData)
                .contains("Accessed URL = /get?param=value",
                          "Accessed Method = GET");
        } catch (IOException | InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

また、Async Http Clientは、以下のHTTP通信ライブラリを基盤として使用することができます。

  • Netty(デフォルト)
  • Grizzly
  • Commons HttpClient
  • java.net.URL

デフォルトはNettyで、Commons HttpClientなどのライブラリを使用する場合は、オプションとなっている依存関係の追加が必要です。

例えば、Commons HttpClientを使う場合は、以下の依存関係と

    <!-- Async Http Client, using Commons Http Client-->
    <dependency>
      <groupId>commons-httpclient</groupId>
      <artifactId>commons-httpclient</artifactId>
      <version>3.1</version>
    </dependency>

import文を追加して

// for Async Http Client, using Apache Http Client 3.1 
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.AsyncHttpProvider;
import com.ning.http.client.providers.apache.ApacheAsyncHttpProvider;

このようなコードになります。

    @Test
    public void testAsynHttpClientGetUsingCommonsHttpClient() {
        AsyncHttpClientConfig.Builder builder = new AsyncHttpClientConfig.Builder();
        AsyncHttpProvider provider = new ApacheAsyncHttpProvider(builder.build());

        try (AsyncHttpClient client = new AsyncHttpClient(provider)) {
            Response response =
                client
                .prepareGet("http://localhost:8080/get?param=value")
                .execute()
                .get();

            assertThat(response.getStatusCode())
                .isEqualTo(200);
            assertThat(response.getResponseBody("UTF-8"))
                .contains("Accessed URL = /get?param=value",
                          "Accessed Method = GET");
        } catch (IOException | InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

この部分が、少し長くなりましたね。

        AsyncHttpClientConfig.Builder builder = new AsyncHttpClientConfig.Builder();
        AsyncHttpProvider provider = new ApacheAsyncHttpProvider(builder.build());

        try (AsyncHttpClient client = new AsyncHttpClient(provider)) {

API的には、なかなか使いやすそうなHTTPクライアントライブラリだと思います。

依存ライブラリが古い(Nettyは3系、Commons HttpClient使用)なのですが、GitHubのmasterはすでにパッケージ名から変わった2系が進んでいるようです。ただ、Providerの部分はNettyとGrizzlyしかなさそうでしたが。

まあ、通信基盤はNettyで良いと思うので、2系の登場についても待っていようと思います。

あと、URLエンコードをAsyncHttpClientに任せる場合は、確かUTF-8固定だった気がしたような…。

google-http-java-client

こちらは、前述のブログを見るまで存在を知らなかった、GoogleによるHTTPクライアントライブラリです。参照先の説明が詳しいので、詳細はそちらへ…。

google-http-java-client
https://code.google.com/p/google-http-java-client/

Apache HttpComponents、java.net.URL、そしてGoogle App Engine向けの実装を含んだHTTPクライアントのようです。

Apache HttpComponentsは、少し古くて4.0.1に依存しています(google-http-java-clientの1.19.0時点)。

Maven依存関係。

    <dependency>
      <groupId>com.google.http-client</groupId>
      <artifactId>google-http-client</artifactId>
      <version>1.19.0</version>
    </dependency>

import文。

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.InputStreamContent;
import com.google.api.client.http.apache.ApacheHttpTransport;

ここでは、Apache HttpComponentsを使うことにします。

GETの例。

    @Test
    public void testGoogleHttpJavaClientGet() {
        HttpTransport transport = new ApacheHttpTransport();
        HttpRequestFactory factory = transport.createRequestFactory();

        GenericUrl url = new GenericUrl("http://localhost:8080/get?param=value");

        try {
            HttpRequest request = factory.buildGetRequest(url);
            HttpResponse response = request.execute();

            try {
                assertThat(response.getStatusCode())
                    .isEqualTo(200);
                assertThat(response.parseAsString())
                    .contains("Accessed URL = /get?param=value",
                              "Accessed Method = GET");
            } finally {
                response.disconnect();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                transport.shutdown();
            } catch (IOException e ) {
                e.printStackTrace();
            }
        }
    }

POSTの例。

    @Test
    public void testGoogleHttpJavaClientPost() {
        HttpTransport transport = new ApacheHttpTransport();
        HttpRequestFactory factory = transport.createRequestFactory();

        GenericUrl url = new GenericUrl("http://localhost:8080/post");

        try {
            StringBuilder builder = new StringBuilder();
            builder.append("POST Body");
            builder.append("\r\n");
            builder.append("Hello Http Server!!");
            builder.append("\r\n");

            HttpRequest request =
                factory.buildPostRequest(url,
                                         new InputStreamContent("text/plain",
                                                                new ByteArrayInputStream(builder.toString().getBytes(StandardCharsets.UTF_8))));
            HttpResponse response = request.execute();

            try {
                assertThat(response.getStatusCode())
                    .isEqualTo(200);
                assertThat(response.parseAsString())
                    .contains("Accessed URL = /post",
                              "Accessed Method = POST",
                              "Request Body<<",
                              "POST Body",
                              "Hello Http Server!!",
                              ">>");
            } finally {
                response.disconnect();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                transport.shutdown();
            } catch (IOException e ) {
                e.printStackTrace();
            }
        }
    }

HttpTransport#shutdownは、お作法的に呼び出した方がよさそうですね。特にApache HttpComponentsを使った場合は、これの呼び出しがHttpClient#getConnectionManager#shutdownになっていますので。

jcabi-http

最後は、ちょっと前に見かけたもので、jcabi-httpです。こちらも、それほど情報を見たことはないですが…。

Fluent HTTP Client
http://http.jcabi.com/

通信基盤はApache HttpComponentsとjava.net.URLで、HTTP通信をFluentに書けるライブラリです。

Maven依存関係。

    <dependency>
      <groupId>com.jcabi</groupId>
      <artifactId>jcabi-http</artifactId>
      <version>1.9.2</version>
    </dependency>

引っ張られてくる依存関係が、上記2つのライブラリに比べて多めなのがちょっと気になるところだったりします…。

import文。

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import com.jcabi.http.Request;
import com.jcabi.http.Response;
import com.jcabi.http.request.JdkRequest;

まずは、JdkRequest(java.net.URL)を使ってみます。

GETの例。

    @Test
    public void testJcabiHttpGet() {
        try {
            Response response =
                new JdkRequest("http://localhost:8080/get?param=value")
                .method(Request.GET)
                .fetch();

            assertThat(response.status())
                .isEqualTo(200);
            assertThat(response.body())
                .contains("Accessed URL = /get?param=value",
                          "Accessed Method = GET");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

POSTの例。

    @Test
    public void testJcabiHttpPost() {
        try {
            StringBuilder builder = new StringBuilder();
            builder.append("POST Body");
            builder.append("\r\n");
            builder.append("Hello Http Server!!");
            builder.append("\r\n");

            Response response =
                new JdkRequest("http://localhost:8080/post")
                .method(Request.POST)
                .fetch(new ByteArrayInputStream(builder.toString().getBytes(StandardCharsets.UTF_8)));

            assertThat(response.status())
                .isEqualTo(200);
            assertThat(response.body())
                .contains("Accessed URL = /post",
                          "Accessed Method = POST",
                          "Request Body<<",
                          "POST Body",
                          "Hello Http Server!!",
                          ">>");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

特にクローズ関係の呼び出しがないですが、Request#fetchの呼び出し時に、クローズ処理が入っているようです。

続いて、通信ライブラリをApache HttpComponentsに切り替えてみます。Maven依存関係に以下を追加。

    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpcore</artifactId>
      <version>4.3.3</version>
    </dependency>
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.3.3</version>
    </dependency>

import文に以下を追加。

import com.jcabi.http.request.ApacheRequest;

GETの例だと、このようになります。

    @Test
    public void testJcabiHttpGetUsingApacheHttpComponents() {
        try {
            Response response =
                new ApacheRequest("http://localhost:8080/get?param=value")
                .method(Request.GET)
                .fetch();

            assertThat(response.status())
                .isEqualTo(200);
            assertThat(response.body())
                .contains("Accessed URL = /get?param=value",
                          "Accessed Method = GET");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

まあ、ここが変わっただけですが。

                new ApacheRequest("http://localhost:8080/get?param=value")

それで

簡単な使い方をざっと載せただけのご紹介、という感じでしたが実際にどれを使うかはAPIの好みとかで良いと思います。

個人的には、データ形式の変換とかはそれほどこだわらなくていいので、簡潔に書けるものであればそれでいい気がしています。

Apache HttpComponentsは、高機能ではありますがいろいろ冗長だったり、リソース関係でハマるなど、あまり良い印象がなくてですね…とはいえ、仕事だとこちらを使うことも多いのですが…。