跳到主要内容

Chi

Chi 是一个轻量级、符合 HTTP 标准的 Go HTTP 路由器,以 net/http 为基础构建,提供了强大的路由功能和中间件支持。它设计简洁、性能优异,是构建 RESTful API 和微服务的优秀选择。

简介

Chi 特性

Chi 核心特性:

  • 轻量级: 核心小巧,零外部依赖
  • 标准兼容: 完全兼容 net/http
  • 高性能: 极快的路由匹配速度
  • 中间件: 灵活的中间件系统
  • URL 模式: 支持通配符和正则表达式
  • 上下文: 请求上下文管理
  • 模块化: 可组合的子路由器
  • 优雅: 代码优雅,易于理解

适用场景:

  • RESTful API 开发
  • 微服务架构
  • 需要精细控制的 Web 服务
  • 标准 net/http 项目的路由增强
  • 轻量级 Web 应用

安装 Chi

# 初始化 Go module
go mod init myproject

# 安装 Chi
go get -u github.com/go-chi/chi/v5

第一个 Chi 应用

package main

import (
"net/http"
"github.com/go-chi/chi/v5"
)

func main() {
r := chi.NewRouter()

r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Chi!"))
})

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

核心概念

1. 路由 (Routing)

基本路由

package main

import (
"net/http"
"github.com/go-chi/chi/v5"
)

func main() {
r := chi.NewRouter()

// GET 请求
r.Get("/users", getUsers)

// POST 请求
r.Post("/users", createUser)

// PUT 请求
r.Put("/users/{id}", updateUser)

// DELETE 请求
r.Delete("/users/{id}", deleteUser)

// 所有 HTTP 方法
r.MethodFunc(http.MethodGet, "/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})

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

func getUsers(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Get all users"))
}

func createUser(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Create user"))
}

func updateUser(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Update user"))
}

func deleteUser(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Delete user"))
}

路径参数

// 路径参数
r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
w.Write([]byte("User ID: " + id))
})

// 多个路径参数
r.Get("/users/{id}/posts/{postId}", func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
postId := chi.URLParam(r, "postId")
w.Write([]byte("User: " + id + ", Post: " + postId))
})

// 通配符参数
r.Get("/files/*", func(w http.ResponseWriter, r *http.Request) {
path := chi.URLParam(r, "*")
w.Write([]byte("File path: " + path))
})

// 多段通配符
r.Get("/docs{/.*}", func(w http.ResponseWriter, r *http.Request) {
path := chi.URLParam(r, "*")
w.Write([]byte("Docs path: " + path))
})

查询参数

r.Get("/search", func(w http.ResponseWriter, r *http.Request) {
keyword := r.URL.Query().Get("keyword")
page := r.URL.Query().Get("page")
limit := r.URL.Query().Get("limit")

if limit == "" {
limit = "10"
}

w.Write([]byte("Search: " + keyword + ", Page: " + page + ", Limit: " + limit))
})

2. 路由分组

func main() {
r := chi.NewRouter()

// API v1 路由组
r.Route("/api/v1", func(r chi.Router) {
r.Get("/users", getUsers)
r.Get("/posts", getPosts)
})

// API v2 路由组
r.Route("/api/v2", func(r chi.Router) {
r.Get("/users", getUsersV2)
r.Get("/posts", getPostsV2)
})

// 带中间件的路由组
r.Group(func(r chi.Router) {
r.Use(authMiddleware)
r.Get("/protected", protectedHandler)
})

// 路由挂载
r.Mount("/api", apiRouter())

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

func apiRouter() chi.Router {
r := chi.NewRouter()
r.Get("/items", getItems)
r.Get("/items/{id}", getItem)
return r
}

3. 中间件 (Middleware)

基本中间件

import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)

func main() {
r := chi.NewRouter()

// 请求 ID 中间件
r.Use(middleware.RequestID)

// 日志中间件
r.Use(middleware.Logger)

// 错误恢复中间件
r.Use(middleware.Recoverer)

// URL 清理中间件
r.Use(middleware.StripSlashes)

// 压缩中间件
r.Use(middleware.Compress(5))

// 内容类型中间件
r.Use(middleware.AllowContentType("application/json"))

// 心跳检测
r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})

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

自定义中间件

// 自定义中间件函数
func customMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 请求处理前
println("Before request")

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

// 请求处理后
println("After request")
})
}

// 使用自定义中间件
r.Use(customMiddleware)

// 带配置的中间件
func authMiddleware(config string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")

if token != config {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}

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

// 使用带配置的中间件
r.Use(authMiddleware("secret-token"))

中间件链

// 应用多个中间件
r.With(middleware.Logger, middleware.Recoverer).Get("/", handler)

// 路由组中间件
r.Group(func(r chi.Router) {
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Get("/", handler)
})

4. 上下文 (Context)

import "context"

// 设置上下文值
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "user", "Alice")
next.ServeHTTP(w, r.WithContext(ctx))
})
})

// 获取上下文值
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value("user").(string)
w.Write([]byte("User: " + user))
})

5. 请求处理

JSON 处理

import "encoding/json"

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

// 解析 JSON
r.Post("/users", func(w http.ResponseWriter, r *http.Request) {
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// 处理用户创建逻辑
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
})

// 返回 JSON
r.Get("/users", func(w http.ResponseWriter, r *http.Request) {
users := []User{
{Name: "Alice", Email: "alice@example.com"},
{Name: "Bob", Email: "bob@example.com"},
}

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

表单处理

r.Post("/form", func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

name := r.FormValue("name")
email := r.FormValue("email")

w.Write([]byte("Name: " + name + ", Email: " + email))
})

6. 文件服务

// 静态文件服务
import "embed"

//go:embed static/*
var staticFS embed.FS

func main() {
r := chi.NewRouter()

// 文件服务器
FileServer(r, "/static", http.FS(staticFS))

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

// FileServer 函数
func FileServer(r chi.Router, path string, root http.FileSystem) {
if strings.ContainsAny(path, "{}*") {
panic("FileServer does not permit URL parameters.")
}

fs := http.FileServer(root)

r.Get(path+"*", http.StripPrefix(path, fs).ServeHTTP)
}

高级功能

1. 路由模式

// 通配符
r.Get("/files/{filepath:*}", func(w http.ResponseWriter, r *http.Request) {
filepath := chi.URLParam(r, "filepath")
w.Write([]byte("File: " + filepath))
})

// 正则表达式
r.Get("/users/{id:[0-9]+}", func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
w.Write([]byte("User ID: " + id))
})

// 多段通配符
r.Get("/docs{/.*}", func(w http.ResponseWriter, r *http.Request) {
path := chi.URLParam(r, "*")
w.Write([]byte("Docs: " + path))
})

2. CORS 支持

import "github.com/go-chi/cors"

r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"http://localhost:3000"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300,
}))

3. 限流

import "github.com/go-chi/chi/v5/middleware"

// 限流中间件
r.Use(middleware.Throttle(1000, time.Second))

// 超时
r.Use(middleware.Timeout(60 * time.Second))

4. 请求 ID

import "github.com/go-chi/chi/v5/middleware"

r.Use(middleware.RequestID)

r.Get("/", func(w http.ResponseWriter, r *http.Request) {
requestID := middleware.GetReqID(r.Context())
w.Write([]byte("Request ID: " + requestID))
})

实战示例

RESTful API

package main

import (
"encoding/json"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
)

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

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

func main() {
r := chi.NewRouter()

r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.AllowContentType("application/json"))

r.Route("/api", func(r chi.Router) {
r.Get("/users", getUsers)
r.Get("/users/{id}", getUser)
r.Post("/users", createUser)
r.Put("/users/{id}", updateUser)
r.Delete("/users/{id}", deleteUser)
})

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

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

func getUser(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(chi.URLParam(r, "id"))

for _, user := range users {
if user.ID == id {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
return
}
}

http.Error(w, "User not found", http.StatusNotFound)
}

func createUser(w http.ResponseWriter, r *http.Request) {
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

user.ID = seq
seq++
users = append(users, user)

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

func updateUser(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(chi.URLParam(r, "id"))

var updateUser User
if err := json.NewDecoder(r.Body).Decode(&updateUser); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

for i, user := range users {
if user.ID == id {
users[i].Name = updateUser.Name
users[i].Email = updateUser.Email

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users[i])
return
}
}

http.Error(w, "User not found", http.StatusNotFound)
}

func deleteUser(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(chi.URLParam(r, "id"))

for i, user := range users {
if user.ID == id {
users = append(users[:i], users[i+1:]...)
w.WriteHeader(http.StatusNoContent)
return
}
}

http.Error(w, "User not found", http.StatusNotFound)
}

最佳实践

1. 项目结构

my-chi-app/
├── main.go
├── handlers/
│ └── user_handler.go
├── models/
│ └── user.go
├── middlewares/
│ └── auth.go
├── routes/
│ └── routes.go
└── config/
└── config.go

2. 路由组织

func setupRoutes() chi.Router {
r := chi.NewRouter()

// 中间件
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)

// 路由
r.Mount("/api", apiRoutes())
r.Mount("/auth", authRoutes())

return r
}

func apiRoutes() chi.Router {
r := chi.NewRouter()
r.Use(authMiddleware)

r.Get("/users", getUsers)
r.Get("/users/{id}", getUser)

return r
}

3. 错误处理

type ErrorResponse struct {
Error string `json:"error"`
Code int `json:"code"`
}

func respondWithError(w http.ResponseWriter, code int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(ErrorResponse{
Error: message,
Code: code,
})
}

func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(payload)
}

性能优化

1. 路由优化

// 使用精确路由优先
r.Get("/users", getUsers)
r.Get("/users/{id}", getUser) // 更具体的路由放后面

2. 连接池

var httpClient = &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
},
}

总结

Chi 是一个优雅、轻量级的 HTTP 路由器,完全兼容 net/http 标准库。它提供了强大的路由功能和灵活的中间件系统,使得开发者能够轻松构建高性能的 Web 应用和 API 服务。Chi 的设计哲学是简单、模块化、可组合,非常适合追求代码优雅性和标准兼容性的项目。

参考资料