Programming Fundamentals 0012_Go_Web and Network Programming Essentials

Table of Contents

Go Web and Network Programming Essentials

Knowledge Sources:
- 《Building Web Apps with Go》
- 《Go API Programming》
- 《Go Web Programming》(Go Web Programming, Sau Sheong Chang)
- 《Go Network Programming》(Network Programming with Go)
- 《Mastering Go Web Services》
- 《Go Web Programming》(2013, Xie Mengjun/astaxie)


I. Deep Dive into the net/http Package (Handler Interface, ServeMux, Middleware Chain)

1.1 Handler Interface – The Core of Everything

The core abstraction for Go HTTP handling is a single interface:

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

The entire design philosophy of net/http revolves around this interface. Routers, middleware, static file servers – all are implementations of Handler.

HandlerFunc Adapter – Functions can also be Handlers:

type HandlerFunc func(ResponseWriter, *Request)

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

This is a classic Go design pattern: enabling ordinary functions to satisfy an interface through type conversion.

// 以下两种注册方式完全等价
http.Handle("/hello", http.HandlerFunc(helloFunc))
http.HandleFunc("/hello", helloFunc)

Custom Handler Struct – Carrying Dependency Injection:

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 Routing Mechanism

DefaultServeMux vs. Custom 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)

Routing Matching Rules:
- Exact matches take precedence over prefix matches
- Patterns ending with / are subtree matches (prefix matches)
- Patterns not ending with / are exact matches
- Longest match takes precedence

Go 1.22+ Enhanced Routing – Finally Supports Methods and Path Parameters:

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")    // 提取路径参数
    // ...
}

Precedence Rules (Go 1.22+):
- More specific patterns take precedence (GET /users/{id} takes precedence over /users/{id})
- Patterns with methods take precedence over those without methods
- If two patterns conflict and cannot be compared, registration will panic

1.3 Custom Server Configuration

In production environments, a custom http.Server must be used:

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 Support:

// 方式一:直接使用证书文件
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 Middleware Pattern

Middleware is a function that takes a Handler and returns a new Handler, forming an onion model:

type Middleware func(http.Handler) http.Handler

Logging Middleware:

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 Middleware (panic recovery):

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

Middleware Chaining:

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 Middleware:

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

II. HTTP Server Implementation Principles

2.1 Source Code Analysis of Startup Process

The complete call chain of 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

Key Design: One goroutine per request. Go's goroutines are very lightweight (initial stack is only 2KB), allowing tens of thousands of concurrent connections to be handled simultaneously without callbacks or thread pools.

2.2 Detailed Connection Handling

// 简化版的 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 The Three-Layer Interface of 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) Implementation

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

III. RESTful API Design and Implementation

3.1 Resource Design and Route Registration

// 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 Unified Response Format

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 Handling Techniques

// 结构体 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 Pagination, Filtering, and Sorting

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 Request Validation

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 Version Management

// 方式一: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))
    })
}

IV. WebSocket Programming

4.1 Basic WebSocket Service

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 Chat Room Hub Pattern

This is a classic architecture for WebSocket applications, derived from the official gorilla/websocket example:

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 (A More Modern Choice)

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

V. TCP/UDP Network Programming

5.1 TCP Server

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 Client

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 Length-Prefixed Message Protocol

TCP is a stream protocol, requiring manual handling of message boundaries. A common solution is length prefixing:

// 发送: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 Programming

// 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 Concurrent Connection Limit

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

VI. HTTP Client and Connection Pool Management

6.1 Custom Transport (Connection Pool Core)

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,
}

Key Points:
- http.DefaultClient has no timeout and must never be used in production.
- http.Transport internally maintains a connection pool; it must be reused, not recreated for every request.
- MaxIdleConnsPerHost defaults to 2; it must be increased for high-concurrency scenarios.
- Always read and close resp.Body; otherwise, the connection cannot be returned to the connection pool.

6.2 Constructing Requests

// 带 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 With Retries and Exponential Backoff

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 Connection Pool Monitoring

// 获取连接池状态(用于 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))

VII. Web Security (CSRF, XSS, SQL Injection Protection, JWT Authentication)

7.1 CSRF Protection

Principle: Cross-Site Request Forgery (CSRF) exploits the browser's mechanism of automatically sending cookies. The defense method is to embed a server-generated random Token in the form and validate it upon submission.

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

Recommended to use the gorilla/csrf library:

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 Protection

Core Principle: Never trust user input; always escape it upon output.

// 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 Injection Protection

// 错误写法:直接拼接 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 Complete JWT Authentication Implementation

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 Password Security

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 Request Rate Limiting

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
}

VIII. Template Engine (html/template)

8.1 Basic Usage

// 解析并渲染模板
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 Complete Template Syntax

<!-- 变量输出(自动 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 Custom Template Functions

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 Pattern (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)
}

IX. Static File Serving

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 File Upload

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 File Download

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

X. Session/Cookie Management

10.1 Cookie Operations

// 设置 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 Server-Side Session Storage

// 内存存储(仅开发环境,不支持多实例部署)
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 Using gorilla/sessions (Recommended)

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 Storage (Recommended for Production)

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

XI. Web Framework Comparison (Gin, Echo, etc.)

11.1 Standard Library vs. Framework Selection Reference

Feature net/http (1.22+) Gin Echo Chi
Route Parameters Supported Supported Supported Supported
Method Routing Supported Supported Supported Supported
Route Grouping Not Supported Supported Supported Supported
Middleware Manual Chain Built-in Built-in Built-in
Parameter Binding Manual Automatic Automatic Manual
Validation Manual Integrated Integrated Manual
Performance High Very High Very High High
Dependencies Zero Few Few Very Few
Learning Curve Low Low Low Low

Selection Advice:
- Small projects/microservices: Standard library net/http (Go 1.22+) or Chi
- Medium to large API projects: Gin (largest ecosystem, most active community)
- For extreme performance: Gin or Echo
- Preferring standard library style: Chi (fully compatible with net/http)

11.2 Gin Framework Core Usage

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 Framework Core Usage

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 Framework (Standard Library Compatible Style)

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

XII. Context Application in Web Development

12.1 Request-Level 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 Passing Values in Middleware

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

XIII. RPC and gRPC

13.1 Standard Library 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)
}

XIV. Database Operations

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()

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

XVI. DNS and Address Resolution

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

XVII. Quick Reference for Common Third-Party Libraries

Purpose Library Name Description
HTTP Routing chi, gorilla/mux Feature-rich router
Web Framework gin, echo, fiber High-performance Web framework
ORM gorm, ent, sqlx Database ORM
Validation go-playground/validator Struct field validation
Configuration viper, envconfig Configuration management
Logging slog, zap, zerolog Structured logging
JWT golang-jwt/jwt JWT authentication
WebSocket gorilla/websocket, nhooyr/websocket WebSocket support
HTTP Client resty, go-retryablehttp Enhanced HTTP client
gRPC google.golang.org/grpc gRPC framework
Swagger swaggo/swag API documentation generation
CSRF gorilla/csrf CSRF protection
Session gorilla/sessions Session management
HTML Sanitization bluemonday XSS protection
Rate Limiting x/time/rate, tollbooth Request rate limiting

XVIII. Project Structure Best Practices

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

XIX. Quick Reference Memo

HTTP Status Code Constants

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

Key Interfaces in net Package

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 hours ago
下一篇 Feb 26, 2025 17:17

Related Posts

EN
简体中文 繁體中文 English