首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 原生 HTTP 客戶端實現:HttpURLConnection 發送 GET/POST 請求完整代碼與最佳實踐

     編輯:Java 相關知識

雖然生產環境推薦使用 Apache HttpClient、OkHttp 或 JDK 11+ HttpClient,但理解原生 HttpURLConnection 的工作原理是排查網絡問題、面試答題、輕量級項目無依賴發送請求的基礎。本文提供生產級封裝代碼,涵蓋連接池模擬、超時控制、異常處理、資源釋放、響應碼判斷等實戰細節。

核心工具類:HttpRequestUtil

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.Map.Entry;

public class HttpRequestUtil {

    private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
    private static final int CONNECT_TIMEOUT = 5000;  // 連接超時 5s
    private static final int READ_TIMEOUT = 10000;    // 讀取超時 10s

    /**
     * 發送 GET 請求
     * @param url     請求地址(不含參數)
     * @param params  參數 Map,自動編碼拼接
     * @return 響應體字串
     */
    public static String sendGet(String url, Map<String, String> params) {
        HttpURLConnection conn = null;
        BufferedReader reader = null;
        StringBuilder response = new StringBuilder();
        
        try {
            // 1. 構建完整 URL(含查詢參數)
            StringBuilder urlBuilder = new StringBuilder(url);
            if (params != null && !params.isEmpty()) {
                urlBuilder.append(url.contains("?") ? "&" : "?");
                boolean first = true;
                for (Entry<String, String> entry : params.entrySet()) {
                    if (!first) urlBuilder.append("&");
                    urlBuilder.append(URLEncoder.encode(entry.getKey(), "UTF-8"))
                              .append("=")
                              .append(URLEncoder.encode(entry.getValue(), "UTF-8"));
                    first = false;
                }
            }
            
            URL requestUrl = new URL(urlBuilder.toString());
            
            // 2. 打開連接並配置
            conn = (HttpURLConnection) requestUrl.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(CONNECT_TIMEOUT);
            conn.setReadTimeout(READ_TIMEOUT);
            conn.setRequestProperty("User-Agent", USER_AGENT);
            conn.setRequestProperty("Accept", "application/json, text/plain, */*");
            conn.setRequestProperty("Connection", "keep-alive");
            
            // 3. 檢查響應碼
            int responseCode = conn.getResponseCode();
            if (responseCode != HttpURLConnection.HTTP_OK) {
                throw new HttpException("HTTP 錯誤碼: " + responseCode, responseCode);
            }
            
            // 4. 讀取響應
            reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            
        } catch (IOException e) {
            throw new HttpException("GET 請求異常: " + e.getMessage(), e);
        } finally {
            // 5. 釋放資源(順序:讀取器 → 連接)
            closeQuietly(reader);
            if (conn != null) conn.disconnect();
        }
        return response.toString();
    }

    /**
     * 發送 POST 請求(表單/JSON 通用)
     * @param url     請求地址
     * @param params  表單參數(application/x-www-form-urlencoded)
     * @param jsonBody JSON 字串(application/json),優先級高於 params
     * @return 響應體字串
     */
    public static String sendPost(String url, Map<String, String> params, String jsonBody) {
        HttpURLConnection conn = null;
        BufferedReader reader = null;
        OutputStreamWriter writer = null;
        StringBuilder response = new StringBuilder();
        
        try {
            URL requestUrl = new URL(url);
            conn = (HttpURLConnection) requestUrl.openConnection();
            conn.setRequestMethod("POST");
            conn.setConnectTimeout(CONNECT_TIMEOUT);
            conn.setReadTimeout(READ_TIMEOUT);
            conn.setRequestProperty("User-Agent", USER_AGENT);
            conn.setRequestProperty("Accept", "application/json, text/plain, */*");
            conn.setRequestProperty("Connection", "keep-alive");
            
            // 關鍵:POST 必須設置輸出/輸入
            conn.setDoOutput(true);
            conn.setDoInput(true);
            
            // 寫入請求體
            if (jsonBody != null && !jsonBody.isEmpty()) {
                conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
                writer = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
                writer.write(jsonBody);
            } else if (params != null && !params.isEmpty()) {
                conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
                StringBuilder body = new StringBuilder();
                boolean first = true;
                for (Entry<String, String> entry : params.entrySet()) {
                    if (!first) body.append("&");
                    body.append(URLEncoder.encode(entry.getKey(), "UTF-8"))
                        .append("=")
                        .append(URLEncoder.encode(entry.getValue(), "UTF-8"));
                    first = false;
                }
                writer = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
                writer.write(body.toString());
            }
            if (writer != null) writer.flush();
            
            // 檢查響應碼
            int responseCode = conn.getResponseCode();
            InputStream inputStream = responseCode >= 400 ? conn.getErrorStream() : conn.getInputStream();
            
            // 讀取響應
            reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            
            if (responseCode != HttpURLConnection.HTTP_OK) {
                throw new HttpException("HTTP 錯誤碼: " + responseCode + ", 響應: " + response, responseCode);
            }
            
        } catch (IOException e) {
            throw new HttpException("POST 請求異常: " + e.getMessage(), e);
        } finally {
            closeQuietly(writer);
            closeQuietly(reader);
            if (conn != null) conn.disconnect();
        }
        return response.toString();
    }

    // 靜默關閉資源
    private static void closeQuietly(Closeable closeable) {
        if (closeable != null) {
            try { closeable.close(); } catch (IOException ignored) {}
        }
    }

    // 自定義異常攜帶狀態碼
    public static class HttpException extends RuntimeException {
        private final int statusCode;
        public HttpException(String message, int statusCode) { super(message); this.statusCode = statusCode; }
        public HttpException(String message, Throwable cause) { super(message, cause); this.statusCode = -1; }
        public int getStatusCode() { return statusCode; }
    }
}

調用示例

public class Demo {
    public static void main(String[] args) {
        // GET 請求
        Map<String, String> getParams = new HashMap<>();
        getParams.put("key", "123");
        getParams.put("v", "456");
        try {
            String getResult = HttpRequestUtil.sendGet("https://httpbin.org/get", getParams);
            System.out.println("GET 響應: " + getResult);
        } catch (HttpRequestUtil.HttpException e) {
            System.err.println("GET 失敗: " + e.getMessage() + ", 狀態碼: " + e.getStatusCode());
        }

        // POST 表單
        Map<String, String> postParams = new HashMap<>();
        postParams.put("username", "test");
        postParams.put("password", "123456");
        try {
            String postResult = HttpRequestUtil.sendPost("https://httpbin.org/post", postParams, null);
            System.out.println("POST 表單響應: " + postResult);
        } catch (HttpRequestUtil.HttpException e) {
            System.err.println("POST 失敗: " + e.getMessage());
        }

        // POST JSON
        String json = "{\"name\":\"張三\",\"age\":28}";
        try {
            String jsonResult = HttpRequestUtil.sendPost("https://httpbin.org/post", null, json);
            System.out.println("POST JSON 響應: " + jsonResult);
        } catch (HttpRequestUtil.HttpException e) {
            System.err.println("JSON POST 失敗: " + e.getMessage());
        }
    }
}

關鍵實戰細節解析

細節原生代碼做法為什麼重要
超時控制setConnectTimeout/setReadTimeout防止線程永久阻塞,導致線程池耗盡
資源釋放順序先關流,後 disconnect()避免連接洩漏,確保底層 Socket 歸還
錯誤流讀取responseCode >= 400 ? conn.getErrorStream() : conn.getInputStream()4xx/5xx 響應體在 errorStream,不讀會導致連接無法復用
參數編碼URLEncoder.encode(..., "UTF-8")特殊字符(空格、中文、&)必須編碼,否則服務端解析錯誤
User-Agent設置常見瀏覽器 UA部分反爬蟲/網關攔截無 UA 請求
Keep-AliveConnection: keep-alive復用 TCP 連接,減少握手開銷
重定向處理setInstanceFollowRedirects(true) (預設)自動跟隨 301/302,或手動處理 Location 標頭

常見坑與避雷指南

  1. 忘記 setDoOutput(true):POST 請求不設置則無法寫入請求體,拋 ProtocolException。
  2. 未讀取響應體直接關閉:導致底層連接無法進入連接池復用(雖然原生無池,但影響 Keep-Alive)。
  3. 大文件上傳/下載:原生不支援流式處理、進度監聽、斷點續傳,必須換 HttpClient/OkHttp。
  4. HTTPS 證書驗證:自簽證書需配置 TrustManager / HostnameVerifier,代碼極其繁瑣。
  5. 並發效能:HttpURLConnection 底層無連接池,高併發下頻繁創建 Socket,效能遠遜於 HttpClient/OkHttp。

何時升級?

  • ✅ 簡單工具類、單元測試、無依賴限制、低併發 → 原生足夠
  • 🚀 生產環境、高併發、需要連接池、重試、熔斷、指標監控、HTTP/2、WebSocket → Apache HttpClient 5.x / OkHttp 4.x / JDK 11+ HttpClient
<!-- Maven 依賴建議 -->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.3</version>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.11.0</version>
</dependency>

掌握原生實現,是為了在框架失效時能看懂堆疊追蹤、在受限環境下能快速交付、在面試時能說清 TCP/HTTP 細節。但請記住:生產環境請擁抱成熟客戶端庫。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved