Go 先锋
读完需要
速读仅需 4 分钟
1. 请求路由的概念
请求路由的定义:请求路由是指将客户端的请求与服务器上对应的处理程序匹配和映射的过程。它决定了不同的 URL 或 API 请求被映射到哪些处理函数。
请求路由的作用:请求路由实现了请求与处理函数之间的解耦,使代码更加模块化。同时,它也使得 URL 和 API 更符合 RESTful 设计规范。请求路由还可以实现诸如负载均衡、缓存、日志记录、身份验证等功能。
2. net/http 包提供的请求路由功能
ServeMux 路由请求:net/http 包中的 ServeMux 类型实现了 HTTP 请求路由。它包含一个映射表,将注册的模式映射到对应的处理程序。
请求来临时,ServeMux 会在这个表中查找与请求 URL 匹配的模式,如果找到匹配的,就会调用注册的处理程序。
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/foo", fooHandler)
mux.HandleFunc("/bar", barHandler)
http.ListenAndServe(":8080", mux)
}
func fooHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello Foo!")
}
func barHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello Bar!")
}
上面使用 ServeMux 将 /foo 映射到 fooHandler 处理程序, /bar 映射到 barHandler。请求来临时就会调用对应的处理程序。
Handlers and HandlerFunc 路由处理:Go 语言通过 http.Handler 接口和 HandlerFunc 类型来定义请求处理程序。所有处理请求的处理程序都必须满足 http.Handler 接口。
HandlerFunc 类型则是实现了 http.Handler 接口的一个适配器,可以很方便的把普通函数(符合特定签名)转换成 http.Handler。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
在上面的 ServeMux 的示例中,用 HandleFunc() 函数使用了 HandlerFunc 类型的处理程序。
路由匹配规则:ServeMux 的路由匹配采用最长匹配原则。也就是说匹配度越高的路由拥有优先权。如果添加了这两个路由映射关系:
mux.HandleFunc("/app/", appHandler)
mux.HandleFunc("/app/info", infoHandler)
那么对于 /app/info 请求来说,会匹配 infoHandler 而不是 appHandler。
因为更长更具体的模式 /app/info 拥有优先匹配权。这就使得 ServeMux 实现了基本的请求路由功能。
3. 第三方路由框架
Go 语言生态中有很多优秀的请求路由框架。基于 net/http 包的路由功能,这些框架提供了更多高级功能,使用更加灵活方便。这里介绍两个流行的第三方路由框架。
Gorilla mux 的用法及特点:Gorilla mux 是 Go 语言中最流行的请求路由框架之一。它在 net/http 基础上,提供了强大的路由匹配与派发功能。特点包括:
功能强大的路由匹配:支持变量、通配符、正则表达式等多种方式的路由模式。
优雅的 URL 构建 API:可以通过代码构建复杂的 URL,而不需要自己拼接。
中间件支持:支持任意拦截器和中间件。实现诸如日志、身份验证等功能。
完全兼容 net/http。
下面是一个 Gorilla mux 的基本用法示例:
import (
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
// 变量路由
r.HandleFunc("/user/{userId}/{userName}", UserHandler)
// 正则表达式路由
r.HandleFunc("/articles/{category:[a-z]+}/", ArticlesCategoryRegHandler)
http.ListenAndServe(":8080", r)
}
用以上代码可以看出,Gorilla mux 通过声明方式定义路由,使得代码结构更加清晰。各种占位符、正则表达式也使得 URL 规则更加灵活。
Chi 的用法及特点:Chi 是另一个比较流行的 Go 语言路由框架。它采用中间件的模式,提供了很多额外的路由功能。
支持链式路由定义方式,语法更清晰。
内置强大的中间件生态。
支持通配、正则等路由方式。
性能高效。
下面是一个使用 Chi 构建服务的示例:
import (
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
)
func main() {
r := chi.NewRouter()
// 中间件,日志、错误处理等
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Get("/", HomePageHandler)
r.Route("/users", func(r chi.Router) {
r.Get("/", UserListHandler) // 匹配 /users
r.Get("/{userId}", UserGetHandler) // 匹配 /users/{userId}
})
http.ListenAndServe(":3000", r)
// chi 用 chain 方式处理请求,中间件模式在其中发挥得淋漓尽致。
}
各框架对比:Gorilla mux 和 chi 都是在 net/http 标准路由功能上的扩展,它们有以下主要区别:
Gorilla mux 使用声明式语法定义路由,chi 采用中间件链式处理。
Gorilla mux 功能更丰富,chi 性能更高。
Gorilla mux 社区更加活跃。
所以,若要求功能强大, Gorilla mux 是一个不错的选择; 如果要求性能和链式处理,chi 更胜一筹。
4. 定义路由的最佳实践
良好的路由设计有助于构建健壮、易用、易扩展的系统。将介绍请求路由的两个最佳实践。
RESTful API 设计:REST 架构风格提倡使用统一的接口风格设计 API,使其更简洁、灵活、易理解。最常见的设计就是通过 HTTP 动词区分对资源的不同操作类型。如下表所示:
HTTP 动词 | 操作类型 |
---|---|
GET | 获取资源 |
POST | 创建资源 |
PUT | 更新资源 |
DELETE | 删除资源 |
在 Go 语言中实现这样的 API 也非常简单。以一个用户管理 API 为例:
func main() {
r := mux.NewRouter()
r.HandleFunc("/users", getUsers).Methods("GET")
r.HandleFunc("/users/{userId}", createUser).Methods("POST")
r.HandleFunc("/users/{userId}", updateUser).Methods("PUT")
r.HandleFunc("/users/{userId}", deleteUser).Methods("DELETE")
log.Fatal(http.ListenAndServe(":8000", r))
}
用 Go 语言的请求路由功能,轻松构建符合 REST 风格。
优雅的代码结构:Go 语言中比较推荐的项目布局方式是按功能将代码分组到内部包中。这种方式也适用于组织路由代码。下面是一个按功能分组的目录结构示例:
├── main.go
├── route.go
├── router/
| ├── blog.go
| └── user.go
├── handlers/
| ├── blog.go
| └── user.go
└── middlewares/
├── auth.go
└── logging.go
在这样的布局下,可以在 route.go 中构建路由器; router 包中按功能定义路由规则; handlers 包中实现请求处理; middlewares 包实现中间件。这样可以使各个功能组件解耦,也便于代码维护。
main.go 中可以如下初始化:
func main() {
router := NewRouter()
// 加载user路由
user.Load(router)
// 加载blog路由
blog.Load(router)
// 使用日志、认证中间件
router.Use(middlewares.Logging, middlewares.Auth)
http.ListenAndServe(":8000", router)
}
5. 性能优化手段
对 Go 语言 Web 服务来说,请求路由性能是关键的性能优化点。主要可以从两个方面进行优化:
优化请求匹配:请求路由中的模式匹配是性能的关键点。可以从编写高效正则,最小化回溯次数等方面进行优化。一些框架也使用了并行匹配、预排序、cache 等机制提高性能。
并发连接处理:Go 语言得天独厚的并发支持也应该在请求路由中得以利用。常见的做法是为不同路由分组使用不同的 ServeMux 实例。并使用 Goroutine 并发处理它们
func main() {
apiRouter := mux.NewRouter()
webRouter := mux.NewRouter()
// 为两个路由器分别处理请求
go http.ListenAndServe(":8080", apiRouter)
http.ListenAndServe(":8000", webRouter)
}
6. 总结
Go 语言实现 Web 请求路由的特点
语言原生支持路由功能,使用简单。
支持高度定制化的路由实现。
第三方路由框架功能强大。
高并发处理能力。
Go 方式 vs 传统方式
Go 语言通过 interface 实现灵活机制。类似 node.js 中间件模式。
性能接近 Nginx,并发能力强。
更容易实现微服务架构。
Go 语言利用自身优势,提供了简单却强大的 Web 请求路由实现。
同时丰富的设计模式使得 Go 路由功能可扩展性强,容易实现高性能服务。
未来随着框架的成熟,Go 语言在后端服务端会有越来越广阔的应用前景。