第 14 章:Web 开发实战

jerry北京市2026年5月9日Go 19 次阅读 约 21 分钟
第 14 章:Web 开发实战

使用标准库 net/http 和 Gin 框架构建 RESTful API,涵盖路由、中间件、JSON 处理等核心技能。


14.1 net/http 基础

Go 标准库自带强大的 HTTP 服务器:

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Query().Get("name"))
}

func main() {
    http.HandleFunc("/hello", helloHandler)
    fmt.Println("服务器启动在 :8080")
    http.ListenAndServe(":8080", nil)
}

请求处理

func handler(w http.ResponseWriter, r *http.Request) {
    // 请求方法
    fmt.Println(r.Method)  // GET, POST, PUT, DELETE...

    // URL 参数
    name := r.URL.Query().Get("name")

    // 请求头
    contentType := r.Header.Get("Content-Type")

    // 请求体
    body, _ := io.ReadAll(r.Body)
    defer r.Body.Close()

    // 设置响应头
    w.Header().Set("Content-Type", "application/json")

    // 设置状态码
    w.WriteHeader(http.StatusOK)

    // 写入响应体
    w.Write([]byte(`{"message": "ok"}`))
}

JSON 请求和响应

type CreateUserRequest struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

type Response struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

func createUserHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    var req CreateUserRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        w.WriteHeader(http.StatusBadRequest)
        json.NewEncoder(w).Encode(Response{
            Code:    400,
            Message: "无效的请求体",
        })
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(Response{
        Code:    200,
        Message: "创建成功",
        Data:    req,
    })
}

14.2 自定义 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, "用户 ID: %s", id)
})

mux.HandleFunc("POST /users", createUserHandler)
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("OK"))
})

http.ListenAndServe(":8080", mux)

14.3 中间件

// 中间件:接收 handler 返回 handler
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        fmt.Printf("%s %s %v\n", r.Method, r.URL.Path, time.Since(start))
    })
}

func authMiddleware(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, "未授权", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// 链式调用
handler := loggingMiddleware(authMiddleware(mux))
http.ListenAndServe(":8080", handler)

14.4 使用 Gin 框架

Gin 是 Go 最流行的 Web 框架,性能优秀,API 简洁:

go get github.com/gin-gonic/gin

基本用法

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()  // 包含 Logger 和 Recovery 中间件

    // 路由
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })

    r.Run(":8080")
}

RESTful API 实战

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

var users = []User{
    {ID: 1, Name: "Alice", Email: "alice@example.com"},
    {ID: 2, Name: "Bob", Email: "bob@example.com"},
}

func main() {
    r := gin.Default()

    v1 := r.Group("/api/v1")
    {
        v1.GET("/users", getUsers)
        v1.GET("/users/:id", getUser)
        v1.POST("/users", createUser)
        v1.PUT("/users/:id", updateUser)
        v1.DELETE("/users/:id", deleteUser)
    }

    r.Run(":8080")
}

func getUsers(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "code": 200,
        "data": users,
    })
}

func getUser(c *gin.Context) {
    id, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
        return
    }

    for _, u := range users {
        if u.ID == id {
            c.JSON(http.StatusOK, gin.H{"data": u})
            return
        }
    }
    c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
}

func createUser(c *gin.Context) {
    var user User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user.ID = len(users) + 1
    users = append(users, user)
    c.JSON(http.StatusCreated, gin.H{"data": user})
}

func updateUser(c *gin.Context) {
    id, _ := strconv.Atoi(c.Param("id"))
    var input User
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    for i, u := range users {
        if u.ID == id {
            users[i].Name = input.Name
            users[i].Email = input.Email
            c.JSON(http.StatusOK, gin.H{"data": users[i]})
            return
        }
    }
    c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
}

func deleteUser(c *gin.Context) {
    id, _ := strconv.Atoi(c.Param("id"))
    for i, u := range users {
        if u.ID == id {
            users = append(users[:i], users[i+1:]...)
            c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
            return
        }
    }
    c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
}

Gin 中间件

// 自定义中间件
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "未授权"})
            c.Abort()
            return
        }
        // 验证 token...
        c.Set("userID", 123)  // 设置上下文值
        c.Next()
    }
}

// 使用中间件
authorized := r.Group("/api")
authorized.Use(AuthMiddleware())
{
    authorized.GET("/profile", func(c *gin.Context) {
        userID := c.GetInt("userID")
        c.JSON(200, gin.H{"userID": userID})
    })
}

请求参数绑定

// Query 参数
// GET /search?q=golang&page=1
func search(c *gin.Context) {
    q := c.Query("q")
    page := c.DefaultQuery("page", "1")
    c.JSON(200, gin.H{"q": q, "page": page})
}

// 路径参数
// GET /users/123
func getUser(c *gin.Context) {
    id := c.Param("id")
    c.JSON(200, gin.H{"id": id})
}

// 结构体绑定
type SearchQuery struct {
    Q    string `form:"q" binding:"required"`
    Page int    `form:"page" binding:"min=1"`
}

func search(c *gin.Context) {
    var query SearchQuery
    if err := c.ShouldBindQuery(&query); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    // 使用 query.Q 和 query.Page
}

14.5 优雅关闭

func main() {
    r := gin.Default()
    // 注册路由...

    srv := &http.Server{
        Addr:    ":8080",
        Handler: r,
    }

    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("启动失败: %s\n", err)
        }
    }()

    // 等待中断信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("正在关闭服务器...")

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

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("服务器强制关闭:", err)
    }
    log.Println("服务器已退出")
}

14.6 面试要点

  1. net/http 的 Handler 接口?

    • ServeHTTP(ResponseWriter, *Request) 方法
    • HandlerFunc 是实现了 Handler 接口的函数类型
  2. 中间件的实现原理?

    • 函数接收 Handler 返回 Handler,形成调用链
    • 洋葱模型:请求从外到内,响应从内到外
  3. 如何实现优雅关闭?

    • 监听系统信号
    • 调用 Server.Shutdown() 等待现有请求完成
  4. Gin 和标准库的区别?

    • Gin 基于 httprouter,路由性能更好
    • 提供参数绑定、验证、中间件等便捷功能
    • 标准库 Go 1.22+ 也支持了方法路由和路径参数

练习

  1. 使用标准库实现一个简单的 TODO API(CRUD)
  2. 使用 Gin 实现带认证中间件的 RESTful API
  3. 实现一个请求限流中间件
  4. 实现优雅关闭,验证关闭时正在处理的请求能正常完成

← 上一章:测试与性能优化 | 下一章:Go 面试高频题精讲 →

评论

登录 后发表评论

暂无评论