HTTP API
HTTP 模組提供 HTTP 客戶端功能,基於 Apache HttpClient 實作。
套件: io.leandev.appfuse.http
核心類別
StandardHttpClient
標準 HTTP 客戶端。
public class StandardHttpClient implements AutoCloseable {
StandardHttpClient()
StandardHttpClient(PoolingHttpClientConnectionManager connectionManager)
StandardHttpClient(PoolingHttpClientConnectionManager connectionManager,
RequestConfig requestConfig)
}
主要方法
| 方法 | 說明 |
|---|---|
execute(ClassicHttpRequest) | 執行請求 |
execute(ClassicHttpRequest, int) | 執行請求(指定重試次數) |
setGateway(URL) | 設定 API 閘道 |
addInterceptor(HttpClientInterceptor) | 新增攔截器 |
setAuthenticator(Supplier<AccessToken>) | 設定認證器 |
setRetries(int) | 設定重試次數 |
getTotalStats() | 取得統計資訊 |
close() | 關閉客戶端 |
StandardHttpClientBuilder
使用 Builder 建構 HTTP 客戶端。
方法列表
| 方法 | 說明 |
|---|---|
withMaxTotal(int) | 最大連線數 |
withDefaultMaxPerRoute(int) | 每路由最大連線數 |
withTimeout(int) | 超時時間(秒) |
withAuthenticator(Supplier<AccessToken>) | 認證器 |
usingGateway(URL) | API 閘道 |
disableSSLVerification() | 停用 SSL 驗證 |
withInterceptor(HttpClientInterceptor) | 新增攔截器 |
withLogging() | 啟用日誌 |
withLogging(HttpLogHandler, boolean, boolean, boolean) | 自訂日誌 |
build() | 建構客戶端 |
預設配置
| 項目 | 預設值 |
|---|---|
| 連線超時 | 60 秒 |
| 請求超時 | 60 秒 |
| 最大連線數 | 100 |
| 每路由最大連線數 | 100 |
| SSL 驗證 | 啟用 |
使用範例
基本用法
StandardHttpClient client = StandardHttpClient.builder()
.withTimeout(30)
.withMaxTotal(50)
.build();
try {
HttpGet request = new HttpGet("https://api.example.com/users");
ClassicHttpResponse response = client.execute(request);
int status = response.getCode();
String body = EntityUtils.toString(response.getEntity());
} finally {
client.close();
}
使用 API 閘道
StandardHttpClient client = StandardHttpClient.builder()
.usingGateway(new URL("https://gateway.example.com"))
.withTimeout(30)
.build();
// 請求會透過閘道發送
HttpGet request = new HttpGet("/api/v1/users");
ClassicHttpResponse response = client.execute(request);
使用認證器
StandardHttpClient client = StandardHttpClient.builder()
.withAuthenticator(() -> {
// 取得或刷新 Access Token
return authService.getAccessToken();
})
.build();
// 請求會自動帶上 Authorization header
HttpGet request = new HttpGet("https://api.example.com/protected");
ClassicHttpResponse response = client.execute(request);
啟用日誌
// 基本日誌
StandardHttpClient client = StandardHttpClient.builder()
.withLogging()
.build();
// 自訂日誌(含請求/回應內容)
StandardHttpClient client = StandardHttpClient.builder()
.withLogging(
new Slf4jHttpLogHandler(),
true, // includeRequestBody
true, // includeResponseBody
true // includeStackTrace
)
.build();
自訂攔截器
public class MetricsInterceptor implements HttpClientInterceptor {
@Override
public void beforeRequest(InterceptorContext context, ClassicHttpRequest request) {
context.put("startTime", System.currentTimeMillis());
}
@Override
public void afterResponse(InterceptorContext context, ClassicHttpRequest request,
ClassicHttpResponse response) {
long duration = System.currentTimeMillis() - (Long) context.get("startTime");
metrics.recordLatency(request.getPath(), duration);
}
@Override
public void onError(InterceptorContext context, ClassicHttpRequest request,
Exception error) {
metrics.recordError(request.getPath(), error);
}
}
StandardHttpClient client = StandardHttpClient.builder()
.withInterceptor(new MetricsInterceptor())
.build();
異常類別
異常階層
HttpClientException
├── ClientErrorException (4xx)
│ ├── BadRequestException (400)
│ ├── NotAuthorizedException (401)
│ ├── ForbiddenException (403)
│ ├── NotFoundException (404)
│ ├── NotAllowedException (405)
│ └── NotAcceptableException (406)
└── ServerErrorException (5xx)
├── InternalServerErrorException (500)
└── ServiceUnavailableException (503)
異常處理
try {
ClassicHttpResponse response = client.execute(request);
} catch (NotFoundException e) {
// 處理 404
log.warn("Resource not found: {}", e.getMessage());
} catch (NotAuthorizedException e) {
// 處理 401,可能需要刷新 token
authService.refreshToken();
} catch (ServerErrorException e) {
// 處理 5xx
log.error("Server error: {}", e.getMessage());
} catch (HttpClientException e) {
// 處理其他 HTTP 錯誤
log.error("HTTP error: {}", e.getMessage());
}
攔截器介面
HttpClientInterceptor
public interface HttpClientInterceptor {
void beforeRequest(InterceptorContext context, ClassicHttpRequest request);
void afterResponse(InterceptorContext context, ClassicHttpRequest request,
ClassicHttpResponse response);
void onError(InterceptorContext context, ClassicHttpRequest request,
Exception error);
}
InterceptorContext
public interface InterceptorContext {
void put(String key, Object value);
Object get(String key);
<T> T get(String key, Class<T> type);
}
統計資訊
Stats
public class Stats {
int getAvailable(); // 可用連線數
int getLeased(); // 已借出連線數
int getPending(); // 等待中請求數
int getMax(); // 最大連線數
}
取得統計
Stats stats = client.getTotalStats();
log.info("Available: {}, Leased: {}, Pending: {}",
stats.getAvailable(), stats.getLeased(), stats.getPending());
最佳實踐
- 重用客戶端 - 不要每次請求都建立新的客戶端
- 設定合理超時 - 避免請求無限等待
- 使用連線池 - 設定適當的連線池大小
- 處理異常 - 根據 HTTP 狀態碼做適當處理
- 關閉資源 - 使用 try-with-resources 或手動關閉