← 返回
后端开发 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
}