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 的设计哲学是简单、模块化、可组合,非常适合追求代码优雅性和标准兼容性的项目。