Java HttpClient
Java 11以降で標準提供されるHTTPクライアントAPI。モダンで流暢なAPIデザイン、HTTP/2サポート、非同期・同期両対応。外部依存関係不要でJDKに内蔵され、WebSocketサポートやCompletableFutureベースの非同期処理を提供。
GitHub概要
openjdk/jdk
JDK main-line development https://openjdk.org/projects/jdk
スター21,352
ウォッチ355
フォーク6,084
作成日:2018年9月17日
言語:Java
ライセンス:GNU General Public License v2.0
トピックス
javajvmopenjdk
スター履歴
データ取得日時: 2025/7/18 01:39
ライブラリ
Java HttpClient
概要
Java HttpClientは、Java 11で正式導入された「java.net.http」パッケージの標準HTTPクライアントライブラリです。Java 9でインキュベーション機能として登場し、HTTP/1.1とHTTP/2の両方をサポート、同期・非同期プログラミングモデルに対応した現代的なHTTP通信ソリューション。従来のHttpURLConnectionに代わる公式後継として、リアクティブストリーム対応、ビルダーパターン採用、SSL/TLS完全サポートを特徴とし、外部依存なしでプロダクション品質のHTTP通信を実現します。
詳細
Java HttpClient 2025年版は、Java標準ライブラリとして成熟度と安定性を提供する現代的なHTTP実装です。HTTP/2プロトコルの完全サポートにより効率的な多重化通信を実現し、CompletableFutureベースの非同期API、リアクティブストリームによるボディ処理、自動リダイレクト・認証・プロキシサポートを包括的に提供。JDK標準搭載により外部依存関係が不要で、企業環境での採用が容易。Spring Boot、Quarkus等の主要フレームワークでも推奨HTTP客户端として位置づけられています。
主な特徴
- JDK標準搭載: Java 11以降で外部依存なしで利用可能
- HTTP/2完全対応: 多重化、サーバープッシュ、ヘッダー圧縮サポート
- 同期・非同期両対応: CompletableFutureによる柔軟なプログラミングモデル
- リアクティブストリーム: 効率的なリクエスト・レスポンスボディ処理
- ビルダーパターン: 直感的で型安全なAPI設計
- 包括的セキュリティ: SSL/TLS、認証、証明書検証の完全サポート
メリット・デメリット
メリット
- JDK標準ライブラリで外部依存関係なし、長期サポート保証
- HTTP/2対応による高性能な並列通信とリソース効率の向上
- CompletableFutureによる自然な非同期プログラミング体験
- 型安全で保守性の高いビルダーパターンAPI
- 企業環境での採用が容易、セキュリティ監査もパス
- Spring Boot等の主要フレームワークでの推奨クライアント
デメリット
- Java 11以降限定でレガシー環境では利用不可
- サードパーティライブラリと比較して高レベル機能が限定的
- HTTPSプロキシ設定など一部の詳細設定が複雑
- ストリーミング処理APIがやや低レベル
- デバッグやログ出力機能が基本的
- リトライ・サーキットブレーカー等は自前実装が必要
参考ページ
書き方の例
基本セットアップと依存関係
// Java 11以降では追加依存関係は不要
// module-info.java (モジュールシステム使用時)
module myapp {
requires java.net.http;
}
# Java環境の確認
java -version # Java 11以降であることを確認
# プロジェクト作成例(Mavenの場合)
mvn archetype:generate -DgroupId=com.example.httpclient \
-DartifactId=java-httpclient-demo \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
# JDK 11以降を設定(pom.xml)
<!-- pom.xml -->
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
基本的なHTTPリクエスト(GET・POST・PUT・DELETE)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
public class BasicHttpClientExample {
public static void main(String[] args) throws Exception {
// HttpClientの作成
HttpClient client = HttpClient.newHttpClient();
// 基本的なGETリクエスト
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("Status Code: " + response.statusCode());
System.out.println("Headers: " + response.headers().map());
System.out.println("Body: " + response.body());
}
// カスタム設定でのHttpClient作成
public static void customHttpClientExample() throws Exception {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // HTTP/2を優先
.followRedirects(HttpClient.Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();
// クエリパラメータ付きGETリクエスト
String url = "https://api.example.com/users?page=1&limit=10&sort=created_at";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept", "application/json")
.header("User-Agent", "MyApp/1.0 (Java HttpClient)")
.timeout(Duration.ofMinutes(1))
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
System.out.println("Success: " + response.body());
} else {
System.out.println("Error: " + response.statusCode());
}
}
// POSTリクエスト(JSON送信)
public static void postJsonExample() throws Exception {
HttpClient client = HttpClient.newHttpClient();
String jsonBody = """
{
"name": "田中太郎",
"email": "[email protected]",
"age": 30
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer your-token")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 201) {
System.out.println("ユーザー作成完了: " + response.body());
} else {
System.out.println("エラー: " + response.statusCode() + " - " + response.body());
}
}
// PUTとDELETEリクエスト
public static void putDeleteExample() throws Exception {
HttpClient client = HttpClient.newHttpClient();
// PUTリクエスト(データ更新)
String updatedJsonBody = """
{
"name": "田中次郎",
"email": "[email protected]",
"age": 31
}
""";
HttpRequest putRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users/123"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer your-token")
.PUT(HttpRequest.BodyPublishers.ofString(updatedJsonBody))
.build();
HttpResponse<String> putResponse = client.send(putRequest,
HttpResponse.BodyHandlers.ofString());
System.out.println("更新ステータス: " + putResponse.statusCode());
// DELETEリクエスト
HttpRequest deleteRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users/123"))
.header("Authorization", "Bearer your-token")
.DELETE()
.build();
HttpResponse<String> deleteResponse = client.send(deleteRequest,
HttpResponse.BodyHandlers.ofString());
if (deleteResponse.statusCode() == 204) {
System.out.println("ユーザー削除完了");
}
}
}
非同期リクエストとCompletableFuture
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.List;
import java.util.stream.Collectors;
public class AsyncHttpClientExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 単一の非同期リクエスト
asyncSingleRequest(client);
// 複数の並列非同期リクエスト
asyncMultipleRequests(client);
// 非同期チェイン処理
asyncChainedRequests(client);
}
// 単一非同期リクエスト
public static void asyncSingleRequest(HttpClient client) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Accept", "application/json")
.GET()
.build();
CompletableFuture<HttpResponse<String>> futureResponse =
client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
// 非同期処理の結果を待機
HttpResponse<String> response = futureResponse.get();
System.out.println("非同期レスポンス: " + response.statusCode());
// またはコールバックで処理
futureResponse.thenAccept(resp -> {
System.out.println("コールバック処理: " + resp.body());
}).join();
}
// 複数並列非同期リクエスト
public static void asyncMultipleRequests(HttpClient client) throws Exception {
List<String> urls = List.of(
"https://api.example.com/users",
"https://api.example.com/posts",
"https://api.example.com/comments",
"https://api.example.com/categories"
);
// 並列リクエストの開始
List<CompletableFuture<HttpResponse<String>>> futures = urls.stream()
.map(url -> HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept", "application/json")
.GET()
.build())
.map(request -> client.sendAsync(request, HttpResponse.BodyHandlers.ofString()))
.collect(Collectors.toList());
// 全ての完了を待機
CompletableFuture<List<HttpResponse<String>>> allFutures =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
List<HttpResponse<String>> responses = allFutures.get();
for (int i = 0; i < responses.size(); i++) {
HttpResponse<String> response = responses.get(i);
System.out.println("URL " + urls.get(i) + " - Status: " + response.statusCode());
}
}
// 非同期チェイン処理
public static void asyncChainedRequests(HttpClient client) throws Exception {
// 最初のリクエスト
HttpRequest initialRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/auth/login"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("""
{"username": "user", "password": "pass"}
"""))
.build();
// チェイン処理:ログイン → トークン取得 → 保護されたリソースへアクセス
CompletableFuture<String> result = client
.sendAsync(initialRequest, HttpResponse.BodyHandlers.ofString())
.thenCompose(loginResponse -> {
if (loginResponse.statusCode() == 200) {
// ログイン成功、トークンを抽出(実際にはJSONパースが必要)
String token = extractTokenFromResponse(loginResponse.body());
// トークンを使用して保護されたリソースにアクセス
HttpRequest protectedRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/protected-resource"))
.header("Authorization", "Bearer " + token)
.GET()
.build();
return client.sendAsync(protectedRequest, HttpResponse.BodyHandlers.ofString());
} else {
// ログイン失敗の場合の処理
return CompletableFuture.failedFuture(
new RuntimeException("Login failed: " + loginResponse.statusCode())
);
}
})
.thenApply(protectedResponse -> {
if (protectedResponse.statusCode() == 200) {
return protectedResponse.body();
} else {
throw new RuntimeException("Protected resource access failed: " +
protectedResponse.statusCode());
}
})
.exceptionally(throwable -> {
System.err.println("チェイン処理でエラー: " + throwable.getMessage());
return "Error occurred";
});
String finalResult = result.get();
System.out.println("チェイン処理結果: " + finalResult);
}
private static String extractTokenFromResponse(String body) {
// 実際にはJSONライブラリを使用してパース
// ここではサンプルのためハードコーディング
return "sample-jwt-token";
}
}
認証・セキュリティ・カスタム設定
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.ProxySelector;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.Base64;
import javax.net.ssl.SSLContext;
public class SecurityHttpClientExample {
public static void main(String[] args) throws Exception {
// Basic認証の例
basicAuthenticationExample();
// Bearer Token認証
bearerTokenExample();
// プロキシ設定
proxyConfigurationExample();
// SSL/TLS設定
sslConfigurationExample();
// カスタム認証
customAuthenticationExample();
}
// Basic認証
public static void basicAuthenticationExample() throws Exception {
HttpClient client = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("username", "password".toCharArray());
}
})
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/private"))
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("Basic認証ステータス: " + response.statusCode());
// または手動でBasic認証ヘッダー設定
String auth = "username:password";
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
HttpRequest manualBasicRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/private"))
.header("Authorization", "Basic " + encodedAuth)
.GET()
.build();
HttpResponse<String> manualResponse = client.send(manualBasicRequest,
HttpResponse.BodyHandlers.ofString());
System.out.println("手動Basic認証ステータス: " + manualResponse.statusCode());
}
// Bearer Token認証
public static void bearerTokenExample() throws Exception {
HttpClient client = HttpClient.newHttpClient();
String jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // 実際のJWTトークン
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/protected"))
.header("Authorization", "Bearer " + jwtToken)
.header("Accept", "application/json")
.header("User-Agent", "MyApp/1.0 (Java HttpClient)")
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("Bearer認証ステータス: " + response.statusCode());
// APIキー認証の例
HttpRequest apiKeyRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.header("X-API-Key", "your-api-key-here")
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> apiKeyResponse = client.send(apiKeyRequest,
HttpResponse.BodyHandlers.ofString());
System.out.println("APIキー認証ステータス: " + apiKeyResponse.statusCode());
}
// プロキシ設定
public static void proxyConfigurationExample() throws Exception {
// HTTPプロキシの設定
HttpClient client = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080)))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("プロキシ経由ステータス: " + response.statusCode());
// 認証付きプロキシ(Authenticatorと組み合わせ)
HttpClient authProxyClient = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress("authenticated-proxy.example.com", 8080)))
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (getRequestorType() == RequestorType.PROXY) {
return new PasswordAuthentication("proxyuser", "proxypass".toCharArray());
}
return null;
}
})
.build();
}
// SSL/TLS設定
public static void sslConfigurationExample() throws Exception {
// デフォルトSSLContext使用
HttpClient defaultSslClient = HttpClient.newBuilder()
.sslContext(SSLContext.getDefault())
.build();
// カスタムSSLContext(例:特定のTLSバージョン指定)
try {
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, null, null);
HttpClient customSslClient = HttpClient.newBuilder()
.sslContext(sslContext)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://secure-api.example.com/data"))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = customSslClient.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("カスタムSSL ステータス: " + response.statusCode());
} catch (NoSuchAlgorithmException e) {
System.err.println("TLSv1.3が利用できません: " + e.getMessage());
}
}
// カスタム認証とヘッダー
public static void customAuthenticationExample() throws Exception {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
// カスタムヘッダーとタイムアウト設定
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://secure-api.example.com/data"))
.header("Accept", "application/json")
.header("Accept-Language", "ja-JP,en-US")
.header("X-API-Version", "v2")
.header("X-Request-ID", java.util.UUID.randomUUID().toString())
.header("Authorization", "Bearer your-jwt-token")
.header("User-Agent", "MyApp/1.0 (Java " + System.getProperty("java.version") + ")")
.timeout(Duration.ofSeconds(60))
.GET()
.build();
try {
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("カスタム認証ステータス: " + response.statusCode());
System.out.println("レスポンスヘッダー: " + response.headers().map());
// Cookie処理の例
response.headers().allValues("Set-Cookie").forEach(cookie -> {
System.out.println("受信Cookie: " + cookie);
});
} catch (Exception e) {
System.err.println("リクエストエラー: " + e.getMessage());
}
}
}
エラーハンドリングとリトライ機能
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpConnectTimeoutException;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpTimeoutException;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
public class ErrorHandlingHttpClientExample {
private static final HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
public static void main(String[] args) {
// 基本的なエラーハンドリング
basicErrorHandling();
// リトライ機能付きリクエスト
retryableRequest();
// 非同期エラーハンドリング
asyncErrorHandling();
// サーキットブレーカーパターン
circuitBreakerExample();
}
// 基本的なエラーハンドリング
public static void basicErrorHandling() {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.timeout(Duration.ofSeconds(30))
.GET()
.build();
try {
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
// ステータスコード別処理
switch (response.statusCode()) {
case 200:
System.out.println("成功: " + response.body());
break;
case 401:
System.out.println("認証エラー: トークンを確認してください");
break;
case 403:
System.out.println("権限エラー: アクセス権限がありません");
break;
case 404:
System.out.println("見つかりません: リソースが存在しません");
break;
case 429:
System.out.println("レート制限: しばらく待ってから再試行してください");
break;
case 500:
case 502:
case 503:
case 504:
System.out.println("サーバーエラー: " + response.statusCode());
break;
default:
System.out.println("予期しないステータス: " + response.statusCode());
}
} catch (HttpConnectTimeoutException e) {
System.err.println("接続タイムアウト: " + e.getMessage());
} catch (HttpTimeoutException e) {
System.err.println("リクエストタイムアウト: " + e.getMessage());
} catch (ConnectException e) {
System.err.println("接続エラー: サーバーに接続できません - " + e.getMessage());
} catch (SocketTimeoutException e) {
System.err.println("ソケットタイムアウト: " + e.getMessage());
} catch (IOException e) {
System.err.println("IOエラー: " + e.getMessage());
} catch (InterruptedException e) {
System.err.println("処理が中断されました: " + e.getMessage());
Thread.currentThread().interrupt(); // 割り込み状態を復元
} catch (Exception e) {
System.err.println("予期しないエラー: " + e.getMessage());
}
}
// リトライ機能付きリクエスト
public static void retryableRequest() {
String url = "https://api.example.com/unstable-endpoint";
int maxRetries = 3;
Duration baseDelay = Duration.ofMillis(500);
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(10))
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
System.out.println("リトライ成功 (試行 " + (attempt + 1) + "): " + response.body());
return; // 成功時は終了
} else if (isRetryableStatus(response.statusCode()) && attempt < maxRetries) {
System.out.println("試行 " + (attempt + 1) + " 失敗 (ステータス: " +
response.statusCode() + "). 再試行します...");
} else {
System.err.println("最終的に失敗 (ステータス: " + response.statusCode() + ")");
return;
}
} catch (Exception e) {
if (attempt < maxRetries && isRetryableException(e)) {
System.out.println("試行 " + (attempt + 1) + " で例外発生: " + e.getMessage() + ". 再試行します...");
} else {
System.err.println("最終的に失敗: " + e.getMessage());
return;
}
}
// 指数バックオフでの待機
if (attempt < maxRetries) {
try {
Duration delay = baseDelay.multipliedBy((long) Math.pow(2, attempt));
// ジッターを追加してサーバー負荷を分散
long jitter = ThreadLocalRandom.current().nextLong(0, delay.toMillis() / 4);
Thread.sleep(delay.toMillis() + jitter);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
System.err.println("リトライ待機中に中断されました");
return;
}
}
}
}
private static boolean isRetryableStatus(int statusCode) {
return statusCode == 429 || // Too Many Requests
statusCode == 500 || // Internal Server Error
statusCode == 502 || // Bad Gateway
statusCode == 503 || // Service Unavailable
statusCode == 504; // Gateway Timeout
}
private static boolean isRetryableException(Exception e) {
return e instanceof HttpTimeoutException ||
e instanceof HttpConnectTimeoutException ||
e instanceof ConnectException ||
e instanceof SocketTimeoutException;
}
// 非同期エラーハンドリング
public static void asyncErrorHandling() {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/async-data"))
.timeout(Duration.ofSeconds(30))
.GET()
.build();
CompletableFuture<String> result = client
.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(response -> {
if (response.statusCode() == 200) {
return response.body();
} else {
throw new RuntimeException("HTTP Error: " + response.statusCode());
}
})
.exceptionally(throwable -> {
if (throwable.getCause() instanceof HttpTimeoutException) {
System.err.println("非同期リクエストタイムアウト");
return "TIMEOUT_ERROR";
} else if (throwable.getCause() instanceof ConnectException) {
System.err.println("非同期接続エラー");
return "CONNECTION_ERROR";
} else {
System.err.println("非同期処理エラー: " + throwable.getMessage());
return "UNKNOWN_ERROR";
}
})
.orTimeout(Duration.ofSeconds(45)); // 全体タイムアウト
try {
String responseBody = result.get();
System.out.println("非同期結果: " + responseBody);
} catch (Exception e) {
System.err.println("非同期処理例外: " + e.getMessage());
}
}
// シンプルなサーキットブレーカーパターン
static class SimpleCircuitBreaker {
private int failureCount = 0;
private long lastFailureTime = 0;
private final int threshold;
private final Duration timeout;
private boolean isOpen = false;
public SimpleCircuitBreaker(int threshold, Duration timeout) {
this.threshold = threshold;
this.timeout = timeout;
}
public boolean canExecute() {
if (!isOpen) {
return true;
}
// タイムアウト後にリセット試行
if (System.currentTimeMillis() - lastFailureTime > timeout.toMillis()) {
isOpen = false;
failureCount = 0;
return true;
}
return false;
}
public void recordSuccess() {
failureCount = 0;
isOpen = false;
}
public void recordFailure() {
failureCount++;
lastFailureTime = System.currentTimeMillis();
if (failureCount >= threshold) {
isOpen = true;
}
}
public boolean isOpen() {
return isOpen;
}
}
public static void circuitBreakerExample() {
SimpleCircuitBreaker circuitBreaker = new SimpleCircuitBreaker(3, Duration.ofMinutes(1));
for (int i = 0; i < 10; i++) {
if (!circuitBreaker.canExecute()) {
System.out.println("サーキットブレーカーが開いています。リクエストをスキップ。");
continue;
}
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/unreliable"))
.timeout(Duration.ofSeconds(5))
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
circuitBreaker.recordSuccess();
System.out.println("成功: " + response.body());
} else {
circuitBreaker.recordFailure();
System.out.println("失敗 (ステータス: " + response.statusCode() + ")");
}
} catch (Exception e) {
circuitBreaker.recordFailure();
System.err.println("例外発生: " + e.getMessage());
}
// 次のリクエストまで少し待機
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
ファイルアップロード・ダウンロードとストリーミング
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Flow;
public class FileStreamingHttpClientExample {
private static final HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
public static void main(String[] args) throws Exception {
// ファイルアップロード
fileUploadExample();
// ファイルダウンロード
fileDownloadExample();
// ストリーミングアップロード
streamingUploadExample();
// ストリーミングダウンロード
streamingDownloadExample();
}
// ファイルアップロード
public static void fileUploadExample() throws Exception {
Path filePath = Path.of("upload-file.txt");
// アップロード用ファイルの作成
Files.writeString(filePath, "これはアップロードテスト用のファイルです。\nMultiple lines content...");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/post"))
.header("Content-Type", "text/plain; charset=UTF-8")
.header("Authorization", "Bearer your-token")
.POST(HttpRequest.BodyPublishers.ofFile(filePath))
.timeout(Duration.ofMinutes(5))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("ファイルアップロード ステータス: " + response.statusCode());
System.out.println("レスポンス: " + response.body());
// マルチパート形式でのファイルアップロード
multipartFileUpload();
}
// マルチパート形式でのファイルアップロード
public static void multipartFileUpload() throws Exception {
Path filePath = Path.of("multipart-file.txt");
Files.writeString(filePath, "マルチパートアップロードテストファイル");
String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();
String multipartBody = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n" +
"Content-Type: text/plain\r\n\r\n" +
Files.readString(filePath) + "\r\n" +
"--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"description\"\r\n\r\n" +
"テストファイルの説明\r\n" +
"--" + boundary + "--\r\n";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/post"))
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofString(multipartBody))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("マルチパートアップロード ステータス: " + response.statusCode());
}
// ファイルダウンロード
public static void fileDownloadExample() throws Exception {
Path downloadPath = Path.of("downloaded-file.txt");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/bytes/1024"))
.header("Accept", "*/*")
.GET()
.build();
// ファイルに直接ダウンロード
HttpResponse<Path> response = client.send(request,
HttpResponse.BodyHandlers.ofFile(downloadPath));
System.out.println("ダウンロード ステータス: " + response.statusCode());
System.out.println("ファイル保存先: " + response.body());
System.out.println("ファイルサイズ: " + Files.size(downloadPath) + " bytes");
// 非同期ダウンロード
asyncFileDownload();
}
// 非同期ファイルダウンロード
public static void asyncFileDownload() throws Exception {
Path asyncDownloadPath = Path.of("async-downloaded-file.dat");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/bytes/2048"))
.GET()
.build();
CompletableFuture<Path> downloadFuture = client
.sendAsync(request, HttpResponse.BodyHandlers.ofFile(asyncDownloadPath))
.thenApply(HttpResponse::body);
// ダウンロード完了を待機
Path downloadedFile = downloadFuture.get();
System.out.println("非同期ダウンロード完了: " + downloadedFile);
System.out.println("ファイルサイズ: " + Files.size(downloadedFile) + " bytes");
}
// ストリーミングアップロード
public static void streamingUploadExample() throws Exception {
// カスタムBodyPublisher for streaming
HttpRequest.BodyPublisher streamingPublisher = new HttpRequest.BodyPublisher() {
@Override
public long contentLength() {
return -1; // 不明なサイズ
}
@Override
public void subscribe(Flow.Subscriber<? super java.nio.ByteBuffer> subscriber) {
// ストリーミングデータの送信ロジック
subscriber.onSubscribe(new Flow.Subscription() {
private boolean cancelled = false;
private int sentChunks = 0;
@Override
public void request(long n) {
if (cancelled) return;
try {
for (int i = 0; i < n && sentChunks < 5; i++, sentChunks++) {
String chunk = "Streaming chunk " + sentChunks + "\n";
subscriber.onNext(java.nio.ByteBuffer.wrap(chunk.getBytes()));
}
if (sentChunks >= 5) {
subscriber.onComplete();
}
} catch (Exception e) {
subscriber.onError(e);
}
}
@Override
public void cancel() {
cancelled = true;
}
});
}
};
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/post"))
.header("Content-Type", "text/plain")
.header("Transfer-Encoding", "chunked")
.POST(streamingPublisher)
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("ストリーミングアップロード ステータス: " + response.statusCode());
}
// ストリーミングダウンロード
public static void streamingDownloadExample() throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/stream/5"))
.GET()
.build();
// カスタムBodyHandlerでストリーミング処理
HttpResponse<Void> response = client.send(request,
HttpResponse.BodyHandlers.fromSubscriber(new Flow.Subscriber<java.util.List<java.nio.ByteBuffer>>() {
private Flow.Subscription subscription;
private int receivedChunks = 0;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(1); // 最初のチャンクを要求
}
@Override
public void onNext(java.util.List<java.nio.ByteBuffer> buffers) {
receivedChunks++;
int totalBytes = buffers.stream().mapToInt(java.nio.ByteBuffer::remaining).sum();
System.out.println("チャンク " + receivedChunks + " 受信: " + totalBytes + " bytes");
// データ処理(この例では内容を出力)
buffers.forEach(buffer -> {
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
System.out.print(new String(bytes));
});
subscription.request(1); // 次のチャンクを要求
}
@Override
public void onError(Throwable throwable) {
System.err.println("ストリーミングエラー: " + throwable.getMessage());
}
@Override
public void onComplete() {
System.out.println("\nストリーミング完了 (総チャンク数: " + receivedChunks + ")");
}
}));
System.out.println("ストリーミングダウンロード ステータス: " + response.statusCode());
}
// 進捗表示付きダウンロード
public static void downloadWithProgress() throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/bytes/10240")) // 10KB
.GET()
.build();
HttpResponse<InputStream> response = client.send(request,
HttpResponse.BodyHandlers.ofInputStream());
if (response.statusCode() == 200) {
Path outputPath = Path.of("progress-download.dat");
try (InputStream inputStream = response.body()) {
long totalSize = response.headers().firstValueAsLong("Content-Length").orElse(-1L);
long downloadedBytes = 0;
byte[] buffer = new byte[1024];
int bytesRead;
Files.deleteIfExists(outputPath);
while ((bytesRead = inputStream.read(buffer)) != -1) {
Files.write(outputPath, buffer, 0, bytesRead,
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
downloadedBytes += bytesRead;
if (totalSize > 0) {
double progress = (double) downloadedBytes / totalSize * 100;
System.out.printf("\rダウンロード進捗: %.1f%% (%d/%d bytes)",
progress, downloadedBytes, totalSize);
} else {
System.out.printf("\rダウンロード: %d bytes", downloadedBytes);
}
}
System.out.println("\nダウンロード完了: " + outputPath);
}
}
}
}