Golang 網路編程:使用 net/http 建立 HTTP 伺服器
簡介
在現代的 Web 開發與微服務架構中,HTTP 伺服器是最常見的入口點。Go 語言自帶的 net/http 套件提供了簡潔、效能優異且易於擴充的 API,讓開發者只需幾行程式碼就能啟動一個功能完整的 Web 服務。
本篇文章將從基礎概念出發,逐步帶領讀者了解如何使用 net/http 建立、管理與優化 HTTP 伺服器,並提供實務上常見的範例與最佳實踐,適合 初學者到中級開發者 參考。
核心概念
1. http.Handler 與 http.HandlerFunc
net/http 的核心抽象是 Handler 介面:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
只要實作 ServeHTTP 方法,就能成為一個可被 HTTP 伺服器呼叫的處理器。為了降低門檻,Go 提供了 HandlerFunc 類型,讓普通函式也能自動符合 Handler 介面:
type HandlerFunc func(ResponseWriter, *Request)
技巧:使用
http.HandlerFunc包裝匿名函式是最常見的寫法,簡潔且易於閱讀。
2. http.ServeMux(路由器)
ServeMux 是內建的 多路由(multiplexer),負責把不同的 URL 路徑對應到對應的 Handler。使用方式如下:
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
mux.Handle("/", http.FileServer(http.Dir("./static")))
如果需求更複雜(例如支援參數、正則表達式),可以自行實作 Handler,或導入第三方路由套件(如 gorilla/mux、chi)。
3. 啟動伺服器:http.ListenAndServe
最簡單的伺服器只需要一行:
log.Fatal(http.ListenAndServe(":8080", nil))
- 第一個參數是 監聽位址(
host:port),若僅提供:8080,則會在所有介面上監聽。 - 第二個參數是 根處理器,若傳入
nil,會使用預設的DefaultServeMux。
程式碼範例
以下提供 5 個實用範例,涵蓋從最基礎的 Hello World 到支援靜態檔案、JSON API、Graceful Shutdown 與 Middleware。
範例 1:最簡單的 Hello World
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
// 寫入回應內容
fmt.Fprintln(w, "Hello, Golang HTTP Server!")
}
func main() {
http.HandleFunc("/", helloHandler) // 設定根路徑的處理器
log.Println("Server listening on http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
說明:
http.HandleFunc直接把匿名函式註冊到全域的DefaultServeMux,適合快速測試。
範例 2:使用 ServeMux 管理多路由
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from /hello")
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "About page")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
mux.HandleFunc("/about", aboutHandler)
log.Println("Server listening on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
重點:
ServeMux讓路由管理更有條理,未來若要加入中介層(middleware)或子路由,只需要在mux上操作即可。
範例 3:回傳 JSON API
package main
import (
"encoding/json"
"log"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
func userHandler(w http.ResponseWriter, r *http.Request) {
// 設定回應的 Content-Type
w.Header().Set("Content-Type", "application/json")
// 建立假資料
user := User{ID: 1, Name: "Alice", Age: 28}
// 直接寫入 JSON
if err := json.NewEncoder(w).Encode(user); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func main() {
http.HandleFunc("/api/user", userHandler)
log.Println("JSON API listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
技巧:使用
json.NewEncoder(w).Encode(v)可以一次完成 序列化 + 寫入,且自動處理錯誤。
範例 4:服務靜態檔案與目錄索引
package main
import (
"log"
"net/http"
)
func main() {
// 以 ./public 為根目錄提供靜態資源
fs := http.FileServer(http.Dir("./public"))
// 讓所有以 /static/ 開頭的請求走到檔案伺服器
http.Handle("/static/", http.StripPrefix("/static/", fs))
log.Println("Static file server listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
說明:
http.StripPrefix會把 URL 前綴移除,讓檔案路徑正確對應到磁碟上的目錄結構。
範例 5:Graceful Shutdown(優雅關閉)+ Middleware 範例
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"
)
// loggingMiddleware 會在每次請求前後印出日誌
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("▶ %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("◀ %s %s (%v)", r.Method, r.URL.Path, time.Since(start))
})
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Graceful shutdown demo"))
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", helloHandler)
// 套用 middleware
handler := loggingMiddleware(mux)
srv := &http.Server{
Addr: ":8080",
Handler: handler,
}
// 啟動伺服器(在 goroutine 中執行,讓主執行緒可以監聽訊號)
go func() {
log.Println("Server listening on :8080")
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 監聽 OS 終止訊號(Ctrl+C、SIGTERM)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("Shutdown signal received, exiting...")
// 設定 5 秒的關閉期限
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server gracefully stopped")
}
關鍵點
- Graceful Shutdown:使用
http.Server.Shutdown讓正在處理的請求有機會完成,避免斷線。- Middleware:透過
http.Handler包裝的方式實作,可重複使用於任意路由。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記設定 Content-Type |
客戶端可能無法正確解析回傳資料(例如 JSON) | 在回應前 w.Header().Set("Content-Type", "application/json") |
直接在 Handler 中使用 panic |
會導致整個伺服器崩潰 | 使用 recover 或第三方 middleware 捕捉 panic,回傳 500 錯誤 |
大量同步寫入 log |
會造成 I/O 阻塞,影響效能 | 使用非同步 logger(如 zap、zerolog)或緩衝寫入 |
| 未限制請求大小 | 大檔案上傳可能耗盡記憶體 | r.Body = http.MaxBytesReader(w, r.Body, 10<<20) 限制為 10 MB |
忘記關閉 Request.Body(對於非 GET) |
可能造成資源泄漏 | defer r.Body.Close() |
直接在 ServeMux 中使用全域變數 |
競爭條件(race condition) | 使用 sync.RWMutex 或將狀態封裝在 struct 中,實作自己的 Handler |
其他最佳實踐
使用 Context 控制逾時
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second) defer cancel() // 將 ctx 傳給下層服務(DB、外部 API)以支援取消分離路由與業務邏輯
- 建議把路由設定放在
router.go,業務處理放在handler/*.go,保持程式碼可維護。
- 建議把路由設定放在
結合健康檢查端點
http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })在容器化部署時,K8s 會依賴此端點判斷服務是否可用。
使用 HTTPS
- 在正式環境務必使用
http.ListenAndServeTLS,或在前端放置反向代理(如 Nginx、Traefik)處理 TLS 終端。
- 在正式環境務必使用
日誌結構化
- 使用 JSON 格式日誌,方便在 ELK、Grafana Loki 等平台搜尋與分析。
實際應用場景
| 場景 | 為何選擇 net/http |
相關功能 |
|---|---|---|
| 微服務 API | 輕量、內建路由、易於與 context 整合 |
JSON 編碼、Graceful Shutdown、健康檢查 |
| 靜態網站或資產服務 | http.FileServer 可直接提供檔案,無需額外套件 |
靜態檔案快取、ETag、If-Modified-Since |
| WebSocket | net/http 與 golang.org/x/net/websocket 或 gorilla/websocket 搭配使用 |
升級協議、長連線 |
| 內部工具(Dashboard) | 快速開發、可嵌入認證中介層 | 基本認證、Session 管理 |
| Serverless(AWS Lambda) | aws-lambda-go 可將 http.Handler 包裝成 Lambda 處理函式 |
免除自行管理伺服器 |
總結
net/http 是 Go 語言最核心的網路套件之一,以最少的程式碼即可建立功能完整的 HTTP 伺服器。本文從 Handler、ServeMux、基本啟動方式切入,提供了五個實務範例,並闡述常見陷阱、最佳實踐與典型應用情境。掌握以下要點,即可在專案中快速構建安全、可維護且效能優異的 Web 服務:
- Handler 與 Middleware:利用
http.HandlerFunc與函式組合實作可重用的請求處理流程。 - 路由管理:
ServeMux足以應付大多數需求,必要時可自行實作或導入第三方路由器。 - Graceful Shutdown:在容器化或高可用環境中,務必實作關閉機制,避免突斷連線。
- 安全與效能:設定正確的
Content-Type、限制請求大小、使用 Context 控制逾時、結構化日誌。 - 實務場景:從簡易 API、靜態檔案服務到微服務與 WebSocket,都能直接受惠於
net/http的設計。
只要熟悉這些概念與技巧,你就能在 Go 生態系 中自信地開發各式 HTTP 服務,為後續的分散式系統或雲端原生應用奠定堅實基礎。祝開發順利,Happy coding! 🚀