HTTP 是 Web 通訊的基石。本文詳細解析 HTTP 請求結構、常見標頭,並提供 GET/POST 完整實作範例,涵蓋圖片下載、表單提交、參數編碼等實戰場景。
GET /api/users?id=123&name=tom HTTP/1.1 Host: example.com Accept: application/json User-Agent: Mozilla/5.0 Connection: keep-alive [請求體 - GET 通常為空]
| 特性 | GET | POST |
|---|---|---|
| 參數位置 | URL 查詢字串 (?key=val&...) | 請求體 |
| 資料長度 | 受限 (~2KB~8KB,視瀏覽器/伺服器) | 無限制 |
| 安全性 | 參數暴露在 URL,不適合敏感資料 | 參數在請求體,相對安全 |
| 快取 | 可被快取、書籤、歷史記錄 | 預設不快取 |
| 冪等性 | 冪等 | 非冪等 |
| 編碼類型 | application/x-www-form-urlencoded | 支援 multipart/form-data (檔案上傳) |
| 標頭 | 用途 | 範例 |
|---|---|---|
| Accept | 客戶端可處理的媒體類型 | application/json, text/html |
| Accept-Charset | 支援的字元集 | UTF-8, ISO-8859-1 |
| Accept-Encoding | 支援的壓縮格式 | gzip, deflate, br |
| Accept-Language | 偏好語言 | zh-TW, zh;q=0.9, en;q=0.8 |
| Host | 目標主機 (必須) | api.example.com:8080 |
| User-Agent | 客戶端識別資訊 | Mozilla/5.0 / Java/17 |
| Content-Type | 請求體媒體類型 | application/json; charset=UTF-8 |
| Content-Length | 請求體長度 (位元組) | 1024 |
| Authorization | 認證資訊 | Bearer eyJhbGciOiJIUzI1NiIs... |
| Cookie | 攜帶 Cookie | sessionId=abc123; token=xyz |
| Referer | 來源頁面 (防盜鏈) | https://example.com/page |
| Connection | 連線管理 | keep-alive, close |
public class HttpGetDemo {
private static final String IMAGE_URL = "https://httpbin.org/image/png";
private static final String SAVE_PATH = "downloaded_image.png";
public static void main(String[] args) {
downloadImage();
}
public static void downloadImage() {
try (InputStream in = createInputStream();
FileOutputStream out = new FileOutputStream(SAVE_PATH)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
System.out.println("圖片下載完成: " + SAVE_PATH);
} catch (IOException e) {
System.err.println("下載失敗: " + e.getMessage());
e.printStackTrace();
}
}
private static InputStream createInputStream() throws IOException {
URL url = new URL(IMAGE_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 關鍵配置
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000); // 連線超時 5 秒
conn.setReadTimeout(10000); // 讀取超時 10 秒
conn.setRequestProperty("User-Agent", "Java-HttpClient/1.0");
conn.setRequestProperty("Accept", "image/*,*/*;q=0.8");
int responseCode = conn.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
throw new IOException("HTTP 錯誤碼: " + responseCode);
}
return conn.getInputStream();
}
}
public class HttpPostDemo {
private static final String API_URL = "https://httpbin.org/post";
public static void main(String[] args) {
Map formData = new HashMap<>();
formData.put("username", "admin");
formData.put("password", "secret123");
formData.put("remember", "true");
String response = sendPostForm(formData, "UTF-8");
System.out.println("伺服器回應: " + response);
}
public static String sendPostForm(Map params, String charset) {
if (params == null || params.isEmpty()) return "";
// 1. 建構表單資料
StringBuilder formData = new StringBuilder();
try {
for (Map.Entry entry : params.entrySet()) {
if (formData.length() > 0) formData.append('&');
formData.append(URLEncoder.encode(entry.getKey(), charset))
.append('=')
.append(URLEncoder.encode(entry.getValue(), charset));
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("編碼不支援: " + charset, e);
}
byte[] postData = formData.toString().getBytes(StandardCharsets.UTF_8);
// 2. 發送請求
try {
URL url = new URL(API_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(5000);
conn.setReadTimeout(10000);
conn.setDoOutput(true); // 允許輸出
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=" + charset);
conn.setRequestProperty("Content-Length", String.valueOf(postData.length));
conn.setRequestProperty("User-Agent", "Java-HttpClient/1.0");
// 寫入請求體
try (OutputStream out = conn.getOutputStream()) {
out.write(postData);
out.flush();
}
// 讀取回應
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
return readResponse(conn.getInputStream(), charset);
} else {
String error = readResponse(conn.getErrorStream(), charset);
throw new IOException("HTTP " + responseCode + ": " + error);
}
} catch (IOException e) {
throw new RuntimeException("POST 請求失敗", e);
}
}
private static String readResponse(InputStream in, String charset) throws IOException {
if (in == null) return "";
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, charset))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
return sb.toString();
}
}
}
public static String sendPostJson(String jsonBody, String url) {
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(5000);
conn.setReadTimeout(10000);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
conn.setRequestProperty("Accept", "application/json");
try (OutputStream out = conn.getOutputStream()) {
out.write(jsonBody.getBytes(StandardCharsets.UTF_8));
}
int code = conn.getResponseCode();
InputStream stream = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream();
return readResponse(stream, "UTF-8");
} catch (IOException e) {
throw new RuntimeException("JSON POST 失敗", e);
}
}
// 使用範例
String json = "{\"name\":\"張三\",\"age\":25,\"skills\":[\"Java\",\"Spring\"]}";
String result = sendPostJson(json, "https://api.example.com/users");
setConnectTimeout() + setReadTimeout(),避免執行緒永久阻塞getErrorStream() 取得錯誤詳情URLEncoder.encode(),避免特殊字元導致錯誤setInstanceFollowRedirects(false) 關閉java.net.http.HttpClient (支援 HTTP/2、非同步、CompletableFuture)掌握原生 HttpURLConnection 雖然較繁瑣,但能深入理解 HTTP 協議細節。實務開發建議根據專案需求選擇適合的 HTTP 客戶端庫,並建立統一的請求封裝層。