この記事は、Java Advent Calendar 2015 の 18 日目の記事です。
昨日は @mdstoy さんの で、結局 Optional のインスタンスに対して ifNotPresent 的なことをやりたいときはどうしたらいいんですか? でした。
明日は @maaya8585 さんです。

みなさんはWeb APIの呼び出しはどのライブラリを使ってますか?

などなど、色んな選択肢がありますよね。
(Retrofitは内部でOkHttpが使われているみたいだし、FeignもClientとしてOkHttpを使う事が出来るみたいです)

僕はここ数年Jersey Clientを使ってて特に不満はなかったのですが、
TECHNOLOGY RADAR NOV'15で、OkHttpがASSESS(調査)としてリストアップされたので試してみる事にしました

※TECHNOLOGY RADARについてはこちらの記事が詳しいです




OkHttpについて調べてみる


HTTP/2

OkHttpの公式サイトのOverviewを読んでみると以下の1文が

  • HTTP/2 and SPDY support allows all requests to the same host to share a socket.

2015年は16年ぶりとなるHTTPの新しいバージョン、HTTP/2がRFCになりました

2015年はHTTP/2の年であったと言っても過言ではないでしょう
僕のこのブログサイトもHTTP/2に対応しています :)

HTTP/2について詳しく知りたい人は以下を読むと良いと思います


JavaではHTTP/2をサポートする新しいHttpClientがJDK 9に入る予定になってます


JavaOne 2015でもHTTP/2に関するセッションがいくつかあったみたいですね


Jettyは既にサーバ、クライアントともにHTTP/2に対応しています


なので、JDK 9のリリースを待たなくても、Jettyのhttp2-clientやOkHttpを使えば今でもHTTP/2で通信が出来る訳です


では早速、簡単なコード例を


まずはpom.xmlにdependencyを追加

<dependency>
    <groupId>com.squareup.okhttp</groupId>
    <artifactId>okhttp</artifactId>
    <version>(insert latest version)</version>
</dependency>

単純なGETの例

Request request = new Request.Builder()
        .url("https://matsumana.info/")
        .build();

OkHttpClient client = new OkHttpClient();
Response response = client.newCall(request).execute();

System.out.println(response.body().string());

実は、HTTP/2で通信するために特別なコードを書く必要はありません。

jetty-alpnというALPN (Application Layer Protocol Negotiation)の実装ライブラリを-Xbootclasspathで指定して実行するだけでHTTP/2で通信するようになります

※Javaのバージョンが古いとハンドシェイク時点でIllegalAccessErrorが発生して動きません。Javaは最新バージョンを使うようにしてください
 どのバージョンから例外が発生せずにきちんと動くのかは調べてませんが、
 1.8u40だとNG、1.8u65だとOKでした

pom.xmlにdependencyを追加します

<dependency>
    <groupId>org.mortbay.jetty.alpn</groupId>
    <artifactId>alpn-boot</artifactId>
    <version>(insert latest version)</version>
</dependency>

実行はこんな感じです

java \
  -Xbootclasspath/p:$HOME/.m2/repository/org/mortbay/jetty/alpn/alpn-boot/8.1.6.v20151105/alpn-boot-8.1.6.v20151105.jar \
  -classpath :$HOME/.m2/repository/com/squareup/okhttp/okhttp/2.6.0/okhttp-2.6.0.jar:$HOME/.m2/repository/com/squareup/okio/okio/1.6.0/okio-1.6.0.jar \
  info.matsumana.Main

Mavenで実行するには、pom.xmlに以下のように書いて、

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.4.0</version>
            <configuration>
                <executable>java</executable>
                <arguments>
                    <argument>
                        -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/alpn/alpn-boot/${alpn-boot.version}/alpn-boot-${alpn-boot.version}.jar
                    </argument>
                    <argument>-classpath</argument>
                    <classpath/>
                    <argument>info.matsumana.Main</argument>
                </arguments>
            </configuration>
        </plugin>
    </plugins>
</build>

$ mvn exec:exec

とすれば良いです


HTTP/2で通信しているかどうか確認するには

ALPN.debug = true;

としてデバッグログを出力するか、

レスポンスヘッダーのOkHttp-Selected-Protocolで確認できます

ALPNのデバッグログには、以下の様に出力されます

[C] ALPN protocols [h2, spdy/3.1, http/1.1] for 174d20a[SSL_NULL_WITH_NULL_NULL: Socket[addr=matsumana.info/45.55.242.211,port=443,localport=59272]]
[C] ALPN protocol 'h2' selected by server for 174d20a[SSL_NULL_WITH_NULL_NULL: Socket[addr=matsumana.info/45.55.242.211,port=443,localport=59272]]

レスポンスヘッダーは以下の様になります

OkHttp-Selected-Protocol: h2


他のライブラリを使ったコードとの比較

比較のためにJAX-RS Clientのコードも載せておきます

JAX-RS ClientでのGET

Client client = ClientBuilder.newClient();
WebTarget target = client.target("https://matsumana.info/");
Response response = target.request().get();

OkHttpでのGET

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
        .url("https://matsumana.info/")
        .build();
Response response = client.newCall(request).execute();

ほとんど一緒ですね


依存ライブラリ

OkHttpは依存ライブラリが少ないです

HTTP/2通信する場合はalpn-bootも必要ですが、
OkHttp自体はOkioの1つしか依存ライブラリがありません


ドキュメント

詳しい使い方はOkHttpのGitHub Wikiを見てください

わかり易くまとまっています




おまけ

HTTP/2とは関係ありませんが、ユニットテストで使うMockWebServerという便利なモックサーバがあるので、あわせてご紹介しておきます

READMEのexampleを読んでもらうのが一番良いのですが簡単な例を書いておきます

pom.xmlにdependencyを追加

<dependency>
    <groupId>com.squareup.okhttp</groupId>
    <artifactId>mockwebserver</artifactId>
    <version>(insert latest version)</version>
    <scope>test</scope>
</dependency>

テスト対象コードの例

public class Get {

    HttpUrl url = HttpUrl.parse("https://matsumana.info/");

    public String request() throws IOException {

        Request request = new Request.Builder()
                .url(url)
                .build();

        OkHttpClient client = new OkHttpClient();
        Response response = client.newCall(request).execute();

        return response.body().string();
    }
}

テストコードの例

public class GetTest {

    Get sut = new Get();

    @Test
    public void test() throws Exception {

        // Start MockWebServer
        MockWebServer server = new MockWebServer();
        server.enqueue(new MockResponse().setBody("hoge"));
        server.start();

        sut.url = server.url("/");
        String body = sut.request();

        assertThat(body, is("hoge"));

        // Shutdown MockWebServer
        server.shutdown();
    }
}

モックサーバにアクセスする前に、モックレスポンスをenqueueしておくという使い方です
簡単に使えて便利だと思います



まとめ

  • HTTP/2で通信出来る
  • 依存ライブラリが少ない
  • MockWebServer便利

今回のソースコードはここに置いてます

ぜひお試しあれー