2. Go Web服务基础
构建契约压测闭环的第一步,是拥有一个可运行、可测试的Web服务。本章将带领你使用Go标准库 net/http 与流行框架 gin 分别搭建RESTful API服务,并深入理解路由与中间件这两个核心概念。
2.1 标准库 net/http:零依赖的RESTful服务
Go标准库 net/http 提供了构建HTTP服务所需的一切基础组件,无需引入任何第三方依赖即可快速启动一个API服务。
2.1.1 基础服务搭建
以下代码使用 net/http 创建了一个监听 :8080 端口的服务,并注册了三个RESTful风格的路由:
package main
import (
"encoding/json"
"log"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
var users = []User{{ID: 1, Name: "Alice"}}
func getUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func createUser(w http.ResponseWriter, r *http.Request) {
var newUser User
if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
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("/api/users", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
getUsers(w, r)
case http.MethodPost:
createUser(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
关键点:标准库的路由器
http.DefaultServeMux 仅支持路径匹配,不支持HTTP方法匹配。开发者需要在处理函数内部通过 r.Method 手动分发,这是标准库与gin等框架的主要差异之一。
2.1.2 标准库路由的局限性
| 特性 | net/http 默认路由 | 说明 |
|---|---|---|
| HTTP方法匹配 | 不支持 | 需手动 switch r.Method |
| 路径参数 | 不支持 | 无法直接获取 /api/users/:id |
| 路由分组 | 不支持 | 所有路由平铺注册 |
| 中间件链 | 需手动包装 | 通过函数嵌套实现 |
2.2 Gin框架:高性能的RESTful路由引擎
Gin是目前Go社区最流行的Web框架之一,其核心优势在于高性能的路由树(基于radix tree)和丰富的中间件生态。
2.2.1 使用Gin重构API服务
以下代码使用Gin实现了与上一节完全相同的功能,但代码更加简洁、可读性更强:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
var users = []User{{ID: 1, Name: "Alice"}}
func main() {
r := gin.Default()
r.GET("/api/users", func(c *gin.Context) {
c.JSON(http.StatusOK, users)
})
r.POST("/api/users", func(c *gin.Context) {
var newUser User
if err := c.ShouldBindJSON(&newUser); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
newUser.ID = len(users) + 1
users = append(users, newUser)
c.JSON(http.StatusCreated, newUser)
})
r.Run(":8080")
}
对比优势:Gin通过
r.GET()、r.POST() 等方法直接绑定HTTP方法和路径,无需手动判断 r.Method;同时内置 ShouldBindJSON 简化了请求体解析,c.JSON 自动设置Content-Type并序列化响应。
2.3 路由与中间件核心机制
路由和中间件是Web框架的两大基石。路由决定了请求如何被分发到对应的处理函数,中间件则提供了在请求处理前后执行通用逻辑的能力。
2.3.1 Gin路由详解
Gin的路由支持路径参数、查询参数和路由分组,这些特性在构建RESTful API时非常实用:
// 路径参数
r.GET("/api/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"user_id": id})
})
// 查询参数
r.GET("/api/search", func(c *gin.Context) {
keyword := c.Query("q")
page := c.DefaultQuery("page", "1")
c.JSON(http.StatusOK, gin.H{"keyword": keyword, "page": page})
})
// 路由分组
v1 := r.Group("/api/v1")
{
v1.GET("/users", listUsersV1)
v1.POST("/users", createUserV1)
}
2.3.2 中间件:请求处理管道
中间件是Gin框架的核心设计模式之一。每个中间件都是一个 gin.HandlerFunc,通过 c.Next() 控制请求在管道中的流转。
以下是一个日志记录中间件的实现:
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 请求前
start := time.Now()
path := c.Request.URL.Path
// 继续执行后续处理
c.Next()
// 请求后
latency := time.Since(start)
log.Printf("[%s] %s %d %v",
c.Request.Method,
path,
c.Writer.Status(),
latency,
)
}
}
// 使用中间件
r := gin.New()
r.Use(LoggerMiddleware())
r.Use(gin.Recovery())
注意:使用
gin.Default() 会自动附加 Logger 和 Recovery 两个内置中间件。如果使用 gin.New() 创建引擎,则需要手动添加中间件。在契约压测场景中,建议使用 gin.New() 以精确控制中间件栈,避免日志输出影响压测性能数据。
2.4 中间件执行顺序与架构
理解中间件的执行顺序对于调试和性能优化至关重要。以下SVG图展示了Gin框架中多个中间件与最终处理函数的执行流程:
2.5 中间件在契约压测中的价值
在契约压测闭环中,中间件承担着三个关键角色:
- 请求审计:记录每个请求的路径、方法、状态码和耗时,为契约验证提供原始数据。
- 契约校验:在请求进入业务逻辑之前,对请求体进行结构校验,确保符合契约规范。
- 性能隔离:在压测场景中,通过中间件注入延迟或模拟故障,验证服务在异常契约下的行为。
实践建议:在构建契约压测闭环时,建议将契约校验逻辑封装为独立中间件,与业务中间件分离。这样在压测阶段可以灵活启用/禁用契约校验,避免校验逻辑成为性能瓶颈。
2.6 本章小结
| 知识点 | 核心要点 | 契约压测关联 |
|---|---|---|
| net/http 基础 | 零依赖、手动方法分发、无路径参数 | 适合轻量级契约验证服务 |
| Gin框架 | 高性能路由树、路径参数、路由分组 | 推荐用于生产级契约压测服务 |
| 中间件机制 | c.Next() 控制流、前置/后置逻辑 | 契约校验、日志审计、性能隔离 |
| 路由分组 | 版本管理、模块化组织 | 按契约版本分组管理API |