本文 AI 產出,尚未審核

Golang 網路編程:使用 net/http 建立 HTTP 伺服器

簡介

在現代的 Web 開發與微服務架構中,HTTP 伺服器是最常見的入口點。Go 語言自帶的 net/http 套件提供了簡潔、效能優異且易於擴充的 API,讓開發者只需幾行程式碼就能啟動一個功能完整的 Web 服務。
本篇文章將從基礎概念出發,逐步帶領讀者了解如何使用 net/http 建立、管理與優化 HTTP 伺服器,並提供實務上常見的範例與最佳實踐,適合 初學者到中級開發者 參考。


核心概念

1. http.Handlerhttp.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/muxchi)。

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")
}

關鍵點

  1. Graceful Shutdown:使用 http.Server.Shutdown 讓正在處理的請求有機會完成,避免斷線。
  2. Middleware:透過 http.Handler 包裝的方式實作,可重複使用於任意路由。

常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記設定 Content-Type 客戶端可能無法正確解析回傳資料(例如 JSON) 在回應前 w.Header().Set("Content-Type", "application/json")
直接在 Handler 中使用 panic 會導致整個伺服器崩潰 使用 recover 或第三方 middleware 捕捉 panic,回傳 500 錯誤
大量同步寫入 log 會造成 I/O 阻塞,影響效能 使用非同步 logger(如 zapzerolog)或緩衝寫入
未限制請求大小 大檔案上傳可能耗盡記憶體 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

其他最佳實踐

  1. 使用 Context 控制逾時

    ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
    defer cancel()
    // 將 ctx 傳給下層服務(DB、外部 API)以支援取消
    
  2. 分離路由與業務邏輯

    • 建議把路由設定放在 router.go,業務處理放在 handler/*.go,保持程式碼可維護。
  3. 結合健康檢查端點

    http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
        w.WriteHeader(http.StatusOK)
    })
    

    在容器化部署時,K8s 會依賴此端點判斷服務是否可用。

  4. 使用 HTTPS

    • 在正式環境務必使用 http.ListenAndServeTLS,或在前端放置反向代理(如 Nginx、Traefik)處理 TLS 終端。
  5. 日誌結構化

    • 使用 JSON 格式日誌,方便在 ELK、Grafana Loki 等平台搜尋與分析。

實際應用場景

場景 為何選擇 net/http 相關功能
微服務 API 輕量、內建路由、易於與 context 整合 JSON 編碼、Graceful Shutdown、健康檢查
靜態網站或資產服務 http.FileServer 可直接提供檔案,無需額外套件 靜態檔案快取、ETagIf-Modified-Since
WebSocket net/httpgolang.org/x/net/websocketgorilla/websocket 搭配使用 升級協議、長連線
內部工具(Dashboard) 快速開發、可嵌入認證中介層 基本認證、Session 管理
Serverless(AWS Lambda) aws-lambda-go 可將 http.Handler 包裝成 Lambda 處理函式 免除自行管理伺服器

總結

net/http 是 Go 語言最核心的網路套件之一,以最少的程式碼即可建立功能完整的 HTTP 伺服器。本文從 Handler、ServeMux、基本啟動方式切入,提供了五個實務範例,並闡述常見陷阱、最佳實踐與典型應用情境。掌握以下要點,即可在專案中快速構建安全、可維護且效能優異的 Web 服務:

  1. Handler 與 Middleware:利用 http.HandlerFunc 與函式組合實作可重用的請求處理流程。
  2. 路由管理ServeMux 足以應付大多數需求,必要時可自行實作或導入第三方路由器。
  3. Graceful Shutdown:在容器化或高可用環境中,務必實作關閉機制,避免突斷連線。
  4. 安全與效能:設定正確的 Content-Type、限制請求大小、使用 Context 控制逾時、結構化日誌。
  5. 實務場景:從簡易 API、靜態檔案服務到微服務與 WebSocket,都能直接受惠於 net/http 的設計。

只要熟悉這些概念與技巧,你就能在 Go 生態系 中自信地開發各式 HTTP 服務,為後續的分散式系統或雲端原生應用奠定堅實基礎。祝開發順利,Happy coding! 🚀