翻译自pkg.go.dev,对某些模糊的地方添加了自己的解释
链接
- Github源码
- 关于看源码,还是应该从pkg.go.dev网站上去看,github上的源码可能包含尚未发布的内容。从pkg.go.dev网站上底部的source code可以直接查看相应的发布版本的源码
博客
- 他人博客,不一定正确,不一定及时,不一定可靠
- httprouter框架-博客园
- httprouter-源码分析 | Go 技术论坛
简要介绍
1. 概要
HttpRouter是轻量级高性能的HTTP请求路由,也称多路复用器(Mux),主要实现了
net/http
中的Handler部分的内容- 与
net/http
中实现的Mux不同,本路由器支持在路由规则(pattern)中使用变量,并与request方法匹配,且具有更好的伸缩性 - 使用了基数树进行路由的有效匹配,性能更高占用内存更小(类似redis的stream的底层数据结构)
-
2. 特性
唯一的明确匹配
- 其他的路由,比如http.ServeMux,一个URL请求可以匹配多个路由,所以需要添加各种优先级规则,比如最长匹配、先注册先匹配等。本路由器的设计是,一个请求只能匹配0或1个路由。所以,不会发生意外的匹配,这样有利于 SEO 以及用户体验
- 不关心最后的斜杠
- URL的末尾是否有
/
不会造成影响,路由器将会自动地进行重定向 - 这个功能也可以关掉
- URL的末尾是否有
- 路径的自动修正
- 除了自动对末尾
/
进行检测以外,路由器还可以修复错误的情况并移除多余的路径元素(如../
或者//
) - 可以启用大小写不敏感等功能
- 除了自动对末尾
- 路由中的参数
- 不再解析请求中的URL路径,仅仅只给路径部分进行命名,路由器会提供动态值
- 无垃圾
- 匹配和分派的过程中不会产生垃圾。仅有的堆分配是:为路径参数构建的KEY-VALUE切片,以及构建新的context以及请求对象(后者仅在标准的
Handler/HandlerFunc
API中)
- 匹配和分派的过程中不会产生垃圾。仅有的堆分配是:为路径参数构建的KEY-VALUE切片,以及构建新的context以及请求对象(后者仅在标准的
- 最好的性能
- 不再发生服务器崩溃
- 可以使用
PanicHandler
来处理恐慌并从中恢复,并可以记录日志并发送一个错误页面
- 可以使用
API的最佳选择
- 本路由器的设计鼓励构建合理的、分层的RESTful的API。此外,它对OPTIONS请求和
405 Method Not Allowed
的答复提供了内建的支持 - 也可以自定义
NotFound
和MethodNotAllowed
的handler3. 关于
http.Handler
我认为,// package net/http
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Handler
这个接口,某种程度上可以直接理解为路由器,ServeHTTP这个方法,就是用于实现转发、处理请求等功能的,可以实现成hello这样的函数,可以实现成继续转发的功能等等。使用HttpRouter包
1. 导入
import (
"github.com/julienschmidt/httprouter"
)
2. 关于路由中的两种参数
- 本路由器的设计鼓励构建合理的、分层的RESTful的API。此外,它对OPTIONS请求和
named parameters
:name
,匹配所有在下一个/
的内容- Path: /blog/:category/:post
- /blog/go/request-routers match: category=”go”, post=”request-routers”
- /blog/go/request-routers/ no match, but the router would redirect
- /blog/go/ no match
- /blog/go/request-routers/comments no match
全匹配catch-all parameters
*name
,匹配所有内容;只能用于一整个pattern的最后一个参数- Path: /files/*filepath
- /files/ match: filepath=”/“
- /files/LICENSE match: filepath=”/LICENSE”
- /files/templates/article.html match: filepath=”/templates/article.html”
- /files no match, but the router would redirect
工作原理
路由器依赖于基数树(radix tree),这是一个GET
请求的路由树例子Priority Path Handle
9 \ *<1>
3 ├s nil
2 |├earch\ *<2>
1 |└upport\ *<3>
2 ├blog\ *<4>
1 | └:post nil //:post只是一个占位符
1 | └\ *<5>
2 ├about-us\ *<6>
1 | └team\ *<7>
1 └contact\ *<8>
*<num>
表示的是其handler方法的内存地址。- 与哈希表不同,使用树形结构允许我们使用像
:post
这样的动态部分。 - 根据Benchmarks speak for themselves,这么做效率特别高。
- 由于URL是分层的结构,并且使用的字符集有限,因此存在很多公共前缀的概率是很高的。
路由器为每种请求方法单独管理一棵树(GET、POST等)。这个方法比[在每个节点都保存method->handle 映射更加节省空间],还可以使得我们在查找之前就减少路由问题
为了更好的扩展性,每一层的子节点都按照优先级排序。这里的优先级的含义是,其子孙节点中的handle的数量之和。这样做有如下的好处
关于Named parameters,在将http.Handler
注册成httprouter.Handler
之后,就可以通过request.Context
获得:
func Hello(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
fmt.Fprintf(w, "hello, %s!\n", params.ByName("name"))
}
上文说的方式是httprouter提供的辅助函数,也可以通过下面这种方式直接获得
params := r.Context().Value(httprouter.ParamsKey)
自动OPTIONS响应以及CORS
关于使用中间件
httprouter包仅仅只是提供了一个高效的路由器,其实这个路由器与http.ServeMux
相同,都是实现了http.Handler
接口,所以你仍然可以在路由器之前链接任何与http.Handler
兼容的中间件。比如,你可以just write your own,或者直接使用基于httprouter的框架
多域/子域
你的服务器是否服务于多个domian/host?是否需要使用sub-domain?为每个主机定义一个路由器即可
// We need an object that implements the http.Handler interface.
// Therefore we need a type for which we implement the ServeHTTP method.
// We just use a map here, in which we map host names (with port) to http.Handlers
type HostSwitch map[string]http.Handler
// Implement the ServeHTTP method on our new type
func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Check if a http.Handler is registered for the given host.
// If yes, use it to handle the request.
if handler := hs[r.Host]; handler != nil {
handler.ServeHTTP(w, r)
} else {
// Handle host names for which no handler is registered
http.Error(w, "Forbidden", 403) // Or Redirect?
}
}
func main() {
// Initialize a router as usual
router := httprouter.New()
router.GET("/", Index)
router.GET("/hello/:name", Hello)
// Make a new HostSwitch and insert the router (our http handler)
// for example.com and port 12345
hs := make(HostSwitch)
hs["example.com:12345"] = router
// Use the HostSwitch to listen and serve on port 12345
log.Fatal(http.ListenAndServe(":12345", hs))
}
// 新定义了一个路由器,用于转发
基本的认证
package main
import (
"fmt"
"log"
"net/http"
"github.com/julienschmidt/httprouter"
)
func BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// Get the Basic Authentication credentials
user, password, hasAuth := r.BasicAuth()
if hasAuth && user == requiredUser && password == requiredPassword {
// Delegate request to the given handle
h(w, r, ps)
} else {
// Request Basic Authentication otherwise
w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
}
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Not protected!\n")
}
func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Protected!\n")
}
func main() {
user := "gordon"
pass := "secret!"
router := httprouter.New()
router.GET("/", Index)
router.GET("/protected/", BasicAuth(Protected, user, pass))
log.Fatal(http.ListenAndServe(":8080", router))
}
与NotFound handler链接
可能需要将
Router.HandleMethodNotAllowed
设为false,以避免发生问题
你可以使用另一个http.Handler
(如另一个router),通过其Router.NotFound
来处理当前路由器无法匹配的请求。这是可以链接的(我是这么理解的,这段话是在说明Router.NotFound字段的用法)
静态文件
比如,可以用NotFound
处理器给/
目录提供静态文件
// Serve static files from the ./public directory
router.NotFound = http.FileServer(http.Dir("public"))
但是以上的方法回避了路由器的严格核心规则以避免路由问题。较为干净的方式是使用具体的子路径来提供文件,比如/static/*filepath
等
基于HttpRouter的框架
- Ace: Blazing fast Go Web Framework
- api2go: A JSON API Implementation for Go
- Gin: Features a martini-like API with much better performance
- Goat: A minimalistic REST API server in Go
- goMiddlewareChain: An express.js-like-middleware-chain
- Hikaru: Supports standalone and Google AppEngine
- Hitch: Hitch ties httprouter,httpcontext, and middleware up in a bow
- httpway: Simple middleware extension with context for httprouter and a server with gracefully shutdown support
- kami: A tiny web framework using x/net/context
- Medeina: Inspired by Ruby’s Roda and Cuba
- Neko: A lightweight web application framework for Golang
- pbgo: pbgo is a mini RPC/REST framework based on Protobuf
- River: River is a simple and lightweight REST server
- siesta: Composable HTTP handlers with contexts
- xmux: xmux is a httprouter fork on top of xhandler (net/context aware)