普段はSpringBootを使った開発をしているためRestTemplateを使うのだが、いざSpringBootに依存しない実装が必要となったときに、Apache HttpClient を使わなくてもJavaの標準機能だけで簡単に実装できるようになったことを恥ずかしながら最近知ったので、昨年Java17がリリースされた今となってはもう古い記事になってしまうが、今更ながらJava標準機能でHttp通信するクライアントプログラムについて簡単にまとめておくことにした。
本記事の前提
- Java11以上であること(Java8ではサポートされてない機能)
- サーバ側のコードは割愛
- Jsonとのマッピング機能はJacksonを想定
受信するデータ型
public class ResponseDto {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "ResponseDto{" +
"message='" + message + '\'' +
'}';
}
}
サンプル実装
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class Java11HttpSample {
private final HttpClient httpClient;
Java11HttpSample() {
this.httpClient = HttpClient.newHttpClient();
}
Java11HttpSample(HttpClient httpClient) {
this.httpClient = httpClient;
}
/**
* JSON文字列をGetするサンプル
*/
public String getJsonString() throws IOException, InterruptedException {
// リクエストを作成
HttpRequest request = HttpRequest.newBuilder(URI.create("http://localhost:8080/sample/get"))
.build();
// 同期API呼び出し
HttpResponse<String> response = this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body()); // 受信したJSON文字列を確認
return response.body();
}
/**
* JSON文字列を受信してオブジェクトにマッピングする。マッピング自体はJacksonで実行する
*/
public ResponseDto getJsonData() throws IOException, InterruptedException {
// リクエストを作成
HttpRequest request = HttpRequest
.newBuilder(URI.create("http://localhost:8080/sample/get"))
.build();
// 同期API呼び出し
HttpResponse<ResponseDto> response = this.httpClient.send(request, responseInfo ->
// 受信データをマッピングするロジック
HttpResponse.BodySubscribers.mapping(
HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8), // JSONを文字列で受信して
(String body) -> { // 受信した文字列をラムダの引数bodyで受け取り
try {
return new ObjectMapper().readValue(body, ResponseDto.class); // Jacksonでオブジェクトに変換
} catch (IOException e) {
throw new UncheckedIOException(e);
}
})
);
System.out.println(response.body()); // body is ResponseDto
return response.body();
}
/**
* Postのサンプル
*/
public void postJson() throws IOException, InterruptedException {
HttpRequest httpRequest = HttpRequest.newBuilder(URI.create("http://localhost:8080/sample/post"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
new ObjectMapper().writeValueAsString(Map.of("key", "value"))))
.build();
HttpResponse<String> httpResponse = this.httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
System.out.println(httpResponse.body());
}
}
テストコード
一般的に、単体テストではHttp通信する処理をモックにすると思うので、上記サンプルをMockitoでモックして(サーバ不要で)単体テストするコードも載せておく。
そのためにjava.net.http.HttpClient
はコンストラクタ等で外部から設定できるように実装しておいたほうがいい。
@ExtendWith(MockitoExtension.class)
public class Java11HttpTest {
@Mock
HttpResponse<String> httpResponseString;
@Mock
HttpResponse<ResponseDto> httpResponseJson;
@Spy
HttpClient httpClient;
@Test
public void getJsonStringTest() throws IOException, InterruptedException {
Mockito.when(this.httpResponseString.body()).thenReturn("hello");
Mockito.when(this.httpClient.send(Mockito.any(), Mockito.any(HttpResponse.BodyHandlers.ofString().getClass())))
.thenReturn(this.httpResponseString);
Assertions.assertEquals("hello", new Java11HttpSample(this.httpClient).getJsonString());
}
@Test
public void getJsonDataTest() throws IOException, InterruptedException {
final ResponseDto expected = new ResponseDto();
Mockito.when(this.httpResponseJson.body()).thenReturn(expected);
Mockito.when(this.httpClient.send(Mockito.any(), Mockito.any(HttpResponse.BodyHandler.class)))
.thenReturn(this.httpResponseJson);
Assertions.assertEquals(expected, new Java11HttpSample(this.httpClient).getJsonData());
}
}
さいごに
Java8など古いJavaではjava.net.http
パッケージが提供されていないため、Apache HttpClientなどの外部ライブラリを利用したほうが簡単に実装できるが、Java11以上であれば標準機能だけで、かなりシンプルに実装できる。
また、今回は基本的なGET/POSTのサンプルのみだが、WebSocketやHTTP/2対応した通信も実装できる(詳細は以下Javadoc等を確認)ため、特にこだわりがなければ今後は標準機能の実装方法を覚えていったほうが汎用性が高いのではないかと感じた。