跳至主要内容

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());

最佳實踐

  1. 重用客戶端 - 不要每次請求都建立新的客戶端
  2. 設定合理超時 - 避免請求無限等待
  3. 使用連線池 - 設定適當的連線池大小
  4. 處理異常 - 根據 HTTP 狀態碼做適當處理
  5. 關閉資源 - 使用 try-with-resources 或手動關閉

相關連結