こんにちは
maron8676です。
梅雨に入ってじめじめした日が続いていますね。
今回はwiremockを使ってHTTP/2のモックサーバを作成する方法を紹介します。
HTTP/2という比較的新しいプロトコルであっても、
モックサーバを利用してテストコードも書いていきたいですね。
背景
HTTP/2について
2015年にRFCで承認され、最近普及してきているHTTPプロトコルです。*1
HTTP/2には、HTTP/2 over TLS(h2)とHTTP/2 over TCP(h2c)という2つの方式があります。
h2ではサーバ証明書の設定など、HTTPバージョン以外の話も多くなってしまうため、
本記事ではh2cについて取り扱います*2。
モックサーバ
本記事では、外部サービスや、他の内部サービスの処理を模倣するサーバを指します。
システム開発では、外部APIと連携したり、内部でサービスを分割して開発することがたびたびあります。
しかし、モックサーバがないと実施したいテストを実行できない問題が発生します。
例えば、図1のような処理をテストする場合を考えます。単純に実行してテストできればよいのですが、
外部APIが利用できない状況だと、図中の処理3といった後続処理で値が使用できずエラーになる状況が出てきます。
モックサーバを利用することで、外部サービスや他の内部サービスを直接利用できない状況でも、システム連携を想定したテストを行えるため、品質の高い状態で開発を進めることができます。
モックサーバのHTTP/2対応状況
モックサーバを作成するためのツールとしては以下が挙げられますが、
2021/06/05現在、HTTP/2に対応しているのはWiremockのみとなっています。
そのため、本記事ではWiremockを使ってモックサーバを作成したいと思います。*3
1. Wiremock
2. MockServer
3. Karate
HTTP/2のモックサーバ作成
では、Wiremockでモックサーバ作成を作成するコードを見ていきましょう。
1. Spring Initializrでプロジェクトを作成する
今回は、Maven Project、Packaging JarのJavaプロジェクトを作成します。
2. DemoApplication.javaを書き換える
以下のコードに書き換えます。
createHttpConnectorメソッドをオーバーライドして、h2cのコネクタを追加で指定してあげることによってh2cで通信できるサーバとなります。
3. ビルドしてjarファイルを作成する
mvn clean build
でモックサーバのjarファイルを作成できます。
Wiremock公式からダウンロードできるstandaloneのjarファイル(公式jarファイル)はh2c設定がされていないため、
自分でjarファイルを作成する必要があります。
4. モック動作を設定する
作成したjarは、公式jarファイルと同じようにmappingsディレクトリに配置されたjsonファイルを読み取ってモック動作を変更できます。
公式ドキュメントを見て、必要な動作を定義しましょう。
package com.example.demo; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.common.ConsoleNotifier; import com.github.tomakehurst.wiremock.common.JettySettings; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.jetty94.Jetty94HttpServer; import org.apache.commons.lang3.StringUtils; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.io.NetworkTrafficListener; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.ServerConnector; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import java.util.List; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; @SpringBootApplication public class DemoApplication implements ApplicationRunner { public static void main(String[] args) { SpringApplication.run(WiremockHttp2Application.class, args); } @Override public void run(ApplicationArguments args) { boolean hasPort = args.containsOption("port"); List<String> ports = args.getOptionValues("port"); boolean hasDirectory = args.containsOption("rootDir"); List<String> dirs = args.getOptionValues("rootDir"); if (!hasPort || ports.size() == 0 || StringUtils.isBlank(ports.get(0))) { System.out.println("--portでポート番号を指定してください。"); return; } if (!hasDirectory || dirs.size() == 0 || StringUtils.isBlank(dirs.get(0))) { System.out.println("--rootDirでルートディレクトリを指定してください。"); return; } int port = Integer.parseInt(ports.get(0)); WireMockConfiguration configuration = wireMockConfig().port(port); configuration.withRootDirectory(dirs.get(0)); configuration = configuration.notifier(new ConsoleNotifier(true)); configuration = configuration.httpServerFactory((options, adminRequestHandler, stubRequestHandler) -> new Jetty94HttpServer(options, adminRequestHandler, stubRequestHandler) { @Override protected ServerConnector createHttpConnector( String bindAddress, int port, JettySettings jettySettings, NetworkTrafficListener listener) { HttpConfiguration httpConfig = createHttpConfig(jettySettings); HttpConnectionFactory http11 = new HttpConnectionFactory( httpConfig); HTTP2CServerConnectionFactory h2c = new HTTP2CServerConnectionFactory( httpConfig); ServerConnector connector = createServerConnector(bindAddress, jettySettings, port, listener, http11, h2c); return connector; } }); WireMockServer server = new WireMockServer(configuration); server.start(); } }
動作確認
これで、図1における外部APIを含めたテストをするためのモックサーバを作成できました。
サーバにリクエストをするプログラムの代わりに、curlコマンドでリクエストを送って動作確認してみましょう。
今回はサンプルとして、以下のモック定義を準備しています。
これは公式ドキュメントで紹介されている定義です。
/some/thingにGETリクエストがあった際に、text/plainでHello world!とレスポンスを返します。
{ "request": { "method": "GET", "url": "/some/thing" }, "response": { "status": 200, "body": "Hello world!", "headers": { "Content-Type": "text/plain" } } }
curlコマンドでは --http2 を付けることでHTTP/2でのリクエストとなります。
$ curl -v --http2 localhost:8080/some/thing * Trying 127.0.0.1:8080... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 8080 (#0) ----------1---------- > GET /some/thing HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.68.0 > Accept: */* > Connection: Upgrade, HTTP2-Settings > Upgrade: h2c > HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA > * Mark bundle as not supporting multiuse ----------2---------- < HTTP/1.1 101 Switching Protocols * Received 101 * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * Connection state changed (MAX_CONCURRENT_STREAMS == 128)! ----------3---------- < HTTP/2 200 < content-type: text/plain < matched-stub-id: 6fc1a757-7c85-4315-8f4f-cc52b41aadfa < vary: Accept-Encoding, User-Agent < * Connection #0 to host localhost left intact Hello world!
実行時ログの説明
各部分で何が起こっているのか見ていきましょう。
1. クライアント側からは、Upgradeヘッダにh2cを設定することで、
プロトコルをh2cにアップグレードできないかサーバに問い合わせています。
2. サーバはHTTP/1.1 101 Switching Protocolsを返すことで、
HTTP/2へのアップグレードを行ったことをクライアント側に通知しています。
3. HTTP/2 200 の表示からHTTP/2で通信していることを確認できます。
HTTP/2から、ヘッダのフィールド名が小文字で統一されましたが、
コンソール出力からもcontent-typeのフィールド名が小文字になっていることを確認できます。
---1---などは説明のための目印として挿入したもので、実際に出力されたログではありません。
まとめ
HTTP/2の一方式であるh2cに対応したモックサーバの作成方法を紹介しました。
プロダクトコードと並行してテストコードも実装し、品質を上げていけるとよいですね。
また、未検証のため記載していませんが、Wiremockはh2にも対応しているようなので試してみようと思います。
Acroquest Technologyでは、キャリア採用を行っています。
- ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
- Elasticsearch等を使ったデータ収集/分析/可視化
- マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
- 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。エンジニア募集!100%オンライン、好きな場所で働けます! - Acroquest Technology株式会社のエンジニアリングの求人 - Wantedlywww.wantedly.com
*1:https://www.rfc-editor.org/rfc/rfc7540.txt
*2:主要ブラウザはh2しか対応していないのですが、システム内の内部通信など、h2cを利用する状況もあると考えています
*3:MockServerとKarateでは、HTTP/2対応は提案段階となっています。
MockServer https://trello.com/b/dsfTCP46/mockserver
Karate https://github.com/intuit/karate/projects/3