完整伺服器原始碼
import java.io.*;
import java.net.*;
class SimpleWebServer {
public static void main(String[] args) {
ServerSocket server = null;
int connections = 0;
try {
server = new ServerSocket(80, 5);
while (connections < 5) {
Socket client = server.accept();
serviceClient(client);
connections++;
}
} catch (IOException e) {
System.err.println("伺服器錯誤: " + e);
} finally {
closeQuietly(server);
}
}
static void serviceClient(Socket client) throws IOException {
DataInputStream in = null;
DataOutputStream out = null;
try {
in = new DataInputStream(client.getInputStream());
out = new DataOutputStream(client.getOutputStream());
// 讀取請求頭直到空行
String line;
while ((line = in.readLine()) != null) {
if (line.isEmpty()) {
out.writeBytes(buildResponse());
break;
}
}
} finally {
closeQuietly(out);
closeQuietly(in);
closeQuietly(client);
}
}
static String buildResponse() {
return "HTTP/1.0 200 OK\r\n" +
"Content-Type: text/html; charset=UTF-8\r\n" +
"Content-Length: 42\r\n" +
"\r\n" +
"Hello from Java!
";
}
static void closeQuietly(Closeable c) {
if (c != null) try { c.close(); } catch (IOException ignored) {}
}
}
關鍵實作細節
- 連線計數器:示範處理固定數量請求後關閉
- 請求頭解析:讀取至空行即為請求結束
- HTTP 回應格式:狀態行 + 標頭 + 空行 + 實體
- 資源清理:
closeQuietly 避免 finally 區塊異常吞沒
HTTP 回應建構最佳實務
StringBuilder resp = new StringBuilder();
resp.append("HTTP/1.1 200 OK\r\n");
resp.append("Server: SimpleJava/1.0\r\n");
resp.append("Date: ").append(new Date()).append("\r\n");
resp.append("Content-Type: text/html; charset=UTF-8\r\n");
resp.append("Content-Length: ").append(body.length).append("\r\n");
resp.append("Connection: close\r\n");
resp.append("\r\n");
resp.append(body);
擴充方向
- 解析請求方法、路徑、查詢字串
- 靜態檔案服務與 MIME 類型對應
- 目錄列表、索引檔案預設
- 支援 HEAD、POST 方法
- 存取日誌與錯誤頁面