HTTP 代理伺服器是網路中介,轉發客戶端請求至目標伺服器並回傳響應。本文基於 Java 原生 Socket API,實現一個支援多埠監聽、非阻塞 I/O、連接管道轉發的輕量級代理,核心代碼約 300 行,適合學習網路編程與代理原理。
ServerSocket 監聽指定埠,接受客戶端連接。HTTPSession 執行緒,解析 HTTP 請求頭中的 Host 欄位確定目標地址。package com.example.proxy;
import java.io.*;
import java.net.*;
public class HttpProxy {
private final ServerSocket serverSocket;
private final Thread acceptorThread;
private static long threadCount = 0;
public HttpProxy(int port) throws IOException {
this.serverSocket = new ServerSocket(port);
this.acceptorThread = new Thread(() -> {
try {
while (!Thread.interrupted()) {
Socket client = serverSocket.accept();
new HTTPSession(client).start();
}
} catch (IOException ignored) {}
});
acceptorThread.setDaemon(true);
acceptorThread.start();
System.out.println("代理伺服器啟動於埠口: " + port);
}
public void stop() throws IOException, InterruptedException {
serverSocket.close();
acceptorThread.join();
}
// 內部類:處理單個 HTTP 會話
private class HTTPSession extends Thread {
private final Socket clientSocket;
HTTPSession(Socket socket) {
this.clientSocket = socket;
setDaemon(true);
}
@Override
public void run() {
threadCount++;
try (InputStream in = clientSocket.getInputStream();
OutputStream out = clientSocket.getOutputStream()) {
// 1. 讀取請求頭(直到雙 CRLF)
byte[] buffer = new byte[8192];
int totalRead = 0, headerEnd = 0;
while ((headerEnd = findHeaderEnd(buffer, totalRead)) == 0) {
int read = in.read(buffer, totalRead, buffer.length - totalRead);
if (read <= 0) return;
totalRead += read;
}
// 2. 解析 Host 頭
String header = new String(buffer, 0, headerEnd, "ISO-8859-1");
String hostHeader = extractHost(header);
if (hostHeader == null) {
out.write("HTTP/1.1 400 Bad Request\r\n\r\nMissing Host header".getBytes());
return;
}
Host target = parseHost(hostHeader);
System.out.printf("轉發至: %s:%d%n", target.address, target.port);
// 3. 建立到目標的連接並雙向轉發
try (Socket targetSocket = new Socket(target.address, target.port);
OutputStream targetOut = targetSocket.getOutputStream();
InputStream targetIn = targetSocket.getInputStream()) {
// 先寫入已讀取的請求頭
targetOut.write(buffer, 0, totalRead);
targetOut.flush();
// 雙向管道:客戶端 -> 目標
pipe(in, targetOut);
// 目標 -> 客戶端 (在管道結束後處理剩餘響應)
pipe(targetIn, out);
}
} catch (IOException e) {
System.err.println("會話異常: " + e.getMessage());
} finally {
threadCount--;
closeQuietly(clientSocket);
}
}
// 尋找 HTTP 頭結束位置 (\r\n\r\n)
private int findHeaderEnd(byte[] buf, int len) {
for (int i = 0; i + 3 < len; i++) {
if (buf[i] == '\r' && buf[i+1] == '\n' && buf[i+2] == '\r' && buf[i+3] == '\n') {
return i + 4;
}
}
return 0;
}
private String extractHost(String header) {
for (String line : header.split("\r\n")) {
if (line.toLowerCase().startsWith("host:")) return line.substring(5).trim();
}
return null;
}
private Host parseHost(String hostHeader) {
// 格式: host:port 或 host (預設 80)
int colon = hostHeader.indexOf(':');
if (colon == -1) return new Host(hostHeader, 80);
String addr = hostHeader.substring(0, colon);
int port = Integer.parseInt(hostHeader.substring(colon + 1));
return new Host(addr, port);
}
// 通用管道轉發
private void pipe(InputStream src, OutputStream dst) throws IOException {
byte[] buf = new byte[32 * 1024];
int len;
while ((len = src.read(buf)) != -1) {
dst.write(buf, 0, len);
dst.flush();
}
}
private void closeQuietly(Socket s) {
try { if (s != null) s.close(); } catch (IOException ignored) {}
}
private static class Host {
final String address; final int port;
Host(String address, int port) { this.address = address; this.port = port; }
}
}
// 測試啟動
public static void main(String[] args) throws Exception {
new HttpProxy(8580);
System.in.read(); // 阻塞主執行緒
}
}
代理必須從請求中提取 Host 頭以確定目標伺服器。HTTP/1.1 強制要求 Host 頭,解析邏輯需處理「域名:埠」與「僅域名」兩種格式。
使用 32KB 緩衝區進行流式轉發,避免將完整請求/響應載入記憶體,支援大文件傳輸。flush() 確保數據及時發送,降低延遲。
每連接一執行緒模型簡單直觀,適合中低併發場景。高併發下可升級為 NIO 選擇器模型(單執行緒多路複用)。
本實現展示了 HTTP 代理的核心骨架:監聽 → 解析 Host → 建立上游連接 → 雙向管道轉發。代碼無第三方依賴,邏輯清晰,是理解應用層代理協議、Socket 編程、多執行緒併發的優秀教學案例。生產環境建議使用成熟組件(如 LittleProxy、Netty 實現的代理)。