编程基础 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…

    后端开发 21小时前
    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