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

Table of Contents

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 管理

10.1 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+) Gin Echo Chi
路由參數 支持 支持 支持 支持
方法路由 支持 支持 支持 支持
路由分組 不支持 支持 支持 支持
中間件 手動鏈 內置 內置 內置
參數綁定 手動 自動 自動 手動
驗證 手動 集成 集成 手動
性能 極高 極高
依賴 較少 較少 極少
學習曲線

選型建議:
- 小型項目/微服務:標準庫 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 框架
ORM gorm, ent, sqlx 數據庫 ORM
驗證 go-playground/validator 結構體字段驗證
配置 viper, envconfig 配置管理
日誌 slog, zap, zerolog 結構化日誌
JWT golang-jwt/jwt JWT 認證
WebSocket gorilla/websocket, nhooyr/websocket WebSocket 支持
HTTP 客戶端 resty, go-retryablehttp 增強版 HTTP 客戶端
gRPC google.golang.org/grpc gRPC 框架
Swagger swaggo/swag API 文檔生成
CSRF gorilla/csrf CSRF 防護
Session gorilla/sessions Session 管理
HTML 清洗 bluemonday XSS 防護
限流 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
}

主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/6741

(0)
Walker的頭像Walker
上一篇 13小時前
下一篇 2025年2月26日 17:17

相關推薦

  • Go工程師體系課 009

    其它一些功能 個人中心 收藏 管理收貨地址(增刪改查) 留言 拷貝inventory_srv--> userop_srv 查詢替換所有的inventory Elasticsearch 深度解析文檔 1. 什麼是Elasticsearch Elasticsearch是一個基於Apache Lucene構建的分佈式、RESTful搜索和分析引擎,能夠快速地…

    後端開發 7小時前
    100
  • 編程基礎 0011_Go併發與分佈式實戰精華

    Go 併發與分佈式實戰精華 參考:《Go 併發編程實戰》(郝林)、《Mastering Concurrency in Go》(Nathan Kozyra)、《Go 語言構建高併發分佈式系統實踐》 1. 併發原語深入 1.1 atomic 包 atomic 操作直接映射到 CPU 指令(如 LOCK CMPXCHG),比 mutex 快一個數量級。 impor…

    後端開發 20小時前
    100
  • Go工程師體系課 011

    查詢的倒排索引 1. 什麼是倒排索引? 倒排索引(Inverted Index)是一種數據結構,用於快速查找包含特定詞彙的文檔。它是搜索引擎的核心技術之一。 1.1 基本概念 正排索引:文檔 ID → 文檔內容(詞列表) 倒排索引:詞 → 包含該詞的文檔 ID 列表 1.2 爲什麼叫"倒排"? 倒排索引將傳統的"文檔包含哪些詞"的關係倒轉爲"詞出現在哪些文檔…

  • 編程基礎 0002_名庫講解

    名庫講解 goconfig go 語言針對 windows 下常見的 ini 格式的配置文件解析器,該解析器在涵蓋了所有 ini 文件操作的基礎上,又針對 go 語言實際開發過程中遇到的一些需求進行了擴展。該解析器最大的優勢在於對註釋的極佳支持,除此之外,支持多個配置文件覆蓋加載也是非常特別但好用的功能。 提供與 windows api 一模一樣的操作 支持…

    16小時前
    400
  • Go工程師體系課 004

    需求分析 後臺管理系統 商品管理 商品列表 商品分類 品牌管理 品牌分類 訂單管理 訂單列表 用戶信息管理 用戶列表 用戶地址 用戶留言 輪播圖管理 電商系統 登錄頁面 首頁 商品搜索 商品分類導航 輪播圖展示 推薦商品展示 商品詳情頁 商品圖片展示 商品描述 商品規格選擇 加入購物車 購物車 商品列表 數量調整 刪除商品 結算功能 用戶中心 訂單中心 我的…

    12小時前
    100
簡體中文 繁體中文 English