Java 內建 URLConnection 封裝 HTTP 客戶端,但源碼不公開,實現細節成謎。本文依據 HTTP 協定規範 (RFC 2616),使用 java.net.Socket 從零手刻 HTTP 客戶端,實作 GET/HEAD/POST 三大核心方法,深度解析報文格式與解析邏輯。
TCP/IP 協定族通訊透過 Socket 完成。java.net.Socket 連接主機後返回 I/O 流,實現協定間資訊交換。
請求方法 URL HTTP版本
請求標頭 (Header)
**空行 (CRLF)**
實體主體 (Entity Body)
Accept, User-Agent, Content-Length)HTTP版本 狀態碼 狀態說明
回應標頭
**空行**
實體主體 (HTML/JSON/圖片等)
GET https://www.youhost.com HTTP/1.0
Accept: www/source; text/html; image/gif; image/jpeg; */*
User-Agent: myAgent
// 空行結束
回應範例:
HTTP/1.1 200 OK
Date: Tue, 14 Sep 1999 02:19:57 GMT
Server: Apache/1.2.6
Connection: close
Content-Type: text/html
<html><head>...</html>
import java.net.*; import java.io.*; import java.util.Properties;
public class Http {
protected Socket client;
protected BufferedOutputStream sender;
protected BufferedInputStream receiver;
protected ByteArrayInputStream byteStream;
protected URL target;
private int responseCode = -1;
private String responseMessage = "", serverVersion = "";
private Properties header = new Properties();
// 建構子與 GET/HEAD/POST 入口方法
public void GET(String url) { /* 建立連線、發送 GET 報文、接收解析 */ }
public void HEAD(String url) { /* 僅請求標頭,驗證資源是否更新 */ }
public void POST(String url, String content) { /* 發送表單資料,含 Content-Type/Length */ }
// 協定檢查
protected void checkHTTP(String url) throws ProtocolException { /* 驗證 HTTP 協定與 URL 格式 */ }
// 建立 Socket 連線
protected void openServer(String host, int port) throws UnknownHostException, IOException {
header.clear(); responseCode = -1; responseMessage = "";
if (client != null) closeServer();
InetAddress addr = InetAddress.getByName(host);
client = new Socket(addr, port == -1 ? 80 : port);
sender = new BufferedOutputStream(client.getOutputStream());
receiver = new BufferedInputStream(client.getInputStream());
}
// 發送報文
protected void sendMessage(String data) throws IOException {
sender.write(data.getBytes(), 0, data.length());
sender.flush();
}
// 核心:接收並解析回應
protected void receiveMessage() throws IOException {
byte[] data = new byte[1024];
int count = 0, word = -1;
// 1. 解析狀態行
while ((word = receiver.read()) != -1) {
if (word == '\r' || word == '\n') { /* 讀取換行符 */ break; }
if (count == data.length) data = addCapacity(data);
data[count++] = (byte) word;
}
String line = new String(data, 0, count);
int sp = line.indexOf(' ');
serverVersion = line.substring(0, sp);
while (sp < line.length() && line.charAt(sp+1) == ' ') sp++;
responseCode = Integer.parseInt(line.substring(sp+1, sp+4));
responseMessage = line.substring(sp+4).trim();
// 狀態碼異常處理
switch (responseCode) {
case 400: throw new IOException("錯誤請求");
case 404: throw new FileNotFoundException(getURLFormat(target));
case 503: throw new IOException("服務器不可用");
}
// 2. 解析標頭 (多行,以空行結束)
count = 0;
while (word != '\r' && word != '\n' && word > -1) {
if (word == '\t') word = ' ';
if (count == data.length) data = addCapacity(data);
data[count++] = (byte) word;
// 讀取完整一行 (處理折行)
int symbol;
while ((symbol = receiver.read()) > -1) {
if (symbol == '\t') symbol = ' ';
if (symbol == '\r' || symbol == '\n') {
word = receiver.read();
if (symbol == '\r' && word == '\n') word = receiver.read();
if (word == '\r' || word == '\n' || word > ' ') break;
symbol = ' ';
}
if (count == data.length) data = addCapacity(data);
data[count++] = (byte) symbol;
}
String headerLine = new String(data, 0, count);
int colon = headerLine.indexOf(':');
String key = colon > 0 ? headerLine.substring(0, colon) : null;
if (key != null) {
colon++;
while (colon < headerLine.length() && headerLine.charAt(colon) <= ' ') colon++;
header.put(key, headerLine.substring(colon));
}
count = 0;
}
// 3. 讀取實體主體
while ((word = receiver.read()) != -1) {
if (count == data.length) data = addCapacity(data);
data[count++] = (byte) word;
}
if (count > 0) byteStream = new ByteArrayInputStream(data, 0, count);
closeServer();
}
// 輔助方法:動態擴容位元組陣列
private byte[] addCapacity(byte[] arr) {
byte[] n = new byte[arr.length + 1024];
System.arraycopy(arr, 0, n, 0, arr.length);
return n;
}
// Getter 方法:getResponseCode(), getHeaderValue(key), getInputStream() 等...
}
BufferedOutputStream/InputStream 減少系統呼叫addCapacity() 應對超大標頭/主體application/x-www-form-urlencoded + Content-Length程式碼僅實作 GET/HEAD/POST,可擴充:
SSLSocketFactory)手刻 HTTP 客戶端是理解網路協定、排查疑難雜症 (如標頭解析異常、編碼問題) 的最佳途徑。