OkHttpを使ってHTTP/2で通信する + おまけ
この記事は、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について詳しく知りたい人は以下を読むと良いと思います
- RFC7540 - Hypertext Transfer Protocol Version 2 (HTTP/2)
- RFC7541 - HPACK: Header Compression for HTTP/2
- HTTP/2 Japan Local Activity
- Block Rockin’ Codes - HTTP2 の RFC7540 が公開されました
- YAPC::Asia Tokyo 2015 - HTTP/2時代のウェブサイト設計
- YAPC::Asia Tokyo 2015 - HTTP2 時代の Web
- 人間とウェブの未来 - HTTP/2とmrubyの可能性についてお話してきた
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便利
今回のソースコードはここに置いてます
ぜひお試しあれー