3.1.3 HTTP 服务端开发

3.1.3 HTTP 服务端开发 #

HTTP 服务端开发是 Web 应用的核心。Go 语言凭借其出色的并发性能和简洁的 API 设计,使得构建高性能 HTTP 服务器变得简单而高效。本节将深入探讨如何使用 Go 构建功能完整、性能优异的 HTTP 服务器。

基础服务器搭建 #

最简单的 HTTP 服务器 #

让我们从最基本的 HTTP 服务器开始:

package main

import (
    "fmt"
    "net/http"
    "log"
)

func main() {
    // 注册处理函数
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, World! 你访问的路径是: %s", r.URL.Path)
    })

    // 启动服务器
    log.Println("服务器启动在 http://localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

使用自定义 ServeMux #

虽然默认的 DefaultServeMux 很方便,但在生产环境中建议使用自定义的 ServeMux

package main

import (
    "fmt"
    "net/http"
    "log"
)

func homeHandler(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/" {
        http.NotFound(w, r)
        return
    }
    fmt.Fprint(w, "欢迎来到首页!")
}

func aboutHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "关于我们页面")
}

func main() {
    // 创建自定义 ServeMux
    mux := http.NewServeMux()

    // 注册路由
    mux.HandleFunc("/", homeHandler)
    mux.HandleFunc("/about", aboutHandler)

    // 启动服务器
    server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }

    log.Println("服务器启动在 http://localhost:8080")
    log.Fatal(server.ListenAndServe())
}

路由处理详解 #

路由模式匹配 #

Go 的 ServeMux 支持两种路由模式:

package main

import (
    "fmt"
    "net/http"
    "strings"
)

func main() {
    mux := http.NewServeMux()

    // 1. 精确匹配 - 只匹配 /users
    mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "用户列表页面")
    })

    // 2. 前缀匹配 - 匹配 /api/ 开头的所有路径
    mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
        path := strings.TrimPrefix(r.URL.Path, "/api/")
        fmt.Fprintf(w, "API 端点: %s", path)
    })

    // 3. 根路径匹配 - 匹配所有未匹配的路径
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path == "/" {
            fmt.Fprint(w, "首页")
        } else {
            http.NotFound(w, r)
        }
    })

    http.ListenAndServe(":8080", mux)
}

路径参数提取 #

由于标准库的路由功能有限,我们需要手动提取路径参数:

package main

import (
    "fmt"
    "net/http"
    "strconv"
    "strings"
)

func userHandler(w http.ResponseWriter, r *http.Request) {
    // 提取用户ID
    path := strings.TrimPrefix(r.URL.Path, "/users/")
    if path == "" {
        http.Error(w, "用户ID不能为空", http.StatusBadRequest)
        return
    }

    userID, err := strconv.Atoi(path)
    if err != nil {
        http.Error(w, "无效的用户ID", http.StatusBadRequest)
        return
    }

    // 根据HTTP方法处理不同操作
    switch r.Method {
    case http.MethodGet:
        fmt.Fprintf(w, "获取用户信息: ID=%d", userID)
    case http.MethodPut:
        fmt.Fprintf(w, "更新用户信息: ID=%d", userID)
    case http.MethodDelete:
        fmt.Fprintf(w, "删除用户: ID=%d", userID)
    default:
        http.Error(w, "不支持的HTTP方法", http.StatusMethodNotAllowed)
    }
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/users/", userHandler)

    http.ListenAndServe(":8080", mux)
}

更强大的路由解决方案 #

对于复杂的路由需求,可以实现一个简单的路由器:

package main

import (
    "fmt"
    "net/http"
    "regexp"
    "strconv"
)

type Route struct {
    Method  string
    Pattern *regexp.Regexp
    Handler http.HandlerFunc
}

type Router struct {
    routes []Route
}

func NewRouter() *Router {
    return &Router{}
}

func (r *Router) AddRoute(method, pattern string, handler http.HandlerFunc) {
    regex := regexp.MustCompile(pattern)
    route := Route{
        Method:  method,
        Pattern: regex,
        Handler: handler,
    }
    r.routes = append(r.routes, route)
}

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    for _, route := range r.routes {
        if route.Method == req.Method && route.Pattern.MatchString(req.URL.Path) {
            route.Handler(w, req)
            return
        }
    }
    http.NotFound(w, req)
}

// 参数提取辅助函数
func extractID(path, prefix string) (int, error) {
    idStr := path[len(prefix):]
    return strconv.Atoi(idStr)
}

func main() {
    router := NewRouter()

    // 添加路由
    router.AddRoute("GET", "^/users$", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "用户列表")
    })

    router.AddRoute("GET", "^/users/([0-9]+)$", func(w http.ResponseWriter, r *http.Request) {
        id, _ := extractID(r.URL.Path, "/users/")
        fmt.Fprintf(w, "用户详情: ID=%d", id)
    })

    router.AddRoute("POST", "^/users$", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "创建用户")
    })

    http.ListenAndServe(":8080", router)
}

请求处理 #

处理不同的 HTTP 方法 #

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
    Email string `json:"email"`
}

var users = []User{
    {ID: 1, Name: "张三", Email: "[email protected]"},
    {ID: 2, Name: "李四", Email: "[email protected]"},
}

func usersHandler(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case http.MethodGet:
        handleGetUsers(w, r)
    case http.MethodPost:
        handleCreateUser(w, r)
    default:
        w.Header().Set("Allow", "GET, POST")
        http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
    }
}

func handleGetUsers(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(users)
}

func handleCreateUser(w http.ResponseWriter, r *http.Request) {
    // 检查 Content-Type
    if r.Header.Get("Content-Type") != "application/json" {
        http.Error(w, "Content-Type 必须是 application/json", http.StatusBadRequest)
        return
    }

    // 读取请求体
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "读取请求体失败", http.StatusBadRequest)
        return
    }
    defer r.Body.Close()

    // 解析 JSON
    var newUser User
    if err := json.Unmarshal(body, &newUser); err != nil {
        http.Error(w, "JSON 格式错误", http.StatusBadRequest)
        return
    }

    // 分配 ID
    newUser.ID = len(users) + 1
    users = append(users, newUser)

    // 返回创建的用户
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(newUser)
}

func main() {
    http.HandleFunc("/users", usersHandler)

    fmt.Println("服务器启动在 http://localhost:8080")
    http.ListenAndServe(":8080", nil)
}

处理查询参数 #

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
    Age  int    `json:"age"`
}

var users = []User{
    {ID: 1, Name: "张三", Age: 25},
    {ID: 2, Name: "李四", Age: 30},
    {ID: 3, Name: "王五", Age: 35},
}

func searchUsersHandler(w http.ResponseWriter, r *http.Request) {
    // 获取查询参数
    query := r.URL.Query()

    // 分页参数
    page := 1
    if p := query.Get("page"); p != "" {
        if parsed, err := strconv.Atoi(p); err == nil && parsed > 0 {
            page = parsed
        }
    }

    limit := 10
    if l := query.Get("limit"); l != "" {
        if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
            limit = parsed
        }
    }

    // 过滤参数
    name := query.Get("name")
    minAge := 0
    if age := query.Get("min_age"); age != "" {
        if parsed, err := strconv.Atoi(age); err == nil {
            minAge = parsed
        }
    }

    // 过滤用户
    var filteredUsers []User
    for _, user := range users {
        if name != "" && user.Name != name {
            continue
        }
        if minAge > 0 && user.Age < minAge {
            continue
        }
        filteredUsers = append(filteredUsers, user)
    }

    // 分页
    start := (page - 1) * limit
    end := start + limit
    if start >= len(filteredUsers) {
        filteredUsers = []User{}
    } else if end > len(filteredUsers) {
        filteredUsers = filteredUsers[start:]
    } else {
        filteredUsers = filteredUsers[start:end]
    }

    // 构建响应
    response := map[string]interface{}{
        "data":  filteredUsers,
        "page":  page,
        "limit": limit,
        "total": len(users),
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}

func main() {
    http.HandleFunc("/users/search", searchUsersHandler)

    fmt.Println("服务器启动在 http://localhost:8080")
    fmt.Println("示例: http://localhost:8080/users/search?name=张三&min_age=20&page=1&limit=5")
    http.ListenAndServe(":8080", nil)
}

处理表单数据 #

package main

import (
    "fmt"
    "html/template"
    "net/http"
)

const formHTML = `
<!DOCTYPE html>
<html>
<head>
    <title>用户注册</title>
    <meta charset="UTF-8">
</head>
<body>
    <h2>用户注册</h2>
    <form method="POST" action="/register">
        <p>
            <label>姓名:</label>
            <input type="text" name="name" required>
        </p>
        <p>
            <label>邮箱:</label>
            <input type="email" name="email" required>
        </p>
        <p>
            <label>年龄:</label>
            <input type="number" name="age" min="1" max="120" required>
        </p>
        <p>
            <label>性别:</label>
            <input type="radio" name="gender" value="male"> 男
            <input type="radio" name="gender" value="female"> 女
        </p>
        <p>
            <label>爱好:</label>
            <input type="checkbox" name="hobbies" value="reading"> 阅读
            <input type="checkbox" name="hobbies" value="sports"> 运动
            <input type="checkbox" name="hobbies" value="music"> 音乐
        </p>
        <p>
            <input type="submit" value="注册">
        </p>
    </form>
</body>
</html>
`

func formHandler(w http.ResponseWriter, r *http.Request) {
    tmpl := template.Must(template.New("form").Parse(formHTML))
    tmpl.Execute(w, nil)
}

func registerHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "只支持 POST 方法", http.StatusMethodNotAllowed)
        return
    }

    // 解析表单数据
    if err := r.ParseForm(); err != nil {
        http.Error(w, "解析表单失败", http.StatusBadRequest)
        return
    }

    // 获取表单值
    name := r.FormValue("name")
    email := r.FormValue("email")
    age := r.FormValue("age")
    gender := r.FormValue("gender")
    hobbies := r.Form["hobbies"] // 多选值

    // 构建响应
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    fmt.Fprintf(w, "<h2>注册成功!</h2>")
    fmt.Fprintf(w, "<p>姓名: %s</p>", name)
    fmt.Fprintf(w, "<p>邮箱: %s</p>", email)
    fmt.Fprintf(w, "<p>年龄: %s</p>", age)
    fmt.Fprintf(w, "<p>性别: %s</p>", gender)
    fmt.Fprintf(w, "<p>爱好: %v</p>", hobbies)
    fmt.Fprintf(w, "<a href='/'>返回表单</a>")
}

func main() {
    http.HandleFunc("/", formHandler)
    http.HandleFunc("/register", registerHandler)

    fmt.Println("服务器启动在 http://localhost:8080")
    http.ListenAndServe(":8080", nil)
}

处理文件上传 #

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "path/filepath"
)

const uploadHTML = `
<!DOCTYPE html>
<html>
<head>
    <title>文件上传</title>
    <meta charset="UTF-8">
</head>
<body>
    <h2>文件上传</h2>
    <form method="POST" action="/upload" enctype="multipart/form-data">
        <p>
            <label>选择文件:</label>
            <input type="file" name="file" required>
        </p>
        <p>
            <label>描述:</label>
            <input type="text" name="description">
        </p>
        <p>
            <input type="submit" value="上传">
        </p>
    </form>
</body>
</html>
`

func uploadFormHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    fmt.Fprint(w, uploadHTML)
}

func uploadHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "只支持 POST 方法", http.StatusMethodNotAllowed)
        return
    }

    // 限制上传大小为 10MB
    r.ParseMultipartForm(10 << 20)

    // 获取文件
    file, header, err := r.FormFile("file")
    if err != nil {
        http.Error(w, "获取文件失败", http.StatusBadRequest)
        return
    }
    defer file.Close()

    // 获取描述
    description := r.FormValue("description")

    // 创建上传目录
    uploadDir := "./uploads"
    if err := os.MkdirAll(uploadDir, 0755); err != nil {
        http.Error(w, "创建上传目录失败", http.StatusInternalServerError)
        return
    }

    // 创建目标文件
    filename := filepath.Join(uploadDir, header.Filename)
    dst, err := os.Create(filename)
    if err != nil {
        http.Error(w, "创建文件失败", http.StatusInternalServerError)
        return
    }
    defer dst.Close()

    // 复制文件内容
    if _, err := io.Copy(dst, file); err != nil {
        http.Error(w, "保存文件失败", http.StatusInternalServerError)
        return
    }

    // 返回成功响应
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    fmt.Fprintf(w, "<h2>上传成功!</h2>")
    fmt.Fprintf(w, "<p>文件名: %s</p>", header.Filename)
    fmt.Fprintf(w, "<p>文件大小: %d 字节</p>", header.Size)
    fmt.Fprintf(w, "<p>描述: %s</p>", description)
    fmt.Fprintf(w, "<a href='/'>返回上传页面</a>")
}

func main() {
    http.HandleFunc("/", uploadFormHandler)
    http.HandleFunc("/upload", uploadHandler)

    fmt.Println("服务器启动在 http://localhost:8080")
    http.ListenAndServe(":8080", nil)
}

中间件实现 #

基础中间件框架 #

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

// 中间件类型定义
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 以捕获状态码
        wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}

        // 调用下一个处理器
        next.ServeHTTP(wrapped, r)

        // 记录日志
        duration := time.Since(start)
        log.Printf("%s %s %d %v %s",
            r.Method,
            r.URL.Path,
            wrapped.statusCode,
            duration,
            r.RemoteAddr,
        )
    })
}

// ResponseWriter 包装器
type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}

// 恢复中间件
func RecoveryMiddleware(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 recovered: %v", err)
                http.Error(w, "内部服务器错误", http.StatusInternalServerError)
            }
        }()

        next.ServeHTTP(w, r)
    })
}

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

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

        next.ServeHTTP(w, r)
    })
}

// 认证中间件
func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")

        // 简单的 token 验证
        if token != "Bearer secret-token" {
            http.Error(w, "未授权", http.StatusUnauthorized)
            return
        }

        next.ServeHTTP(w, r)
    })
}

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

func main() {
    // 业务处理器
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello, World!")
    })

    // 需要认证的处理器
    protectedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "这是受保护的资源")
    })

    // 可能出现 panic 的处理器
    panicHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        panic("模拟 panic")
    })

    // 应用中间件
    http.Handle("/", Chain(
        LoggingMiddleware,
        RecoveryMiddleware,
        CORSMiddleware,
    )(handler))

    http.Handle("/protected", Chain(
        LoggingMiddleware,
        RecoveryMiddleware,
        CORSMiddleware,
        AuthMiddleware,
    )(protectedHandler))

    http.Handle("/panic", Chain(
        LoggingMiddleware,
        RecoveryMiddleware,
    )(panicHandler))

    log.Println("服务器启动在 http://localhost:8080")
    http.ListenAndServe(":8080", nil)
}

限流中间件 #

package main

import (
    "fmt"
    "net/http"
    "sync"
    "time"
)

// 令牌桶限流器
type TokenBucket struct {
    capacity int64         // 桶容量
    tokens   int64         // 当前令牌数
    rate     int64         // 令牌生成速率(每秒)
    lastTime time.Time     // 上次更新时间
    mutex    sync.Mutex
}

func NewTokenBucket(capacity, rate int64) *TokenBucket {
    return &TokenBucket{
        capacity: capacity,
        tokens:   capacity,
        rate:     rate,
        lastTime: time.Now(),
    }
}

func (tb *TokenBucket) Allow() bool {
    tb.mutex.Lock()
    defer tb.mutex.Unlock()

    now := time.Now()
    elapsed := now.Sub(tb.lastTime).Seconds()

    // 添加新令牌
    tb.tokens += int64(elapsed * float64(tb.rate))
    if tb.tokens > tb.capacity {
        tb.tokens = tb.capacity
    }

    tb.lastTime = now

    // 检查是否有可用令牌
    if tb.tokens > 0 {
        tb.tokens--
        return true
    }

    return false
}

// 限流中间件
func RateLimitMiddleware(bucket *TokenBucket) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if !bucket.Allow() {
                http.Error(w, "请求过于频繁", http.StatusTooManyRequests)
                return
            }

            next.ServeHTTP(w, r)
        })
    }
}

func main() {
    // 创建令牌桶:容量10,每秒生成2个令牌
    bucket := NewTokenBucket(10, 2)

    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "请求成功! 时间: %s", time.Now().Format("15:04:05"))
    })

    http.Handle("/", RateLimitMiddleware(bucket)(handler))

    fmt.Println("服务器启动在 http://localhost:8080")
    fmt.Println("限流配置: 容量10,每秒生成2个令牌")
    http.ListenAndServe(":8080", nil)
}

错误处理 #

统一错误处理 #

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

// 错误响应结构
type ErrorResponse struct {
    Error   string `json:"error"`
    Code    int    `json:"code"`
    Message string `json:"message"`
    Details string `json:"details,omitempty"`
}

// 自定义错误类型
type AppError struct {
    Code    int
    Message string
    Details string
}

func (e *AppError) Error() string {
    return e.Message
}

// 错误处理函数
func handleError(w http.ResponseWriter, err error) {
    var appErr *AppError
    var statusCode int
    var message string
    var details string

    // 类型断言检查是否为自定义错误
    if e, ok := err.(*AppError); ok {
        appErr = e
        statusCode = e.Code
        message = e.Message
        details = e.Details
    } else {
        // 默认错误处理
        statusCode = http.StatusInternalServerError
        message = "内部服务器错误"
        details = err.Error()
    }

    // 构建错误响应
    errorResp := ErrorResponse{
        Error:   http.StatusText(statusCode),
        Code:    statusCode,
        Message: message,
        Details: details,
    }

    // 发送 JSON 错误响应
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(statusCode)
    json.NewEncoder(w).Encode(errorResp)
}

// 业务逻辑函数
func getUser(id string) (*User, error) {
    if id == "" {
        return nil, &AppError{
            Code:    http.StatusBadRequest,
            Message: "用户ID不能为空",
            Details: "请提供有效的用户ID",
        }
    }

    if id == "999" {
        return nil, &AppError{
            Code:    http.StatusNotFound,
            Message: "用户不存在",
            Details: fmt.Sprintf("ID为 %s 的用户未找到", id),
        }
    }

    // 模拟数据库错误
    if id == "error" {
        return nil, fmt.Errorf("数据库连接失败")
    }

    return &User{ID: 1, Name: "张三"}, nil
}

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func userHandler(w http.ResponseWriter, r *http.Request) {
    id := r.URL.Query().Get("id")

    user, err := getUser(id)
    if err != nil {
        handleError(w, err)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func main() {
    http.HandleFunc("/user", userHandler)

    fmt.Println("服务器启动在 http://localhost:8080")
    fmt.Println("测试URL:")
    fmt.Println("  http://localhost:8080/user?id=1    (正常)")
    fmt.Println("  http://localhost:8080/user?id=     (400错误)")
    fmt.Println("  http://localhost:8080/user?id=999  (404错误)")
    fmt.Println("  http://localhost:8080/user?id=error (500错误)")

    http.ListenAndServe(":8080", nil)
}

服务器配置和优化 #

生产环境服务器配置 #

package main

import (
    "context"
    "crypto/tls"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    // 创建处理器
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, Production Server!"))
    })

    // 配置 TLS
    tlsConfig := &tls.Config{
        MinVersion:               tls.VersionTLS12,
        CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
        PreferServerCipherSuites: true,
        CipherSuites: []uint16{
            tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
            tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
        },
    }

    // 配置服务器
    server := &http.Server{
        Addr:              ":8443",
        Handler:           mux,
        TLSConfig:         tlsConfig,
        ReadTimeout:       15 * time.Second,
        ReadHeaderTimeout: 5 * time.Second,
        WriteTimeout:      15 * time.Second,
        IdleTimeout:       60 * time.Second,
        MaxHeaderBytes:    1 << 20, // 1MB
        ErrorLog:          log.New(os.Stderr, "SERVER: ", log.LstdFlags),
    }

    // 启动服务器
    go func() {
        log.Println("HTTPS 服务器启动在 :8443")
        if err := server.ListenAndServeTLS("server.crt", "server.key"); err != nil && err != http.ErrServerClosed {
            log.Fatalf("服务器启动失败: %v", err)
        }
    }()

    // 优雅关闭
    gracefulShutdown(server)
}

func gracefulShutdown(server *http.Server) {
    // 创建信号通道
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    // 等待信号
    <-quit
    log.Println("正在关闭服务器...")

    // 创建超时上下文
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    // 关闭服务器
    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("服务器强制关闭: %v", err)
    }

    log.Println("服务器已关闭")
}

性能监控 #

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "runtime"
    "sync/atomic"
    "time"
)

// 性能指标
type Metrics struct {
    RequestCount    int64     `json:"request_count"`
    ErrorCount      int64     `json:"error_count"`
    AverageResponse float64   `json:"average_response_ms"`
    Uptime          string    `json:"uptime"`
    MemoryUsage     string    `json:"memory_usage"`
    Goroutines      int       `json:"goroutines"`
}

var (
    requestCount    int64
    errorCount      int64
    totalResponse   int64
    startTime       = time.Now()
)

// 监控中间件
func MetricsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()

        // 包装 ResponseWriter
        wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}

        // 处理请求
        next.ServeHTTP(wrapped, r)

        // 更新指标
        duration := time.Since(start)
        atomic.AddInt64(&requestCount, 1)
        atomic.AddInt64(&totalResponse, duration.Nanoseconds()/1000000) // 转换为毫秒

        if wrapped.statusCode >= 400 {
            atomic.AddInt64(&errorCount, 1)
        }
    })
}

func metricsHandler(w http.ResponseWriter, r *http.Request) {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)

    reqCount := atomic.LoadInt64(&requestCount)
    totalResp := atomic.LoadInt64(&totalResponse)

    var avgResponse float64
    if reqCount > 0 {
        avgResponse = float64(totalResp) / float64(reqCount)
    }

    metrics := Metrics{
        RequestCount:    reqCount,
        ErrorCount:      atomic.LoadInt64(&errorCount),
        AverageResponse: avgResponse,
        Uptime:          time.Since(startTime).String(),
        MemoryUsage:     fmt.Sprintf("%.2f MB", float64(m.Alloc)/1024/1024),
        Goroutines:      runtime.NumGoroutine(),
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(metrics)
}

func main() {
    mux := http.NewServeMux()

    // 业务处理器
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // 模拟处理时间
        time.Sleep(time.Duration(10+r.URL.Query().Get("delay")) * time.Millisecond)
        fmt.Fprint(w, "Hello, World!")
    })

    // 错误处理器
    mux.HandleFunc("/error", func(w http.ResponseWriter, r *http.Request) {
        http.Error(w, "模拟错误", http.StatusInternalServerError)
    })

    // 监控端点
    mux.HandleFunc("/metrics", metricsHandler)

    // 应用监控中间件
    handler := MetricsMiddleware(mux)

    fmt.Println("服务器启动在 http://localhost:8080")
    fmt.Println("监控端点: http://localhost:8080/metrics")
    http.ListenAndServe(":8080", handler)
}

小结 #

本节深入介绍了 Go HTTP 服务端开发的核心技术:

  1. 基础服务器搭建:从简单服务器到自定义 ServeMux 的使用
  2. 路由处理:路由模式匹配、参数提取和自定义路由器实现
  3. 请求处理:处理不同 HTTP 方法、查询参数、表单数据和文件上传
  4. 中间件实现:日志、认证、CORS、限流等中间件的设计和实现
  5. 错误处理:统一错误处理机制和自定义错误类型
  6. 服务器配置:生产环境配置、TLS 设置和优雅关闭
  7. 性能监控:请求指标收集和监控端点实现

这些技术为构建高性能、可维护的 HTTP 服务器提供了坚实的基础。在下一节中,我们将学习 HTTP 客户端开发,了解如何发送 HTTP 请求和处理响应。