golang轻量快速基于net/context的HTTP路由插件chi的使用
Golang轻量快速基于net/context的HTTP路由插件chi的使用
chi是一个轻量级、符合Go语言习惯且可组合的路由器,用于构建Go HTTP服务。它特别适合帮助您编写大型REST API服务,随着项目增长和变化而保持可维护性。
特性
- 轻量级 - 核心路由器代码约1000行
- 快速 - 性能优异
- 100%兼容net/http - 可以使用任何与net/http兼容的中间件
- 模块化/可组合API设计 - 支持中间件、路由组和子路由器挂载
- 上下文控制 - 基于新的context包,提供值链、取消和超时
- 健壮 - 已在Pressly、Cloudflare、Heroku、99Designs等公司生产环境使用
安装
go get -u github.com/go-chi/chi/v5
基本示例
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome"))
})
http.ListenAndServe(":3000", r)
}
REST API完整示例
import (
"context"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
r := chi.NewRouter()
// 基础中间件栈
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hi"))
})
// 文章资源的路由
r.Route("/articles", func(r chi.Router) {
r.With(paginate).Get("/", listArticles) // GET /articles
r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017
r.Post("/", createArticle) // POST /articles
r.Get("/search", searchArticles) // GET /articles/search
// 正则表达式URL参数
r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug) // GET /articles/home-is-toronto
// 子路由
r.Route("/{articleID}", func(r chi.Router) {
r.Use(ArticleCtx)
r.Get("/", getArticle) // GET /articles/123
r.Put("/", updateArticle) // PUT /articles/123
r.Delete("/", deleteArticle) // DELETE /articles/123
})
})
// 挂载管理员子路由
r.Mount("/admin", adminRouter())
http.ListenAndServe(":3333", r)
}
func ArticleCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
articleID := chi.URLParam(r, "articleID")
article, err := dbGetArticle(articleID)
if err != nil {
http.Error(w, http.StatusText(404), 404)
return
}
ctx := context.WithValue(r.Context(), "article", article)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func getArticle(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
article, ok := ctx.Value("article").(*Article)
if !ok {
http.Error(w, http.StatusText(422), 422)
return
}
w.Write([]byte(fmt.Sprintf("title:%s", article.Title)))
}
// 管理员路由
func adminRouter() http.Handler {
r := chi.NewRouter()
r.Use(AdminOnly)
r.Get("/", adminIndex)
r.Get("/accounts", adminListAccounts)
return r
}
func AdminOnly(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
perm, ok := ctx.Value("acl.permission").(YourPermissionType)
if !ok || !perm.IsAdmin() {
http.Error(w, http.StatusText(403), 403)
return
}
next.ServeHTTP(w, r)
})
}
中间件示例
// HTTP中间件设置请求上下文的值
func MyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 从请求上下文中创建新上下文,并设置"user"键的值为"123"
ctx := context.WithValue(r.Context(), "user", "123")
// 调用链中的下一个处理器,传递响应写入器和带有新上下文值的请求对象
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// HTTP处理器访问请求上下文中的数据
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
// 从请求上下文中读取"user"键的值
user := r.Context().Value("user").(string)
// 响应客户端
w.Write([]byte(fmt.Sprintf("hi %s", user)))
}
// HTTP处理器访问URL路由参数
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
// 从匹配的路由模式中获取URL参数"userID"
userID := chi.URLParam(r, "userID")
// 从请求上下文中获取"key"
ctx := r.Context()
key := ctx.Value("key").(string)
// 响应客户端
w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key)))
}
核心中间件
chi提供了一个可选的中件间包,包含一系列标准的net/http中间件。
中间件 | 描述 |
---|---|
AllowContentEncoding | 强制请求Content-Encoding头白名单 |
BasicAuth | 基本HTTP认证 |
Compress | 对接受压缩响应的客户端进行Gzip压缩 |
Logger | 记录每个请求的开始和结束以及处理时间 |
RealIP | 设置http.Request的RemoteAddr为X-Real-IP或X-Forwarded-For |
Recoverer | 优雅地吸收panic并打印堆栈跟踪 |
RequestID | 向每个请求的上下文注入请求ID |
Timeout | 当达到超时截止时间时向请求上下文发出信号 |
性能
chi基于一种Patricia Radix trie实现,性能优异。在Go 1.15.5 Linux AMD 3950x上的基准测试结果:
BenchmarkChi_Param 3075895 384 ns/op 400 B/op 2 allocs/op
BenchmarkChi_Param5 2116603 566 ns/op 400 B/op 2 allocs/op
BenchmarkChi_Param20 964117 1227 ns/op 400 B/op 2 allocs/op
BenchmarkChi_GithubStatic 3045488 395 ns/op 400 B/op 2 allocs/op
BenchmarkChi_GithubParam 2204115 540 ns/op 400 B/op 2 allocs/op
chi是一个专注于简单优雅设计的路由器,特别适合构建大型REST API服务。它的小巧核心和模块化设计使其成为Go HTTP服务开发的理想选择。
更多关于golang轻量快速基于net/context的HTTP路由插件chi的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于golang轻量快速基于net/context的HTTP路由插件chi的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang轻量快速HTTP路由插件chi使用指南
chi是一个轻量级、高性能的Go语言HTTP路由器,基于标准库net/http
构建,支持context
中间件链。它非常适合构建RESTful API服务。
主要特点
- 极轻量级(约1000行代码)
- 100%兼容
net/http
- 支持中间件链
- 基于
context
的路由参数 - 无外部依赖
- 优秀的性能
基本使用
安装
go get -u github.com/go-chi/chi/v5
简单示例
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
r := chi.NewRouter()
// 使用内置中间件
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
// 基本路由
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World!"))
})
// 启动服务器
http.ListenAndServe(":3000", r)
}
路由功能
路径参数
r.Get("/users/{userID}", func(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "userID")
w.Write([]byte(fmt.Sprintf("User ID: %s", userID)))
})
正则匹配
r.Get("/articles/{id:[0-9]+}", func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
w.Write([]byte(fmt.Sprintf("Article ID: %s", id)))
})
路由分组
r.Route("/users", func(r chi.Router) {
r.Get("/", listUsers) // GET /users
r.Post("/", createUser) // POST /users
r.Route("/{userID}", func(r chi.Router) {
r.Get("/", getUser) // GET /users/123
r.Put("/", updateUser) // PUT /users/123
r.Delete("/", deleteUser) // DELETE /users/123
})
})
中间件使用
内置中间件
chi提供了一些常用中间件:
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))
自定义中间件
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "valid-token" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// 使用中间件
r.With(AuthMiddleware).Get("/admin", adminHandler)
文件服务
// 静态文件服务
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
// 单文件服务
r.Get("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./static/favicon.ico")
})
RESTful API示例
package main
import (
"encoding/json"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
type User struct {
ID string `json:"id"`
Name string `json:"name"`
}
var users = map[string]User{
"1": {ID: "1", Name: "Alice"},
"2": {ID: "2", Name: "Bob"},
}
func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Route("/api/users", func(r chi.Router) {
r.Get("/", listUsers)
r.Post("/", createUser)
r.Route("/{userID}", func(r chi.Router) {
r.Get("/", getUser)
r.Put("/", updateUser)
r.Delete("/", deleteUser)
})
})
http.ListenAndServe(":3000", r)
}
func listUsers(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(users)
}
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
}
users[user.ID] = user
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}
func getUser(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "userID")
user, ok := users[userID]
if !ok {
http.Error(w, "User not found", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(user)
}
func updateUser(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "userID")
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
users[userID] = user
json.NewEncoder(w).Encode(user)
}
func deleteUser(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "userID")
delete(users, userID)
w.WriteHeader(http.StatusNoContent)
}
性能优化技巧
- 避免在热路径中使用正则表达式
- 合理使用中间件,避免不必要的处理
- 对于静态路由优先使用
Get()
/Post()
等直接方法 - 使用
r.Mount()
挂载子路由器
chi是一个简单但功能强大的路由器,特别适合构建高性能的API服务。它的设计哲学是保持简单和可组合性,同时提供足够的灵活性来处理复杂路由需求。