go-chi/chi
GitHub: go-chi/chi
chi 是一个轻量、符合 Go 语言习惯且完全兼容 net/http 的路由库,专注于帮助开发者构建可维护的大型 REST API 服务。
Stars: 21826 | Forks: 1083
#
[![GoDoc Widget]][GoDoc]
`chi` 是一个轻量级、符合语言习惯且可组合的路由器,用于构建 Go HTTP 服务。它非常擅长帮助您编写大型 REST API 服务,随着项目的发展和变化,这些服务依然易于维护。`chi` 基于 Go 1.7 中引入的新 `context` 包构建,用于处理 handler 链中的信号、取消和请求范围内的值。
该项目的重点是为编写 REST API 服务器寻求一种优雅且舒适的设计,它是在开发为我们的公共 API 服务提供支持的 Pressly API 服务期间编写的,而该公共 API 服务又为我们所有的客户端应用程序提供支持。
chi 设计的关键考量是:项目结构、可维护性、标准 http handler(仅限 stdlib)、开发者生产力,以及将大型系统解构为许多小部分。核心路由器 `github.com/go-chi/chi` 非常小(少于 1000 行代码),但我们也包含了一些有用/可选的子包:[middleware](/middleware)、[render](https://github.com/go-chi/render) 和 [docgen](https://github.com/go-chi/docgen)。希望您也会喜欢它!
## 安装
```
go get -u github.com/go-chi/chi/v5
```
## 特性
* **轻量级** - chi 路由器代码量约 1000 行
* **快速** - 是的,请参阅 [基准测试](#benchmarks)
* **100% 兼容 net/http** - 可以使用生态系统中任何同样兼容 `net/http` 的 http 或 middleware 包
* **专为模块化/可组合 API 设计** - 中间件、内联中间件、路由组和子路由器挂载
* **Context 控制** - 基于新的 `context` 包构建,提供值链、取消和超时功能
* **健壮** - 已在 Pressly、Cloudflare、Heroku、99Designs 等公司投入生产使用(参见 [讨论](https://github.com/go-chi/chi/issues/91))
* **文档生成** - `docgen` 自动从源代码生成路由文档,输出为 JSON 或 Markdown
* **Go.mod 支持** - 从 v5 版本开始支持 go.mod(参见 [更新日志](https://github.com/go-chi/chi/blob/master/CHANGELOG.md))
* **无外部依赖** - 纯 Go 标准库 + net/http
## 示例
请参阅 [_examples/](https://github.com/go-chi/chi/blob/master/_examples/) 查看各种示例。
**如此简单:**
```
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 预览:**
这是使用 chi 进行路由的一个小预览。也可以看看生成的路由文档,格式为 JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) 和 Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md))。
我强烈建议阅读上面列出的 [示例](https://github.com/go-chi/chi/blob/master/_examples/) 的源代码,它们将向您展示 chi 的所有功能,并作为一种很好的文档形式。
```
import (
//...
"context"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
r := chi.NewRouter()
// A good base middleware stack
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
// Set a timeout value on the request context (ctx), that will signal
// through ctx.Done() that the request has timed out and further
// processing should be stopped.
r.Use(middleware.Timeout(60 * time.Second))
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hi"))
})
// RESTy routes for "articles" resource
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
// Regexp url parameters:
r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug) // GET /articles/home-is-toronto
// Subrouters:
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
})
})
// Mount the admin sub-router
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)))
}
// A completely separate router for administrator routes
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)
})
}
```
## Router 接口
chi 的路由器基于一种 [Patricia Radix trie](https://en.wikipedia.org/wiki/Radix_tree)(基数树)。
该路由器完全兼容 `net/http`。
建立在树之上的是 `Router` 接口:
```
// Router consisting of the core routing methods used by chi's Mux,
// using only the standard net/http.
type Router interface {
http.Handler
Routes
// Use appends one or more middlewares onto the Router stack.
Use(middlewares ...func(http.Handler) http.Handler)
// With adds inline middlewares for an endpoint handler.
With(middlewares ...func(http.Handler) http.Handler) Router
// Group adds a new inline-Router along the current routing
// path, with a fresh middleware stack for the inline-Router.
Group(fn func(r Router)) Router
// Route mounts a sub-Router along a `pattern` string.
Route(pattern string, fn func(r Router)) Router
// Mount attaches another http.Handler along ./pattern/*
Mount(pattern string, h http.Handler)
// Handle and HandleFunc adds routes for `pattern` that matches
// all HTTP methods.
Handle(pattern string, h http.Handler)
HandleFunc(pattern string, h http.HandlerFunc)
// Method and MethodFunc adds routes for `pattern` that matches
// the `method` HTTP method.
Method(method, pattern string, h http.Handler)
MethodFunc(method, pattern string, h http.HandlerFunc)
// HTTP-method routing along `pattern`
Connect(pattern string, h http.HandlerFunc)
Delete(pattern string, h http.HandlerFunc)
Get(pattern string, h http.HandlerFunc)
Head(pattern string, h http.HandlerFunc)
Options(pattern string, h http.HandlerFunc)
Patch(pattern string, h http.HandlerFunc)
Post(pattern string, h http.HandlerFunc)
Put(pattern string, h http.HandlerFunc)
Trace(pattern string, h http.HandlerFunc)
// NotFound defines a handler to respond whenever a route could
// not be found.
NotFound(h http.HandlerFunc)
// MethodNotAllowed defines a handler to respond whenever a method is
// not allowed.
MethodNotAllowed(h http.HandlerFunc)
}
// Routes interface adds two methods for router traversal, which is also
// used by the github.com/go-chi/docgen package to generate documentation for Routers.
type Routes interface {
// Routes returns the routing tree in an easily traversable structure.
Routes() []Route
// Middlewares returns the list of middlewares in use by the router.
Middlewares() Middlewares
// Match searches the routing tree for a handler that matches
// the method/path - similar to routing a http request, but without
// executing the handler thereafter.
Match(rctx *Context, method, path string) bool
}
```
每个路由方法接受一个 URL `pattern`(模式)和一组 `handlers`(处理器)。URL 模式支持命名参数(例如 `/users/{userID}`)和通配符(例如 `/admin/*`)。可以在运行时通过调用 `chi.URLParam(r, "userID")` 获取命名参数,通过调用 `chi.URLParam(r, "*")` 获取通配符参数。
### 中间件处理器 (Middleware handlers)
chi 的中间件只是标准的 net/http 中间件处理器。它们没有什么特别之处,这意味着路由器和所有工具的设计旨在与社区中的任何中间件兼容且友好。这提供了更好的可扩展性和包复用性,也是 chi 目的的核心。
这是一个标准 net/http 中间件的示例,我们将上下文键 `"user"` 赋值为 `"123"`。此中间件在请求上下文中设置一个假设的用户标识符,并调用链中的下一个处理器。
```
// HTTP middleware setting a value on the request context
func MyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// create new context from `r` request context, and assign key `"user"`
// to value of `"123"`
ctx := context.WithValue(r.Context(), "user", "123")
// call the next handler in the chain, passing the response writer and
// the updated request object with the new context value.
//
// note: context.Context values are nested, so any previously set
// values will be accessible as well, and the new `"user"` key
// will be accessible from this point forward.
next.ServeHTTP(w, r.WithContext(ctx))
})
}
```
### 请求处理器 (Request handlers)
chi 使用标准的 net/http 请求处理器。这个小片段是一个 http.Handler func 的示例,它从请求上下文中读取用户标识符——假设性地识别发送经过身份验证请求的用户,该请求由之前的中间件处理器验证并设置。
```
// HTTP handler accessing data from the request context.
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
// here we read from the request context and fetch out `"user"` key set in
// the MyMiddleware example above.
user := r.Context().Value("user").(string)
// respond to the client
w.Write([]byte(fmt.Sprintf("hi %s", user)))
}
```
### URL 参数
chi 的路由器解析 URL 参数并将其直接存储在请求上下文中。这是一个如何在 net/http 处理器中访问 URL 参数的示例。当然,中间件也能够访问相同的信息。
```
// HTTP handler accessing the url routing parameters.
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
// fetch the url parameter `"userID"` from the request of a matching
// routing pattern. An example routing pattern could be: /users/{userID}
userID := chi.URLParam(r, "userID")
// fetch `"key"` from the request context
ctx := r.Context()
key := ctx.Value("key").(string)
// respond to the client
w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key)))
}
```
## 中间件
chi 配备了一个可选的 `middleware` 包,提供了一套标准的 `net/http` 中间件。请注意,生态系统中任何同样兼容 `net/http` 的中间件都可以与 chi 的 mux 一起使用。
### 核心中间件
## | chi/middleware Handler | 描述 |
| :--------------------- | :---------------------------------------------------------------------- |
| [AllowContentEncoding] | 强制要求请求 Content-Encoding 头的白名单 |
| [AllowContentType] | 明确接受的请求 Content-Types 白名单 |
| [BasicAuth] | Basic HTTP 认证 |
| [Compress] | 为接受压缩响应的客户端提供 Gzip 压缩 |
| [ContentCharset] | 确保 Content-Type 请求头的字符集 |
| [CleanPath] | 清除请求路径中的双斜杠 |
| [GetHead] | 自动将未定义的 HEAD 请求路由到 GET 处理器 |
| [Heartbeat] | 用于检查服务器心跳的监控端点 |
| [Logger] | 记录每个请求的开始和结束以及处理耗时 |
| [NoCache] | 设置响应头以防止客户端缓存 |
| [Profiler] | 轻松将 net/http/pprof 附加到您的路由器 |
| [RealIP] | 将 http.Request 的 RemoteAddr 设置为 X-Real-IP 或 X-Forwarded-For |
| [Recoverer] | 优雅地捕获 panic 并打印堆栈跟踪 |
| [RequestID] | 将请求 ID 注入到每个请求的上下文中 |
| [RedirectSlashes] | 重定向路由路径上的斜杠 |
| [RouteHeaders] | 请求头的路由处理 |
| [SetHeader] | 用于设置响应头键/值的简写中间件 |
| [StripSlashes] | 去除路由路径上的斜杠 |
| [Sunset] | 设置 Deprecation/Sunset 响应头 |
| [Throttle] | 限制并发请求的数量 |
| [Timeout] | 当超时截止时间到达时向请求上下文发出信号 |
| [URLFormat] | 从 url 解析扩展名并将其放入请求上下文 |
| [WithValue] | 用于在请求上下文中设置键/值的简写中间件 |
### 额外的中间件和包
请查看 https://github.com/go-chi 获取额外的包。
## | 包 | 描述 |
|:---------------------------------------------------|:-------------------------------------------------------------
| [cors](https://github.com/go-chi/cors) | 跨源资源共享 (CORS) |
| [docgen](https://github.com/go-chi/docgen) | 在运行时打印 chi.Router 路由 |
| [jwtauth](https://github.com/go-chi/jwtauth) | JWT 认证 |
| [hostrouter](https://github.com/go-chi/hostrouter) | 基于域名/主机的请求路由 |
| [httplog](https://github.com/go-chi/httplog) | 小巧但强大的结构化 HTTP 请求日志记录 |
| [httprate](https://github.com/go-chi/httprate) | HTTP 请求速率限制器 |
| [httptracer](https://github.com/go-chi/httptracer) | HTTP 请求性能追踪库 |
| [httpvcr](https://github.com/go-chi/httpvcr) | 为外部源编写确定性测试 |
| [stampede](https://github.com/go-chi/stampede) | HTTP 请求合并器 |
## context 是什么?
`context` 是一个微小的包,提供了简单的接口来跨调用堆栈和 goroutine 发送信号。它最初由 [Sameer Ajmani](https://github.com/Sajmani) 编写,并从 go1.7 开始在标准库中提供。
了解更多 https://blog.golang.org/context
以及..
* 文档:https://golang.org/pkg/context
* 源码:https://github.com/golang/go/tree/master/src/context
## 基准测试
基准测试套件:https://github.com/pkieltyka/go-http-routing-benchmark
截至 2020 年 11 月 29 日,使用 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_ParamWrite 2863413 420 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
BenchmarkChi_GithubAll 10000 113811 ns/op 81203 B/op 406 allocs/op
BenchmarkChi_GPlusStatic 3337485 359 ns/op 400 B/op 2 allocs/op
BenchmarkChi_GPlusParam 2825853 423 ns/op 400 B/op 2 allocs/op
BenchmarkChi_GPlus2Params 2471697 483 ns/op 400 B/op 2 allocs/op
BenchmarkChi_GPlusAll 194220 5950 ns/op 5200 B/op 26 allocs/op
BenchmarkChi_ParseStatic 3365324 356 ns/op 400 B/op 2 allocs/op
BenchmarkChi_ParseParam 2976614 404 ns/op 400 B/op 2 allocs/op
BenchmarkChi_Parse2Params 2638084 439 ns/op 400 B/op 2 allocs/op
BenchmarkChi_ParseAll 109567 11295 ns/op 10400 B/op 52 allocs/op
BenchmarkChi_StaticAll 16846 71308 ns/op 62802 B/op 314 allocs/op
```
与其他路由器的比较:https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc
注意:上述基准测试中的内存分配 (allocs) 来自对 http.Request 的 `WithContext(context.Context)` 方法的调用,该方法克隆 http.Request,在复制(分配)的请求上设置 `Context()` 并返回新的请求对象。这就是 Go 中在请求上设置上下文的方式。
## 超越 REST
chi 只是一个 http 路由器,它允许您将请求处理分解为许多较小的层。许多公司使用 chi 为其公共 API 编写 REST 服务。但是,REST 只是通过 HTTP 管理状态的约定,编写完整的客户端-服务器系统或微服务网络还需要许多其他部分。
展望 REST 之外,我还推荐该领域的一些较新的作品:
* [webrpc](https://github.com/webrpc/webrpc) - 具有代码生成功能的 Web RPC 客户端+服务器框架
* [gRPC](https://github.com/grpc/grpc-go) - Google 基于 protobufs 的 RPC 框架
* [graphql](https://github.com/99designs/gqlgen) - 声明式查询语言
* [NATS](https://nats.io) - 轻量级发布-订阅
## 许可证
版权所有 (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka)
根据 [MIT 许可证](./LICENSE) 授权
标签:API网关, Chi, Context, DNS解析, EVTX分析, Go, Golang, HTTP路由, LangChain, net/http, REST API, Ruby工具, Syscall, Web开发, Web框架, 中间件, 安全编程, 开源项目, 日志审计, 服务端开发, 模块化, 路由器, 轻量级