← 返回
后端开发 2026.03.06

編程基礎 0012_Go_Web與網絡編程精華

后端开发

知識來源: - 《Building Web Apps with Go》 - 《Go API 編程》 - 《Go Web 編程》(Go Web Programming, Sau Sheong Chang) - 《Go 網絡編程》(Network Programming with Go) - 《Mastering Go Web Services》 - 《Go Web 編程》(2013, 謝孟軍/astaxie)


一、net/http 包深入(Handler 接口、ServeMux、Middleware 鏈)

1.1 Handler 接口——一切的核心

Go HTTP 處理的核心抽象只有一個接口:

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

整個 net/http 的設計哲學都圍繞這個接口展開。路由器、中間件、靜態文件服務器——全部是 Handler 的實現。

HandlerFunc 適配器——函數也能當 Handler:

type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

這是 Go 的經典設計模式:通過類型轉換,讓普通函數滿足接口。

// 以下兩種註冊方式完全等價
http.Handle("/hello", http.HandlerFunc(helloFunc))
http.HandleFunc("/hello", helloFunc)

自定義 Handler 結構體——攜帶依賴注入:

type UserHandler struct {
    db     *sql.DB
    logger *slog.Logger
    cache  *redis.Client
}

func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 可以訪問 h.db, h.logger, h.cache
    users, err := h.db.QueryContext(r.Context(), "SELECT ...")
    // ...
}

// 註冊
mux.Handle("/users", &UserHandler{db: db, logger: logger, cache: cache})

1.2 ServeMux 路由機制

DefaultServeMux 與自定義 Mux:

// 使用 DefaultServeMux(不推薦,全局狀態,存在安全隱患)
http.HandleFunc("/api/users", usersHandler)
http.ListenAndServe(":8080", nil) // nil = DefaultServeMux

// 使用自定義 ServeMux(推薦)
mux := http.NewServeMux()
mux.HandleFunc("/api/users", usersHandler)
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
http.ListenAndServe(":8080", mux)

路由匹配規則: - 精確匹配優先於前綴匹配 - 以 / 結尾的模式是子樹匹配(前綴匹配) - 不以 / 結尾的模式是精確匹配 - 最長匹配優先

Go 1.22+ 增強路由——終於支持方法和路徑參數:

mux := http.NewServeMux()

// 方法限定
mux.HandleFunc("GET /api/users", listUsers)
mux.HandleFunc("POST /api/users", createUser)

// 路徑參數
mux.HandleFunc("GET /api/users/{id}", getUser)
mux.HandleFunc("DELETE /api/users/{id}", deleteUser)

// 通配符(匹配剩餘路徑)
mux.HandleFunc("GET /files/{path...}", serveFile)

func getUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")    // 提取路徑參數
    // ...
}

優先級規則(Go 1.22+): - 更具體的模式優先(GET /users/{id} 優先於 /users/{id}) - 帶方法的優先於不帶方法的 - 如果兩個模式衝突且無法比較,註冊時 panic

1.3 自定義 Server 配置

生產環境必須使用自定義 http.Server

server := &http.Server{
    Addr:              ":8080",
    Handler:           mux,
    ReadTimeout:       15 * time.Second,  // 讀取整個請求的超時
    ReadHeaderTimeout: 5 * time.Second,   // 讀取請求頭的超時
    WriteTimeout:      15 * time.Second,  // 寫響應的超時
    IdleTimeout:       60 * time.Second,  // Keep-Alive 空閒超時
    MaxHeaderBytes:    1 << 20,           // 請求頭最大 1MB
}
log.Fatal(server.ListenAndServe())

HTTPS / TLS 支持:

// 方式一:直接使用證書文件
server.ListenAndServeTLS("cert.pem", "key.pem")

// 方式二:自定義 TLS 配置
server.TLSConfig = &tls.Config{
    MinVersion:               tls.VersionTLS12,
    CurvePreferences:         []tls.CurveID{tls.X25519, tls.CurveP256},
    PreferServerCipherSuites: true,
}
server.ListenAndServeTLS("cert.pem", "key.pem")

// 方式三:Let's Encrypt 自動證書
import "golang.org/x/crypto/acme/autocert"

manager := &autocert.Manager{
    Cache:      autocert.DirCache("certs"),
    Prompt:     autocert.AcceptTOS,
    HostPolicy: autocert.HostWhitelist("example.com"),
}
server.TLSConfig = manager.TLSConfig()
server.ListenAndServeTLS("", "")

1.4 中間件模式

中間件是接收 Handler 並返回新 Handler 的函數,形成洋蔥模型:

type Middleware func(http.Handler) http.Handler

日誌中間件:

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        // 包裝 ResponseWriter 以捕獲狀態碼
        ww := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
        next.ServeHTTP(ww, r)
        slog.Info("request",
            "method", r.Method,
            "path", r.URL.Path,
            "status", ww.statusCode,
            "duration", time.Since(start),
            "ip", r.RemoteAddr,
        )
    })
}

type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}

Recovery 中間件(panic 恢復):

func recoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                slog.Error("panic recovered",
                    "error", err,
                    "stack", string(debug.Stack()),
                )
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

中間件鏈式組合:

func chainMiddleware(handler http.Handler, middlewares ...Middleware) http.Handler {
    // 從後往前包裝,確保執行順序是從前到後
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// 使用
finalHandler := chainMiddleware(
    myHandler,
    recoveryMiddleware,   // 最外層,最先執行
    loggingMiddleware,
    corsMiddleware,
    authMiddleware,       // 最內層,最後執行
)

CORS 中間件:

func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        w.Header().Set("Access-Control-Max-Age", "86400")
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}

二、HTTP 服務器實現原理

2.1 啓動流程源碼分析

ListenAndServe 的完整調用鏈:

ListenAndServe(addr, handler)
  -> net.Listen("tcp", addr)      // 創建 TCP 監聽器
  -> server.Serve(listener)       // 進入主循環
    -> for { listener.Accept() }  // 循環接受連接
      -> go conn.serve(ctx)       // 每個連接一個 goroutine
        -> serverHandler.ServeHTTP(w, r)  // 調用 Handler

關鍵設計:每請求一個 goroutine。 Go 的 goroutine 非常輕量(初始棧僅 2KB),因此可以同時處理數萬併發連接,無需回調或線程池。

2.2 連接處理詳解

// 簡化版的 conn.serve 核心邏輯
func (c *conn) serve(ctx context.Context) {
    defer c.close()

    // TLS 握手(如果是 HTTPS)
    if tlsConn, ok := c.rwc.(*tls.Conn); ok {
        tlsConn.HandshakeContext(ctx)
    }

    for {
        // 讀取請求(支持 HTTP/1.1 Keep-Alive,一個連接可複用多次)
        w, err := c.readRequest(ctx)
        if err != nil {
            return
        }

        // 調用 Handler 處理請求
        serverHandler{c.server}.ServeHTTP(w, w.req)

        // 刷新響應
        w.finishRequest()

        // 檢查是否 Keep-Alive
        if !w.shouldReuseConnection() {
            return
        }
    }
}

2.3 ResponseWriter 的三層接口

// 基礎接口
type ResponseWriter interface {
    Header() http.Header
    Write([]byte) (int, error)
    WriteHeader(statusCode int)
}

// 實際實現還滿足以下可選接口:
// http.Flusher   -> Flush() 方法,用於 SSE 等流式響應
// http.Hijacker  -> Hijack() 方法,用於 WebSocket 升級
// http.Pusher    -> Push() 方法,用於 HTTP/2 Server Push

// 檢測並使用 Flusher(SSE 場景)
if flusher, ok := w.(http.Flusher); ok {
    fmt.Fprintf(w, "data: %s\n\n", message)
    flusher.Flush()
}

// Hijack 接管連接(WebSocket 底層原理)
if hijacker, ok := w.(http.Hijacker); ok {
    conn, bufrw, err := hijacker.Hijack()
    // 此後可以直接操作底層 TCP 連接
}

2.4 Server-Sent Events (SSE) 實現

func sseHandler(w http.ResponseWriter, r *http.Request) {
    flusher, ok := w.(http.Flusher)
    if !ok {
        http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")

    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-r.Context().Done():
            return
        case t := <-ticker.C:
            fmt.Fprintf(w, "event: time\ndata: %s\n\n", t.Format(time.RFC3339))
            flusher.Flush()
        }
    }
}

三、RESTful API 設計與實現

3.1 資源設計與路由註冊

// CRUD 對應 HTTP 方法
// GET    /api/v1/users       -> 列表
// GET    /api/v1/users/{id}  -> 詳情
// POST   /api/v1/users       -> 創建
// PUT    /api/v1/users/{id}  -> 全量更新
// PATCH  /api/v1/users/{id}  -> 部分更新
// DELETE /api/v1/users/{id}  -> 刪除

type UserAPI struct {
    store  UserStore
    logger *slog.Logger
}

func (api *UserAPI) RegisterRoutes(mux *http.ServeMux) {
    mux.HandleFunc("GET /api/v1/users", api.List)
    mux.HandleFunc("GET /api/v1/users/{id}", api.Get)
    mux.HandleFunc("POST /api/v1/users", api.Create)
    mux.HandleFunc("PUT /api/v1/users/{id}", api.Update)
    mux.HandleFunc("DELETE /api/v1/users/{id}", api.Delete)
}

3.2 統一響應格式

type APIResponse struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

type PagedResponse struct {
    Items      interface{} `json:"items"`
    Page       int         `json:"page"`
    PageSize   int         `json:"page_size"`
    TotalCount int64       `json:"total_count"`
    TotalPages int         `json:"total_pages"`
}

func respondJSON(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json; charset=utf-8")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

func success(w http.ResponseWriter, data interface{}) {
    respondJSON(w, http.StatusOK, APIResponse{Code: 0, Message: "success", Data: data})
}

func errorResponse(w http.ResponseWriter, status int, msg string) {
    respondJSON(w, status, APIResponse{Code: -1, Message: msg})
}

3.3 JSON 處理技巧

// 結構體 json tag
type User struct {
    ID        int64     `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email,omitempty"` // 空值不輸出
    Password  string    `json:"-"`               // 永遠不序列化
    CreatedAt time.Time `json:"created_at"`
}

// 安全解碼:限制大小 + 拒絕未知字段
func decodeJSON(r *http.Request, v interface{}) error {
    // 限制請求體大小 1MB
    r.Body = http.MaxBytesReader(nil, r.Body, 1<<20)
    decoder := json.NewDecoder(r.Body)
    decoder.DisallowUnknownFields()
    if err := decoder.Decode(v); err != nil {
        return fmt.Errorf("invalid JSON: %w", err)
    }
    // 確保沒有多餘數據
    if decoder.More() {
        return errors.New("request body must only contain a single JSON object")
    }
    return nil
}

// 自定義 JSON 序列化
type UnixTime time.Time

func (t UnixTime) MarshalJSON() ([]byte, error) {
    return json.Marshal(time.Time(t).Unix())
}

func (t *UnixTime) UnmarshalJSON(data []byte) error {
    var unix int64
    if err := json.Unmarshal(data, &unix); err != nil {
        return err
    }
    *t = UnixTime(time.Unix(unix, 0))
    return nil
}

3.4 分頁、過濾與排序

type PaginationParams struct {
    Page     int    `json:"page"`
    PageSize int    `json:"page_size"`
    Sort     string `json:"sort"`
    Order    string `json:"order"` // asc, desc
}

func parsePagination(r *http.Request) PaginationParams {
    q := r.URL.Query()
    page, _ := strconv.Atoi(q.Get("page"))
    if page < 1 {
        page = 1
    }
    pageSize, _ := strconv.Atoi(q.Get("page_size"))
    if pageSize < 1 || pageSize > 100 {
        pageSize = 20
    }
    sort := q.Get("sort")
    order := q.Get("order")
    if order != "asc" && order != "desc" {
        order = "desc"
    }
    return PaginationParams{Page: page, PageSize: pageSize, Sort: sort, Order: order}
}

// SQL 中使用
// offset := (params.Page - 1) * params.PageSize
// query := fmt.Sprintf("SELECT * FROM users ORDER BY %s %s LIMIT ? OFFSET ?", sort, order)

3.5 請求驗證

type CreateUserRequest struct {
    Name     string `json:"name"`
    Email    string `json:"email"`
    Password string `json:"password"`
    Age      int    `json:"age"`
}

func (req CreateUserRequest) Validate() error {
    var errs []string
    if strings.TrimSpace(req.Name) == "" {
        errs = append(errs, "name is required")
    }
    if !strings.Contains(req.Email, "@") {
        errs = append(errs, "invalid email format")
    }
    if len(req.Password) < 8 {
        errs = append(errs, "password must be at least 8 characters")
    }
    if req.Age < 0 || req.Age > 150 {
        errs = append(errs, "age must be between 0 and 150")
    }
    if len(errs) > 0 {
        return fmt.Errorf("validation failed: %s", strings.Join(errs, "; "))
    }
    return nil
}

// 也可用 go-playground/validator 做聲明式驗證
import "github.com/go-playground/validator/v10"

type CreateUserRequest struct {
    Name     string `json:"name" validate:"required,min=2,max=50"`
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=8"`
    Age      int    `json:"age" validate:"gte=0,lte=150"`
}

var validate = validator.New()

func (req CreateUserRequest) Validate() error {
    return validate.Struct(req)
}

3.6 API 版本管理

// 方式一:URL 路徑版本(最常用)
mux.HandleFunc("GET /api/v1/users", v1ListUsers)
mux.HandleFunc("GET /api/v2/users", v2ListUsers)

// 方式二:請求頭版本
func versionMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        version := r.Header.Get("Accept-Version")
        if version == "" {
            version = "v1"
        }
        ctx := context.WithValue(r.Context(), "api-version", version)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

四、WebSocket 編程

4.1 基礎 WebSocket 服務

import "github.com/gorilla/websocket"

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    CheckOrigin: func(r *http.Request) bool {
        // 生產環境應嚴格檢查 Origin
        origin := r.Header.Get("Origin")
        return origin == "https://myapp.com"
    },
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println("Upgrade error:", err)
        return
    }
    defer conn.Close()

    // 設置讀取限制和超時
    conn.SetReadLimit(512)
    conn.SetReadDeadline(time.Now().Add(60 * time.Second))
    conn.SetPongHandler(func(string) error {
        conn.SetReadDeadline(time.Now().Add(60 * time.Second))
        return nil
    })

    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            if websocket.IsUnexpectedCloseError(err,
                websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
                log.Printf("WebSocket error: %v", err)
            }
            break
        }
        log.Printf("Received: %s", message)
        err = conn.WriteMessage(messageType, message)
        if err != nil {
            break
        }
    }
}

4.2 聊天室 Hub 模式

這是 WebSocket 應用的經典架構,來源於 gorilla/websocket 官方示例:

type Hub struct {
    clients    map[*Client]bool
    broadcast  chan []byte
    register   chan *Client
    unregister chan *Client
    mu         sync.RWMutex
}

type Client struct {
    hub  *Hub
    conn *websocket.Conn
    send chan []byte
}

func newHub() *Hub {
    return &Hub{
        clients:    make(map[*Client]bool),
        broadcast:  make(chan []byte, 256),
        register:   make(chan *Client),
        unregister: make(chan *Client),
    }
}

func (h *Hub) Run() {
    for {
        select {
        case client := <-h.register:
            h.clients[client] = true
        case client := <-h.unregister:
            if _, ok := h.clients[client]; ok {
                delete(h.clients, client)
                close(client.send)
            }
        case message := <-h.broadcast:
            for client := range h.clients {
                select {
                case client.send <- message:
                default:
                    close(client.send)
                    delete(h.clients, client)
                }
            }
        }
    }
}

// 客戶端讀取 goroutine
func (c *Client) readPump() {
    defer func() {
        c.hub.unregister <- c
        c.conn.Close()
    }()
    for {
        _, message, err := c.conn.ReadMessage()
        if err != nil {
            break
        }
        c.hub.broadcast <- message
    }
}

// 客戶端寫入 goroutine
func (c *Client) writePump() {
    ticker := time.NewTicker(54 * time.Second)
    defer func() {
        ticker.Stop()
        c.conn.Close()
    }()
    for {
        select {
        case message, ok := <-c.send:
            if !ok {
                c.conn.WriteMessage(websocket.CloseMessage, []byte{})
                return
            }
            c.conn.WriteMessage(websocket.TextMessage, message)
        case <-ticker.C:
            // 定時發送 Ping 保持連接
            if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
                return
            }
        }
    }
}

4.3 nhooyr/websocket(更現代的選擇)

import "nhooyr.io/websocket"

func wsHandler(w http.ResponseWriter, r *http.Request) {
    conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{
        OriginPatterns: []string{"myapp.com"},
    })
    if err != nil {
        return
    }
    defer conn.CloseNow()

    ctx, cancel := context.WithTimeout(r.Context(), 10*time.Minute)
    defer cancel()

    for {
        typ, data, err := conn.Read(ctx)
        if err != nil {
            break
        }
        conn.Write(ctx, typ, data)
    }
    conn.Close(websocket.StatusNormalClosure, "")
}

五、TCP/UDP 網絡編程

5.1 TCP 服務器

func startTCPServer() {
    listener, err := net.Listen("tcp", ":9000")
    if err != nil {
        log.Fatal(err)
    }
    defer listener.Close()
    log.Println("TCP server listening on :9000")

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Println("Accept error:", err)
            continue
        }
        go handleConnection(conn)
    }
}

func handleConnection(conn net.Conn) {
    defer conn.Close()
    conn.SetReadDeadline(time.Now().Add(30 * time.Second))

    scanner := bufio.NewScanner(conn)
    for scanner.Scan() {
        line := scanner.Text()
        log.Printf("Received: %s from %s", line, conn.RemoteAddr())
        fmt.Fprintf(conn, "Echo: %s\n", line)
        // 每次收到數據後刷新超時
        conn.SetReadDeadline(time.Now().Add(30 * time.Second))
    }
}

5.2 TCP 客戶端

func tcpClient() {
    conn, err := net.DialTimeout("tcp", "localhost:9000", 5*time.Second)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    fmt.Fprintln(conn, "Hello, Server!")

    response, err := bufio.NewReader(conn).ReadString('\n')
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Response: %s", response)
}

5.3 帶長度前綴的消息協議

TCP 是流協議,需要自行處理消息邊界。常見方案是長度前綴:

// 發送:4字節大端序長度前綴 + 消息體
func sendMessage(conn net.Conn, msg []byte) error {
    length := uint32(len(msg))
    buf := make([]byte, 4+len(msg))
    binary.BigEndian.PutUint32(buf[:4], length)
    copy(buf[4:], msg)
    _, err := conn.Write(buf)
    return err
}

// 接收
func recvMessage(conn net.Conn) ([]byte, error) {
    header := make([]byte, 4)
    if _, err := io.ReadFull(conn, header); err != nil {
        return nil, err
    }
    length := binary.BigEndian.Uint32(header)
    if length > 10<<20 { // 限制最大 10MB
        return nil, fmt.Errorf("message too large: %d", length)
    }
    data := make([]byte, length)
    if _, err := io.ReadFull(conn, data); err != nil {
        return nil, err
    }
    return data, nil
}

5.4 UDP 編程

// UDP 服務器
func udpServer() {
    addr, _ := net.ResolveUDPAddr("udp", ":9001")
    conn, err := net.ListenUDP("udp", addr)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    buf := make([]byte, 1024)
    for {
        n, remoteAddr, err := conn.ReadFromUDP(buf)
        if err != nil {
            continue
        }
        log.Printf("From %s: %s", remoteAddr, buf[:n])
        conn.WriteToUDP([]byte("ACK"), remoteAddr)
    }
}

// UDP 客戶端
func udpClient() {
    addr, _ := net.ResolveUDPAddr("udp", "localhost:9001")
    conn, err := net.DialUDP("udp", nil, addr)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    conn.Write([]byte("Hello UDP"))
    buf := make([]byte, 1024)
    n, _ := conn.Read(buf)
    fmt.Println("Response:", string(buf[:n]))
}

5.5 併發連接限制

func startLimitedServer(maxConns int) {
    listener, _ := net.Listen("tcp", ":9000")
    sem := make(chan struct{}, maxConns) // 信號量限制併發

    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        sem <- struct{}{} // 獲取信號量
        go func() {
            defer func() { <-sem }() // 釋放信號量
            handleConnection(conn)
        }()
    }
}

六、HTTP 客戶端與連接池管理

6.1 自定義 Transport(連接池核心)

transport := &http.Transport{
    // 連接池配置
    MaxIdleConns:        100,              // 全局最大空閒連接數
    MaxIdleConnsPerHost: 10,               // 每個 Host 最大空閒連接數
    MaxConnsPerHost:     100,              // 每個 Host 最大連接數(含活躍+空閒)
    IdleConnTimeout:     90 * time.Second, // 空閒連接超時回收

    // 連接超時
    DialContext: (&net.Dialer{
        Timeout:   30 * time.Second, // TCP 連接超時
        KeepAlive: 30 * time.Second, // TCP Keep-Alive 間隔
    }).DialContext,

    // TLS 握手超時
    TLSHandshakeTimeout:   10 * time.Second,
    ExpectContinueTimeout: 1 * time.Second,
    ResponseHeaderTimeout: 10 * time.Second,

    // 啓用 HTTP/2
    ForceAttemptHTTP2: true,
}

client := &http.Client{
    Timeout:   30 * time.Second, // 整個請求超時(含連接、發送、接收)
    Transport: transport,
}

關鍵要點: - http.DefaultClient 沒有超時,生產環境絕對不能用 - http.Transport 內部維護連接池,必須複用,不要每次請求新建 - MaxIdleConnsPerHost 默認值只有 2,高併發場景必須調大 - 一定要讀完並關閉 resp.Body,否則連接無法歸還連接池

6.2 構造請求

// 帶 Context 的請求(推薦)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

req, err := http.NewRequestWithContext(ctx, "POST", "https://api.example.com/users", body)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("User-Agent", "MyApp/1.0")

resp, err := client.Do(req)
if err != nil {
    return err
}
defer resp.Body.Close()

// 必須讀完 Body,即使不用,否則連接不會歸還連接池
if resp.StatusCode != http.StatusOK {
    io.Copy(io.Discard, resp.Body)
    return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}

6.3 帶重試與指數退避

func doWithRetry(client *http.Client, req *http.Request, maxRetries int) (*http.Response, error) {
    var resp *http.Response
    var err error

    for attempt := 0; attempt <= maxRetries; attempt++ {
        // 重要:需要克隆 Body,因爲請求體只能讀一次
        if req.GetBody != nil {
            req.Body, _ = req.GetBody()
        }
        resp, err = client.Do(req)
        if err == nil && resp.StatusCode < 500 {
            return resp, nil
        }
        if resp != nil {
            io.Copy(io.Discard, resp.Body)
            resp.Body.Close()
        }
        // 指數退避 + 抖動
        backoff := time.Duration(1<<uint(attempt)) * time.Second
        jitter := time.Duration(rand.Int63n(int64(backoff / 2)))
        time.Sleep(backoff + jitter)
    }
    return resp, fmt.Errorf("failed after %d retries: %w", maxRetries, err)
}

6.4 連接池監控

// 獲取連接池狀態(用於 Prometheus 指標等)
func monitorTransport(t *http.Transport) {
    ticker := time.NewTicker(10 * time.Second)
    for range ticker.C {
        // Go 沒有直接暴露連接池狀態的 API
        // 可通過 CloseIdleConnections() 手動清理
        // 或用 httptrace 包追蹤連接複用情況
    }
}

// 使用 httptrace 追蹤連接行爲
import "net/http/httptrace"

trace := &httptrace.ClientTrace{
    GotConn: func(info httptrace.GotConnInfo) {
        log.Printf("連接複用: %v, 空閒: %v, 空閒時長: %v",
            info.Reused, info.WasIdle, info.IdleTime)
    },
    ConnectStart: func(network, addr string) {
        log.Printf("開始建立連接: %s %s", network, addr)
    },
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

七、Web 安全(CSRF、XSS、SQL 注入防護、JWT 認證)

7.1 CSRF 防護

原理: 跨站請求僞造利用瀏覽器自動攜帶 Cookie 的機制。防禦方式是在表單中嵌入一個服務器生成的隨機 Token,提交時驗證。

import "crypto/rand"

// 生成 CSRF Token
func generateCSRFToken() string {
    b := make([]byte, 32)
    rand.Read(b)
    return base64.URLEncoding.EncodeToString(b)
}

// CSRF 中間件
func csrfMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // GET/HEAD/OPTIONS 請求不檢查
        if r.Method == "GET" || r.Method == "HEAD" || r.Method == "OPTIONS" {
            // 生成 Token 並設置 Cookie
            token := generateCSRFToken()
            http.SetCookie(w, &http.Cookie{
                Name:     "csrf_token",
                Value:    token,
                Path:     "/",
                HttpOnly: false, // JS 需要讀取
                SameSite: http.SameSiteStrictMode,
            })
            ctx := context.WithValue(r.Context(), "csrf_token", token)
            next.ServeHTTP(w, r.WithContext(ctx))
            return
        }

        // POST/PUT/DELETE 請求驗證 Token
        cookie, err := r.Cookie("csrf_token")
        if err != nil {
            http.Error(w, "CSRF token missing", http.StatusForbidden)
            return
        }
        formToken := r.Header.Get("X-CSRF-Token")
        if formToken == "" {
            formToken = r.FormValue("csrf_token")
        }
        if !hmac.Equal([]byte(cookie.Value), []byte(formToken)) {
            http.Error(w, "CSRF token mismatch", http.StatusForbidden)
            return
        }
        next.ServeHTTP(w, r)
    })
}

推薦使用 gorilla/csrf 庫:

import "github.com/gorilla/csrf"

csrfProtect := csrf.Protect(
    []byte("32-byte-long-auth-key-here!!!!!"),
    csrf.Secure(true), // 僅 HTTPS
)
http.ListenAndServe(":8080", csrfProtect(mux))

7.2 XSS 防護

核心原則:永遠不要信任用戶輸入,輸出時必須轉義。

// 1. html/template 自動轉義(最重要的防線)
// html/template 會自動對 {{.}} 進行 HTML 轉義
// <script>alert('xss')</script> -> &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;

// 2. 手動轉義
import "html"
safe := html.EscapeString(userInput)

// 3. 輸入清洗(使用 bluemonday)
import "github.com/microcosm-cc/bluemonday"

// 嚴格模式:去除所有 HTML 標籤
p := bluemonday.StrictPolicy()
clean := p.Sanitize(userInput)

// UGC 模式:允許安全標籤
p := bluemonday.UGCPolicy()
clean := p.Sanitize(userInput)

// 4. Content-Security-Policy 響應頭
func securityHeaders(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Security-Policy",
            "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'")
        w.Header().Set("X-Content-Type-Options", "nosniff")
        w.Header().Set("X-Frame-Options", "DENY")
        w.Header().Set("X-XSS-Protection", "1; mode=block")
        w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
        next.ServeHTTP(w, r)
    })
}

7.3 SQL 注入防護

// 錯誤寫法:直接拼接 SQL(嚴重安全漏洞)
query := "SELECT * FROM users WHERE name = '" + username + "'"
// 攻擊者輸入: ' OR '1'='1

// 正確寫法:使用參數化查詢(預編譯語句)
// MySQL
row := db.QueryRow("SELECT id, name FROM users WHERE name = ?", username)

// PostgreSQL
row := db.QueryRow("SELECT id, name FROM users WHERE name = $1", username)

// 預編譯語句(高頻查詢推薦)
stmt, err := db.Prepare("SELECT id, name FROM users WHERE name = ? AND age > ?")
defer stmt.Close()
rows, err := stmt.Query(username, age)

// ORM 也要注意:避免原生 SQL 拼接
// GORM 正確方式
db.Where("name = ?", username).First(&user)
// GORM 錯誤方式
db.Where(fmt.Sprintf("name = '%s'", username)).First(&user) // 仍然有注入風險

7.4 JWT 認證完整實現

import "github.com/golang-jwt/jwt/v5"

var jwtSecret = []byte(os.Getenv("JWT_SECRET"))

type Claims struct {
    UserID int64  `json:"user_id"`
    Role   string `json:"role"`
    jwt.RegisteredClaims
}

// 生成 Access Token + Refresh Token
func generateTokenPair(userID int64, role string) (accessToken, refreshToken string, err error) {
    // Access Token(短期,如 15 分鐘)
    accessClaims := Claims{
        UserID: userID,
        Role:   role,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "myapp",
        },
    }
    accessToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims).SignedString(jwtSecret)
    if err != nil {
        return
    }

    // Refresh Token(長期,如 7 天)
    refreshClaims := jwt.RegisteredClaims{
        ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),
        IssuedAt:  jwt.NewNumericDate(time.Now()),
        Subject:   strconv.FormatInt(userID, 10),
    }
    refreshToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims).SignedString(jwtSecret)
    return
}

// 驗證 Token
func parseToken(tokenString string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &Claims{},
        func(token *jwt.Token) (interface{}, error) {
            if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
            }
            return jwtSecret, nil
        })
    if err != nil {
        return nil, err
    }
    if claims, ok := token.Claims.(*Claims); ok && token.Valid {
        return claims, nil
    }
    return nil, errors.New("invalid token")
}

// JWT 認證中間件
func jwtAuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        authHeader := r.Header.Get("Authorization")
        if !strings.HasPrefix(authHeader, "Bearer ") {
            http.Error(w, "Missing or invalid Authorization header", http.StatusUnauthorized)
            return
        }
        tokenString := strings.TrimPrefix(authHeader, "Bearer ")
        claims, err := parseToken(tokenString)
        if err != nil {
            http.Error(w, "Invalid token: "+err.Error(), http.StatusUnauthorized)
            return
        }
        ctx := context.WithValue(r.Context(), "claims", claims)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// 基於角色的權限控制
func requireRole(role string, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        claims := r.Context().Value("claims").(*Claims)
        if claims.Role != role {
            http.Error(w, "Forbidden", http.StatusForbidden)
            return
        }
        next.ServeHTTP(w, r)
    })
}

7.5 密碼安全

import "golang.org/x/crypto/bcrypt"

func hashPassword(password string) (string, error) {
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    return string(bytes), err
}

func checkPassword(hashedPassword, password string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
    return err == nil
}

7.6 請求限流

import "golang.org/x/time/rate"

// 全侷限流
func rateLimitMiddleware(rps float64, burst int) Middleware {
    limiter := rate.NewLimiter(rate.Limit(rps), burst)
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if !limiter.Allow() {
                http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

// 按 IP 限流
type IPRateLimiter struct {
    limiters sync.Map
    rate     rate.Limit
    burst    int
}

func (rl *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
    if limiter, ok := rl.limiters.Load(ip); ok {
        return limiter.(*rate.Limiter)
    }
    limiter := rate.NewLimiter(rl.rate, rl.burst)
    rl.limiters.Store(ip, limiter)
    return limiter
}

八、模板引擎(html/template)

8.1 基礎用法

// 解析並渲染模板
tmpl, err := template.ParseFiles("templates/index.html")
tmpl.Execute(w, data)

// 解析目錄下所有模板
tmpl, err := template.ParseGlob("templates/*.html")
tmpl.ExecuteTemplate(w, "index.html", data)

// 生產環境推薦:啓動時預解析,避免運行時文件讀取
var templates = template.Must(template.ParseGlob("templates/*.html"))

8.2 模板語法大全

<!-- 變量輸出(自動 HTML 轉義) -->
<h1>{{.Title}}</h1>

<!-- 條件判斷 -->
{{if .IsLoggedIn}}
    <p>Welcome, {{.Username}}!</p>
{{else if .IsGuest}}
    <p>Welcome, Guest!</p>
{{else}}
    <a href="/login">Login</a>
{{end}}

<!-- 循環 -->
{{range .Items}}
    <li>{{.Name}} - {{.Price}}</li>
{{else}}
    <li>No items found.</li>
{{end}}

<!-- range 帶 index -->
{{range $index, $item := .Items}}
    <li>{{$index}}: {{$item.Name}}</li>
{{end}}

<!-- 變量賦值 -->
{{$name := .User.Name}}
<p>{{$name}}</p>

<!-- 管道操作 -->
<p>{{.CreatedAt | formatDate}}</p>
<p>{{.Name | upper | printf "Hello, %s"}}</p>

<!-- 模板嵌套 -->
{{template "header" .}}
{{template "content" .}}

<!-- 使用 block 定義可覆蓋的塊 -->
{{block "sidebar" .}}
    <p>Default sidebar content</p>
{{end}}

<!-- with 改變上下文 -->
{{with .User}}
    <p>{{.Name}} ({{.Email}})</p>
{{end}}

8.3 自定義模板函數

funcMap := template.FuncMap{
    "formatDate": func(t time.Time) string {
        return t.Format("2006-01-02 15:04:05")
    },
    "upper":    strings.ToUpper,
    "truncate": func(s string, n int) string {
        if len(s) > n {
            return s[:n] + "..."
        }
        return s
    },
    "safeHTML": func(s string) template.HTML {
        return template.HTML(s) // 注意:跳過轉義,僅用於可信內容
    },
    "dict": func(values ...interface{}) map[string]interface{} {
        m := make(map[string]interface{})
        for i := 0; i < len(values); i += 2 {
            m[values[i].(string)] = values[i+1]
        }
        return m
    },
}

// 必須在 ParseFiles 之前調用 Funcs
tmpl := template.New("").Funcs(funcMap)
tmpl = template.Must(tmpl.ParseGlob("templates/*.html"))

8.4 佈局模式(Layout / Base Template)

// templates/base.html
{{define "base"}}
<!DOCTYPE html>
<html>
<head>
    <title>{{template "title" .}}</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>
    <nav>{{template "nav" .}}</nav>
    <main>{{template "content" .}}</main>
    <footer>{{template "footer" .}}</footer>
</body>
</html>
{{end}}

// templates/home.html
{{define "title"}}Home Page{{end}}
{{define "content"}}
    <h1>Welcome, {{.User.Name}}</h1>
{{end}}

// 渲染時指定 "base" 模板
func renderPage(w http.ResponseWriter, page string, data interface{}) {
    tmpl := template.Must(template.ParseFiles(
        "templates/base.html",
        "templates/"+page+".html",
    ))
    tmpl.ExecuteTemplate(w, "base", data)
}

九、靜態文件服務

9.1 FileServer

// 基礎方式
fs := http.FileServer(http.Dir("./static"))
mux.Handle("/static/", http.StripPrefix("/static/", fs))

// Go 1.16+ embed 嵌入靜態文件(部署時無需附帶文件目錄)
//go:embed static/*
var staticFiles embed.FS

mux.Handle("/static/", http.FileServer(http.FS(staticFiles)))

// 單頁應用(SPA)支持:找不到文件時返回 index.html
func spaHandler(staticFS http.FileSystem) http.Handler {
    fileServer := http.FileServer(staticFS)
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        path := r.URL.Path
        // 嘗試打開文件
        f, err := staticFS.Open(path)
        if err != nil {
            // 文件不存在,返回 index.html(由前端路由接管)
            r.URL.Path = "/"
        } else {
            f.Close()
        }
        fileServer.ServeHTTP(w, r)
    })
}

9.2 文件上傳

func uploadHandler(w http.ResponseWriter, r *http.Request) {
    // 限制上傳大小
    r.Body = http.MaxBytesReader(w, r.Body, 10<<20) // 10MB

    err := r.ParseMultipartForm(10 << 20)
    if err != nil {
        http.Error(w, "File too large", http.StatusBadRequest)
        return
    }

    file, header, err := r.FormFile("file")
    if err != nil {
        http.Error(w, "Invalid file", http.StatusBadRequest)
        return
    }
    defer file.Close()

    // 驗證文件類型
    buf := make([]byte, 512)
    file.Read(buf)
    contentType := http.DetectContentType(buf)
    if contentType != "image/jpeg" && contentType != "image/png" {
        http.Error(w, "Only JPEG and PNG allowed", http.StatusBadRequest)
        return
    }
    file.Seek(0, io.SeekStart) // 重置讀取位置

    // 生成安全文件名
    ext := filepath.Ext(header.Filename)
    newName := fmt.Sprintf("%s%s", uuid.New().String(), ext)

    dst, err := os.Create(filepath.Join("./uploads", newName))
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer dst.Close()

    io.Copy(dst, file)
    fmt.Fprintf(w, "Uploaded: %s (%d bytes)", newName, header.Size)
}

9.3 文件下載

func downloadHandler(w http.ResponseWriter, r *http.Request) {
    filename := filepath.Base(r.URL.Query().Get("file")) // 安全處理:只取文件名
    filePath := filepath.Join("./files", filename)

    // 檢查文件是否存在
    if _, err := os.Stat(filePath); os.IsNotExist(err) {
        http.Error(w, "File not found", http.StatusNotFound)
        return
    }

    w.Header().Set("Content-Disposition", "attachment; filename="+filename)
    w.Header().Set("Content-Type", "application/octet-stream")
    http.ServeFile(w, r, filePath)
}

十、Session/Cookie 管理

// 設置 Cookie
func setSessionCookie(w http.ResponseWriter, sessionID string) {
    http.SetCookie(w, &http.Cookie{
        Name:     "session_id",
        Value:    sessionID,
        Path:     "/",
        HttpOnly: true,                    // JS 不可讀取(防 XSS)
        Secure:   true,                    // 僅 HTTPS 傳輸
        SameSite: http.SameSiteLaxMode,    // 防 CSRF
        MaxAge:   86400,                   // 過期時間(秒)
    })
}

// 讀取 Cookie
func getSessionID(r *http.Request) (string, error) {
    cookie, err := r.Cookie("session_id")
    if err != nil {
        return "", err
    }
    return cookie.Value, nil
}

// 刪除 Cookie
func clearSessionCookie(w http.ResponseWriter) {
    http.SetCookie(w, &http.Cookie{
        Name:   "session_id",
        Value:  "",
        Path:   "/",
        MaxAge: -1, // 立即過期
    })
}

10.2 服務端 Session 存儲

// 內存存儲(僅開發環境,不支持多實例部署)
type MemorySessionStore struct {
    mu       sync.RWMutex
    sessions map[string]*Session
}

type Session struct {
    ID        string
    Data      map[string]interface{}
    CreatedAt time.Time
    ExpiresAt time.Time
}

func (store *MemorySessionStore) Get(id string) (*Session, bool) {
    store.mu.RLock()
    defer store.mu.RUnlock()
    s, ok := store.sessions[id]
    if !ok || time.Now().After(s.ExpiresAt) {
        return nil, false
    }
    return s, true
}

func (store *MemorySessionStore) Create() *Session {
    store.mu.Lock()
    defer store.mu.Unlock()
    id := generateSessionID()
    s := &Session{
        ID:        id,
        Data:      make(map[string]interface{}),
        CreatedAt: time.Now(),
        ExpiresAt: time.Now().Add(24 * time.Hour),
    }
    store.sessions[id] = s
    return s
}

func generateSessionID() string {
    b := make([]byte, 32)
    rand.Read(b)
    return base64.URLEncoding.EncodeToString(b)
}

10.3 使用 gorilla/sessions(推薦)

import "github.com/gorilla/sessions"

// Cookie Store(Session 數據加密存儲在 Cookie 中)
var store = sessions.NewCookieStore([]byte("secret-key"))

// 或使用 Redis/數據庫後端
// var store = redistore.NewRediStore(10, "tcp", "localhost:6379", "", []byte("secret-key"))

func loginHandler(w http.ResponseWriter, r *http.Request) {
    session, _ := store.Get(r, "session-name")
    // 認證邏輯...
    session.Values["user_id"] = user.ID
    session.Values["role"] = user.Role
    session.Options.MaxAge = 86400 * 7 // 7 天
    session.Save(r, w)
}

func protectedHandler(w http.ResponseWriter, r *http.Request) {
    session, _ := store.Get(r, "session-name")
    userID, ok := session.Values["user_id"].(int64)
    if !ok {
        http.Redirect(w, r, "/login", http.StatusSeeOther)
        return
    }
    // 已認證,繼續處理...
}

func logoutHandler(w http.ResponseWriter, r *http.Request) {
    session, _ := store.Get(r, "session-name")
    session.Options.MaxAge = -1 // 刪除 Session
    session.Save(r, w)
    http.Redirect(w, r, "/", http.StatusSeeOther)
}

10.4 Redis Session 存儲(生產環境推薦)

import "github.com/redis/go-redis/v9"

type RedisSessionStore struct {
    client *redis.Client
    prefix string
    ttl    time.Duration
}

func (s *RedisSessionStore) Get(ctx context.Context, sessionID string) (map[string]interface{}, error) {
    data, err := s.client.Get(ctx, s.prefix+sessionID).Bytes()
    if err == redis.Nil {
        return nil, nil
    }
    var session map[string]interface{}
    json.Unmarshal(data, &session)
    return session, err
}

func (s *RedisSessionStore) Set(ctx context.Context, sessionID string, data map[string]interface{}) error {
    b, _ := json.Marshal(data)
    return s.client.Set(ctx, s.prefix+sessionID, b, s.ttl).Err()
}

func (s *RedisSessionStore) Delete(ctx context.Context, sessionID string) error {
    return s.client.Del(ctx, s.prefix+sessionID).Err()
}

十一、Web 框架對比(Gin、Echo 等)

11.1 標準庫 vs 框架選型參考

特性net/http (1.22+)GinEchoChi
路由參數支持支持支持支持
方法路由支持支持支持支持
路由分組不支持支持支持支持
中間件手動鏈內置內置內置
參數綁定手動自動自動手動
驗證手動集成集成手動
性能極高極高
依賴較少較少極少
學習曲線

選型建議: - 小型項目/微服務:標準庫 net/http(Go 1.22+) 或 Chi - 中大型 API 項目:Gin(生態最大、社區最活躍) - 需要極致性能:Gin 或 Echo - 偏好標準庫風格:Chi(完全兼容 net/http

11.2 Gin 框架核心用法

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default() // 包含 Logger 和 Recovery 中間件

    // 路由分組
    api := r.Group("/api/v1")
    api.Use(authMiddleware()) // 組級中間件
    {
        api.GET("/users", listUsers)
        api.GET("/users/:id", getUser)
        api.POST("/users", createUser)
        api.PUT("/users/:id", updateUser)
        api.DELETE("/users/:id", deleteUser)
    }

    r.Run(":8080")
}

// Handler 使用 gin.Context
func getUser(c *gin.Context) {
    id := c.Param("id")      // 路徑參數
    name := c.Query("name")  // 查詢參數
    page := c.DefaultQuery("page", "1")

    user, err := findUser(id)
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
        return
    }
    c.JSON(http.StatusOK, gin.H{"data": user})
}

// 參數綁定與驗證
type CreateUserInput struct {
    Name     string `json:"name" binding:"required,min=2,max=50"`
    Email    string `json:"email" binding:"required,email"`
    Password string `json:"password" binding:"required,min=8"`
}

func createUser(c *gin.Context) {
    var input CreateUserInput
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    // 創建用戶...
    c.JSON(http.StatusCreated, gin.H{"data": user})
}

// Gin 中間件
func authMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
            return
        }
        claims, err := parseToken(strings.TrimPrefix(token, "Bearer "))
        if err != nil {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
            return
        }
        c.Set("user_id", claims.UserID) // 存入上下文
        c.Next()                         // 繼續處理
    }
}

11.3 Echo 框架核心用法

import "github.com/labstack/echo/v4"

func main() {
    e := echo.New()

    // 內置中間件
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    e.Use(middleware.CORS())
    e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))

    // 路由分組
    api := e.Group("/api/v1")
    api.Use(jwtMiddleware)
    api.GET("/users", listUsers)
    api.GET("/users/:id", getUser)
    api.POST("/users", createUser)

    e.Logger.Fatal(e.Start(":8080"))
}

func getUser(c echo.Context) error {
    id := c.Param("id")
    user, err := findUser(id)
    if err != nil {
        return echo.NewHTTPError(http.StatusNotFound, "user not found")
    }
    return c.JSON(http.StatusOK, user)
}

// 參數綁定
type CreateUserInput struct {
    Name  string `json:"name" validate:"required"`
    Email string `json:"email" validate:"required,email"`
}

func createUser(c echo.Context) error {
    var input CreateUserInput
    if err := c.Bind(&input); err != nil {
        return err
    }
    if err := c.Validate(&input); err != nil {
        return err
    }
    // ...
    return c.JSON(http.StatusCreated, user)
}

11.4 Chi 框架(標準庫兼容風格)

import "github.com/go-chi/chi/v5"

func main() {
    r := chi.NewRouter()

    // 中間件(標準 net/http 中間件完全兼容)
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Use(middleware.Timeout(60 * time.Second))

    r.Route("/api/v1", func(r chi.Router) {
        r.Use(jwtAuth) // 組級中間件

        r.Route("/users", func(r chi.Router) {
            r.Get("/", listUsers)       // GET /api/v1/users
            r.Post("/", createUser)     // POST /api/v1/users

            r.Route("/{id}", func(r chi.Router) {
                r.Get("/", getUser)     // GET /api/v1/users/{id}
                r.Put("/", updateUser)  // PUT /api/v1/users/{id}
                r.Delete("/", deleteUser)
            })
        })
    })

    http.ListenAndServe(":8080", r)
}

// Handler 簽名完全是標準 net/http
func getUser(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")
    // ...
}

十二、Context 在 Web 開發中的應用

12.1 請求級 Context

func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context() // 請求取消時自動 cancel

    // 派生超時 Context
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    result, err := queryDatabase(ctx, "SELECT ...")
    if err != nil {
        if ctx.Err() == context.DeadlineExceeded {
            http.Error(w, "Request timeout", http.StatusGatewayTimeout)
            return
        }
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    respondJSON(w, http.StatusOK, result)
}

12.2 在中間件中傳遞值

type contextKey string

const userKey contextKey = "user"

func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        user, err := authenticate(r)
        if err != nil {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        ctx := context.WithValue(r.Context(), userKey, user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func profileHandler(w http.ResponseWriter, r *http.Request) {
    user := r.Context().Value(userKey).(*User)
    respondJSON(w, http.StatusOK, user)
}

十三、RPC 與 gRPC

13.1 標準庫 net/rpc

type MathService struct{}

type Args struct {
    A, B int
}

func (m *MathService) Add(args *Args, reply *int) error {
    *reply = args.A + args.B
    return nil
}

// 服務端
func startRPCServer() {
    rpc.Register(new(MathService))
    rpc.HandleHTTP()
    listener, _ := net.Listen("tcp", ":1234")
    http.Serve(listener, nil)
}

// 客戶端
func rpcClient() {
    client, _ := rpc.DialHTTP("tcp", "localhost:1234")
    var reply int
    client.Call("MathService.Add", &Args{7, 8}, &reply)
    fmt.Printf("7 + 8 = %d\n", reply) // 15
}

13.2 gRPC

// user.proto
syntax = "proto3";
package user;

service UserService {
    rpc GetUser (GetUserRequest) returns (UserResponse);
    rpc ListUsers (ListUsersRequest) returns (stream UserResponse);
}

message GetUserRequest { int64 id = 1; }
message UserResponse {
    int64 id = 1;
    string name = 2;
    string email = 3;
}
// 服務端
type userServer struct {
    pb.UnimplementedUserServiceServer
}

func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.UserResponse, error) {
    return &pb.UserResponse{Id: req.Id, Name: "Alice", Email: "alice@example.com"}, nil
}

func main() {
    lis, _ := net.Listen("tcp", ":50051")
    s := grpc.NewServer()
    pb.RegisterUserServiceServer(s, &userServer{})
    s.Serve(lis)
}

十四、數據庫操作

14.1 database/sql

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

func initDB() (*sql.DB, error) {
    db, err := sql.Open("mysql", "user:pass@tcp(localhost:3306)/dbname?parseTime=true")
    if err != nil {
        return nil, err
    }
    db.SetMaxOpenConns(25)              // 最大打開連接數
    db.SetMaxIdleConns(5)               // 最大空閒連接數
    db.SetConnMaxLifetime(5 * time.Minute) // 連接最大存活時間
    return db, db.Ping()
}

// 查詢
var user User
err := db.QueryRowContext(ctx, "SELECT id, name FROM users WHERE id = ?", id).
    Scan(&user.ID, &user.Name)

// 事務
tx, err := db.BeginTx(ctx, nil)
defer tx.Rollback()
tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
tx.ExecContext(ctx, "UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID)
err = tx.Commit()

十五、優雅關閉(Graceful Shutdown)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", homeHandler)

    server := &http.Server{Addr: ":8080", Handler: mux}

    go func() {
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("HTTP server error: %v", err)
        }
    }()

    // 等待中斷信號
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    log.Println("Shutting down server...")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("Server forced to shutdown: %v", err)
    }
    log.Println("Server exited gracefully")
}

十六、DNS 與地址解析

// DNS 查詢
ips, _ := net.LookupIP("example.com")
names, _ := net.LookupAddr("93.184.216.34")
cname, _ := net.LookupCNAME("www.example.com")
mxRecords, _ := net.LookupMX("example.com")

// 解析地址
host, port, _ := net.SplitHostPort("localhost:8080")
addr, _ := net.ResolveTCPAddr("tcp", "localhost:8080")

十七、常用第三方庫速查

用途庫名說明
HTTP 路由chi, gorilla/mux功能豐富的路由器
Web 框架gin, echo, fiber高性能 Web 框架
ORMgorm, ent, sqlx數據庫 ORM
驗證go-playground/validator結構體字段驗證
配置viper, envconfig配置管理
日誌slog, zap, zerolog結構化日誌
JWTgolang-jwt/jwtJWT 認證
WebSocketgorilla/websocket, nhooyr/websocketWebSocket 支持
HTTP 客戶端resty, go-retryablehttp增強版 HTTP 客戶端
gRPCgoogle.golang.org/grpcgRPC 框架
Swaggerswaggo/swagAPI 文檔生成
CSRFgorilla/csrfCSRF 防護
Sessiongorilla/sessionsSession 管理
HTML 清洗bluemondayXSS 防護
限流x/time/rate, tollbooth請求限流

十八、項目結構最佳實踐

myapp/
├── cmd/
│   └── server/
│       └── main.go           # 入口:組裝依賴、啓動服務
├── internal/
│   ├── handler/              # HTTP 處理器(Controller 層)
│   │   ├── user.go
│   │   └── middleware.go
│   ├── service/              # 業務邏輯(Service 層)
│   │   └── user.go
│   ├── repository/           # 數據訪問(DAO 層)
│   │   └── user.go
│   └── model/                # 數據模型
│       └── user.go
├── pkg/                      # 可導出的公共包
│   ├── response/
│   └── validator/
├── api/                      # API 定義(OpenAPI/Proto)
├── configs/                  # 配置文件
├── migrations/               # 數據庫遷移
├── static/                   # 靜態資源
├── templates/                # 模板文件
├── go.mod
└── go.sum

十九、速查備忘

HTTP 狀態碼常量

http.StatusOK                  // 200
http.StatusCreated             // 201
http.StatusNoContent           // 204
http.StatusBadRequest          // 400
http.StatusUnauthorized        // 401
http.StatusForbidden           // 403
http.StatusNotFound            // 404
http.StatusMethodNotAllowed    // 405
http.StatusConflict            // 409
http.StatusUnprocessableEntity // 422
http.StatusTooManyRequests     // 429
http.StatusInternalServerError // 500
http.StatusBadGateway          // 502
http.StatusServiceUnavailable  // 503

net 包關鍵接口

type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    Close() error
    LocalAddr() Addr
    RemoteAddr() Addr
    SetDeadline(t time.Time) error
    SetReadDeadline(t time.Time) error
    SetWriteDeadline(t time.Time) error
}

type Listener interface {
    Accept() (Conn, error)
    Close() error
    Addr() Addr
}