Apache HttpClient **預設自動處理重定向**(301/302/303/307/308),最終回傳的是**重定向鏈末端的響應內容**。但業務常需知道:**最終落地的真實 URL 是什麼?** 本文示範如何利用 HttpContext 在執行鏈中捕獲 HttpHost(目標主機)與 HttpUriRequest(實際請求 URI)。
HttpContext 是 HttpClient **跨請求/響應交互的狀態容器**。execute(HttpUriRequest, HttpContext) 時,HttpClient 將**重定向過程中的狀態**寫入 Context。org.apache.http.protocol.ExecutionContext):
HTTP_TARGET_HOST → HttpHost(最終目標主機:scheme + host + port)HTTP_REQUEST → HttpUriRequest(實際發出的請求,含最終 URI)import org.apache.http.*;
import org.apache.http.client.*;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.*;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.*;
import org.apache.http.protocol.*;
import org.apache.http.util.EntityUtils;
public class HttpClientRedirectDemo {
public static void getRedirectInfo() {
// 1. 建立 HttpClient (4.5+ 建議用 HttpClients.createDefault())
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 2. 建立 HttpContext 攜帶狀態
HttpContext httpContext = new BasicHttpContext();
// 3. 發起請求(假設此 URL 會重定向)
HttpGet httpGet = new HttpGet("https://127.0.0.1:8088/blog/main.jsp");
// 4. 執行請求,攜帶 Context
try (CloseableHttpResponse response = httpClient.execute(httpGet, httpContext)) {
// 5. 從 Context 取得重定向後的資訊
HttpHost targetHost = (HttpHost) httpContext.getAttribute(HttpClientContext.TARGET_HOST);
HttpUriRequest realRequest = (HttpUriRequest) httpContext.getAttribute(HttpClientContext.REQUEST);
System.out.println("最終目標主機: " + targetHost); // 例: https://127.0.0.1:8088
System.out.println("最終請求 URI: " + realRequest.getURI()); // 例: /blog/admin/login.jsp
// 6. 處理響應實體
HttpEntity entity = response.getEntity();
if (entity != null) {
String charset = ContentType.getOrDefault(entity).getCharset();
System.out.println("響應內容: " + EntityUtils.toString(entity, charset));
EntityUtils.consume(entity); // 釋放連線
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
getRedirectInfo();
}
}
| 舊常數 (ExecutionContext) | 新常數 (HttpClientContext) | 類型 | 說明 |
|---|---|---|---|
ExecutionContext.HTTP_TARGET_HOST | HttpClientContext.TARGET_HOST | HttpHost | 最終目標主機 |
ExecutionContext.HTTP_REQUEST | HttpClientContext.REQUEST | HttpUriRequest | 實際發出的請求 |
ExecutionContext.HTTP_RESPONSE | HttpClientContext.RESPONSE | HttpResponse | 最終響應 |
ExecutionContext.HTTP_TARGET_AUTH_STATE | HttpClientContext.TARGET_AUTH_STATE | AuthState | 目標認證狀態 |
ExecutionContext.HTTP_PROXY_AUTH_STATE | HttpClientContext.PROXY_AUTH_STATE | AuthState | 代理認證狀態 |
若需**攔截重定向**、記錄鏈路、或**禁止自動重定向**:
// 1. 禁用自動重定向
RequestConfig config = RequestConfig.custom()
.setRedirectsEnabled(false)
.build();
HttpGet get = new HttpGet(url);
get.setConfig(config);
// 2. 手動處理 302
try (CloseableHttpResponse resp = client.execute(get)) {
int status = resp.getStatusLine().getStatusCode();
if (status == 301 || status == 302 || status == 303 || status == 307 || status == 308) {
Header location = resp.getFirstHeader("Location");
String redirectUrl = location.getValue();
System.out.println("重定向到: " + redirectUrl);
// 可選:手動發起下一跳請求
}
EntityUtils.consume(resp.getEntity());
}
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.client5.http.protocol.HttpClientContext;
try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpClientContext context = HttpClientContext.create();
HttpGet get = new HttpGet("https://example.com/redirect");
try (ClassicHttpResponse resp = client.execute(get, context)) {
HttpHost target = context.getTargetHost();
HttpGet realRequest = (HttpGet) context.getRequest();
System.out.println("Target: " + target);
System.out.println("URI: " + realRequest.getUri());
}
}
| 現象 | 原因 | 解決 |
|---|---|---|
targetHost 為 null | 未發生重定向、或 Context 未正確傳遞 | 確認 execute(req, context) 雙參數版本 |
| 最終 URI 為原始 URL | 伺服器未重定向、或重定向被禁用 | 檢查伺服器回應碼、RequestConfig.setRedirectsEnabled(true) |
| HTTPS 重定向主機顯示 IP | SNI/憑證驗證導致主機名解析差異 | 屬正常行為,HttpHost 以實際連線目標為準 |
| 相對路徑 Location | 伺服器回應相對重定向 | HttpClient 內部自動解析為絕對 URI,realRequest.getURI() 已是絕對 |
HttpContext 執行請求**。HttpClientContext.TARGET_HOST (主機) 與 HttpClientContext.REQUEST (完整 URI)。HttpClientContext 常數替代舊 ExecutionContext。Location 標頭。