文章

HTTP 服务

HTTP 服务

包总览

类型/函数用途
http.ServeMux路由注册与分发
http.Handler请求处理器接口
http.HandlerFunc函数适配器
http.ClientHTTP 客户端
http.ServerHTTP 服务器配置
http.Request请求对象
http.ResponseWriter响应写入器
http.CookieCookie 操作
http.Transport传输层配置

HTTP 服务端

基础路由

// Go 1.22+ 增强版 ServeMux:支持方法和路径参数
mux := http.NewServeMux()

// Go 1.22+ 新语法
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintf(w, "User: %s", id)
})

mux.HandleFunc("POST /users", createUser)
mux.HandleFunc("PUT /users/{id}", updateUser)
mux.HandleFunc("DELETE /users/{id}", deleteUser)

// 通配符
mux.HandleFunc("GET /files/{path...}", serveFile)
// r.PathValue("path") 获取剩余路径

// Go 1.21 及之前
mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        // ...
    case "POST":
        // ...
    }
})

http.ListenAndServe(":8080", mux)

ServeMux vs 第三方路由对比

特性ServeMux (1.22+)chiginecho
路径参数{id}/{id}/:id/:id
方法路由GET /path
中间件
路由分组
性能最高
依赖
推荐度简单项目★★★★★★★★★☆★★★★☆

Handler 接口

// http.Handler 接口
type Handler interface {
    ServeHTTP(w http.ResponseWriter, r *http.Request)
}

// 实现 Handler 接口
type UserHandler struct {
    svc UserService
}

func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 路由逻辑
    switch r.Method {
    case "GET":
        h.getUser(w, r)
    case "POST":
        h.createUser(w, r)
    }
}

// http.HandlerFunc:函数适配器
type HandlerFunc func(http.ResponseWriter, *http.Request)

// 任何签名为 func(http.ResponseWriter, *http.Request) 的函数
// 都可以通过 http.HandlerFunc 转换为 Handler
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
    // ...
})

Request 解析

func handler(w http.ResponseWriter, r *http.Request) {
    // 请求行
    r.Method        // "GET", "POST", "PUT", "DELETE", "PATCH"
    r.URL           // *url.URL
    r.URL.Path      // "/api/v1/users"
    r.URL.RawQuery  // "page=1&limit=20"
    r.Proto         // "HTTP/1.1"

    // 路径参数(Go 1.22+)
    r.PathValue("id")

    // Query 参数
    r.URL.Query().Get("page")      // "1"
    r.URL.Query().Get("limit")     // "20"
    r.URL.Query()["tags"]           // []string{"go", "k8s"}

    // Header
    r.Header.Get("Content-Type")   // "application/json"
    r.Header.Get("Authorization")  // "Bearer xxx"
    r.Header.Get("X-Request-ID")
    r.Header["Set-Cookie"]          // 多值 header

    // Cookie
    cookie, err := r.Cookie("session_id")
    if err != nil {
        if errors.Is(err, http.ErrNoCookie) {
            // cookie 不存在
        }
    }
    cookies := r.Cookies()  // []*http.Cookie

    // Body
    body, err := io.ReadAll(r.Body)
    defer r.Body.Close()

    // JSON Body 解析
    var req CreateUserReq
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "invalid JSON", http.StatusBadRequest)
        return
    }

    // Form 表单
    r.ParseForm()
    r.Form.Get("username")
    r.PostForm.Get("password")

    // Multipart 表单
    r.ParseMultipartForm(32 << 20)  // 32MB max memory
    r.FormFile("upload")  // 文件上传

    // 客户端信息
    r.RemoteAddr   // "192.168.1.1:12345"
    r.Host         // "example.com"
    r.Referer()    // 来源页
    r.UserAgent()  // 浏览器标识

    // Context(请求取消、超时)
    ctx := r.Context()
}

ResponseWriter 写入

func handler(w http.ResponseWriter, r *http.Request) {
    // 设置 Header(必须在 WriteHeader/Write 之前)
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("X-Request-ID", "abc123")

    // 多值 Header
    w.Header().Add("Set-Cookie", "a=1")
    w.Header().Add("Set-Cookie", "b=2")

    // 设置状态码
    w.WriteHeader(http.StatusOK)  // 200
    w.WriteHeader(http.StatusCreated)  // 201
    w.WriteHeader(http.StatusNotFound)  // 404

    // 写入 Body
    w.Write([]byte("hello"))

    // JSON 响应
    json.NewEncoder(w).Encode(map[string]interface{}{
        "code": 0,
        "data": user,
    })

    // 设置 Cookie
    http.SetCookie(w, &http.Cookie{
        Name:     "session_id",
        Value:    "abc123",
        Path:     "/",
        HttpOnly:  true,
        Secure:    true,            // 仅 HTTPS
        SameSite:  http.SameSiteStrictMode,
        Expires:   time.Now().Add(24 * time.Hour),
        MaxAge:    86400,
    })

    // 重定向
    http.Redirect(w, r, "/new-path", http.StatusFound)

    // 文件下载
    w.Header().Set("Content-Disposition", `attachment; filename="report.csv"`)
    http.ServeFile(w, r, "/path/to/report.csv")

    // flush(流式响应)
    flusher, ok := w.(http.Flusher)
    if ok {
        w.Write([]byte("data chunk 1"))
        flusher.Flush()
        w.Write([]byte("data chunk 2"))
        flusher.Flush()
    }
}

HTTP 状态码速查

状态码常量含义
200StatusOK成功
201StatusCreated创建成功
204StatusNoContent成功无内容
301StatusMovedPermanently永久重定向
302StatusFound临时重定向
304StatusNotModified缓存有效
400StatusBadRequest请求格式错误
401StatusUnauthorized未认证
403StatusForbidden无权限
404StatusNotFound资源不存在
409StatusConflict冲突
422StatusUnprocessableEntity验证失败
429StatusTooManyRequests限流
500StatusInternalServerError服务器错误
502StatusBadGateway网关错误
503StatusServiceUnavailable服务不可用
504StatusGatewayTimeout网关超时

中间件

中间件模式

// 中间件签名
type Middleware func(http.Handler) http.Handler

// 日志中间件
func Logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// Recovery 中间件
func Recovery(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("panic: %v\n%s", err, debug.Stack())
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

// CORS 中间件
func CORS(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")

        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// 请求 ID 中间件
func RequestID(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        id := r.Header.Get("X-Request-ID")
        if id == "" {
            id = uuid.New().String()
        }
        w.Header().Set("X-Request-ID", id)
        ctx := context.WithValue(r.Context(), "requestID", id)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// 认证中间件
func Auth(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        user, err := verifyToken(token)
        if err != nil {
            http.Error(w, "Invalid token", http.StatusUnauthorized)
            return
        }
        ctx := context.WithValue(r.Context(), "user", user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// 限流中间件
func RateLimit(rate int, burst int) Middleware {
    limiter := rate.NewLimiter(rate.Limit(rate), 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)
        })
    }
}

// 中间件链
func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// 使用
handler := Chain(
    myHandler,
    Recovery,
    Logging,
    RequestID,
    CORS,
)

可记录状态的 ResponseWriter

type statusRecorder struct {
    http.ResponseWriter
    status int
    size   int
}

func (r *statusRecorder) WriteHeader(status int) {
    r.status = status
    r.ResponseWriter.WriteHeader(status)
}

func (r *statusRecorder) Write(b []byte) (int, error) {
    n, err := r.ResponseWriter.Write(b)
    r.size += n
    return n, err
}

func Logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        rec := &statusRecorder{ResponseWriter: w, status: 200}

        next.ServeHTTP(rec, r)

        log.Printf("%s %s %d %d %v",
            r.Method, r.URL.Path, rec.status, rec.size, time.Since(start))
    })
}

HTTP 服务器配置

server := &http.Server{
    Addr:         ":8080",
    Handler:      mux,
    ReadTimeout:  10 * time.Second,   // 读取请求超时
    WriteTimeout: 30 * time.Second,   // 写响应超时
    IdleTimeout:  120 * time.Second,  // 空闲连接超时
    MaxHeaderBytes: 1 << 20,          // 1MB header 限制
    ErrorLog:     log.New(os.Stderr, "http: ", log.LstdFlags),
}

// 使用 TLS
// server.ListenAndServeTLS("cert.pem", "key.pem")

// 优雅关闭
go func() {
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
    <-sigCh

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Printf("server shutdown: %v", err)
    }
}()

if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
    log.Fatal(err)
}

服务器超时配置建议

参数推荐值说明
ReadTimeout10-15s客户端发送请求的时间限制
WriteTimeout30-60s服务端写响应的时间限制
IdleTimeout120skeep-alive 空闲超时
MaxHeaderBytes1MB防止超大 header 攻击

HTTP 客户端

基本请求

// 简单 GET
resp, err := http.Get("https://api.example.com/users")
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))

// 自定义请求
req, err := http.NewRequest("GET", "https://api.example.com/users", nil)
req.Header.Set("Authorization", "Bearer token")
req.Header.Set("Content-Type", "application/json")

resp, err := http.DefaultClient.Do(req)
defer resp.Body.Close()

POST 请求

// JSON POST
body, _ := json.Marshal(map[string]string{"name": "小徐"})
resp, err := http.Post(
    "https://api.example.com/users",
    "application/json",
    bytes.NewBuffer(body),
)

// Form POST
form := url.Values{}
form.Set("username", "小徐")
form.Set("password", "secret")
resp, err := http.PostForm("https://api.example.com/login", form)

// 带自定义 header 的 POST
req, _ := http.NewRequest("POST", "https://api.example.com/users", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer token")
resp, err := http.DefaultClient.Do(req)

高级客户端配置

client := &http.Client{
    Timeout: 30 * time.Second,  // 总超时
    Transport: &http.Transport{
        DialContext: (&net.Dialer{
            Timeout:   5 * time.Second,
            KeepAlive: 30 * time.Second,
        }).DialContext,
        MaxIdleConns:          100,              // 最大空闲连接数
        MaxIdleConnsPerHost:   10,               // 每个 host 的最大空闲连接数
        IdleConnTimeout:       90 * time.Second,  // 空闲连接超时
        TLSHandshakeTimeout:   10 * time.Second,
        ResponseHeaderTimeout: 10 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
    },
}

// 必须复用 client,不要每次请求创建新 client
// ❌ 每次 new http.Client 会导致连接泄漏
// ✓ 全局复用一个 client

带重试的请求

func DoWithRetry(client *http.Client, req *http.Request, maxRetries int) (*http.Response, error) {
    var lastErr error

    for i := 0; i < maxRetries; i++ {
        if i > 0 {
            time.Sleep(time.Duration(i*i) * time.Second)  // 指数退避
        }

        resp, err := client.Do(req)
        if err != nil {
            lastErr = err
            continue
        }

        // 5xx 重试,4xx 不重试
        if resp.StatusCode >= 500 {
            resp.Body.Close()
            lastErr = fmt.Errorf("server error: %d", resp.StatusCode)
            continue
        }

        return resp, nil
    }
    return nil, fmt.Errorf("after %d retries: %w", maxRetries, lastErr)
}

Multipart 文件上传

func uploadFile(client *http.Client, url, filePath string) error {
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)

    // 添加文件
    file, err := os.Open(filePath)
    if err != nil {
        return err
    }
    defer file.Close()

    part, err := writer.CreateFormFile("file", filepath.Base(filePath))
    if err != nil {
        return err
    }
    io.Copy(part, file)

    // 添加额外字段
    writer.WriteField("description", "my file")

    writer.Close()

    req, _ := http.NewRequest("POST", url, body)
    req.Header.Set("Content-Type", writer.FormDataContentType())

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

SSE / 流式响应

// Server-Sent Events 服务端
func SSEHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")

    flusher, ok := w.(http.Flusher)
    if !ok {
        http.Error(w, "streaming not supported", http.StatusInternalServerError)
        return
    }

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

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

// SSE 客户端
resp, _ := http.Get("https://api.example.com/events")
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
    line := scanner.Text()
    if strings.HasPrefix(line, "data: ") {
        data := line[6:]
        fmt.Println(data)
    }
}

WebSocket(标准库不支持,需第三方)

// 使用 gorilla/websocket
import "github.com/gorilla/websocket"

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    CheckOrigin: func(r *http.Request) bool {
        return true  // 生产环境应检查 Origin
    },
}

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

    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            break
        }
        // 回显
        conn.WriteMessage(messageType, message)
    }
}

HTTP 客户端检查清单

检查项说明
✅ 复用 http.Client全局共享,不要每次创建
✅ 设置超时避免无限等待
✅ 关闭 resp.Bodydefer resp.Body.Close()
✅ 配置连接池MaxIdleConnsPerHost
✅ 处理重试5xx 和网络错误
✅ 检查状态码不要假设 200
✅ 限制 body 大小io.LimitReader 防止 OOM
✅ 使用 context支持取消和超时