README
¶
Rivet
专注路由. 简洁, 贪心匹配, 支持注入, 可定制, 深度解耦的 http 路由管理器.
examples 目录中有几个例子, 方便您了解 Rivet.
这里有个路由专项评测 go-http-routing-benchmark.
Rivet 版本号采用 Semantic Versioning.
简洁
Rivet 使用常规风格.
示例: 复制到本地运行此代码, 然后后点击 这里
package main
import (
"io"
"net/http"
"github.com/typepress/rivet"
)
// 常规风格 handler
func HelloWord(rw http.ResponseWriter, req *http.Request) {
io.WriteString(rw, "Hello Word")
}
/**
带参数的 handler.
params 是从 URL.Path 中提取到的参数.
params 的另一种风格是 PathParams/Scene. 参见 Scene.
*/
func Hi(params rivet.Params, rw http.ResponseWriter) {
io.WriteString(rw, "Hi "+params.Get("who")) // 提取参数 who
}
func main() {
// 新建路由管理器
mux := rivet.NewRouter(nil) // 下文解释参数 nil
// 注册路由
mux.Get("/", HelloWord)
mux.Get("/:who", Hi) // 参数名设定为 "who"
// rivet.Router 符合 http.Handler 接口
http.ListenAndServe(":3000", mux)
}
上例中 "/"
是无参数路由. "/:who"
是有参数路由, 参数名为 "who".
访问 "/" 输出:
Hello Word
访问 "/Boy" 输出:
Hi Boy
访问 "/Girl" 输出:
Hi Girl
访问 "/news/sports" 会得到 404 NotFound 页面.
以 api.github.com 真实路由为例:
mux.Get("/users/:user/events", Events)
mux.Get("/users/:user/events/orgs/:org", Events)
因为都用 Events 函数作为 handler, 可以这样写:
func Events(params rivet.Params, rw http.ResponseWriter) {
user := params.Get("owner")
if user == "github" {
// 用户 github 很受欢迎, 需要特别处理
// do something
return
}
// 因为两个路由 path 都用 Events 处理, 可根据参数进行区分
org := params.Get("org")
if org != "" {
// 对应 "/users/:user/events/orgs/:org" 的处理
return
}
// 对应 "/users/:user/events" 的处理
}
事实上 api.github.com 路由很多, 分开用不同的 handler 处理才是好方法:
mux.Get("/users/:user/events", userEvents)
mux.Get("/users/:user/events/orgs/:org", userOrgEvents)
提示: 如果 Params 类型不适合您, 请看 Scene 部分.
贪心匹配
通常 Router 库都能支持静态路由, 参数路由, 可选尾部斜线等, Rivet 也同样支持, 而且做的更好. 下面这些路由并存, 同样能正确匹配:
"/",
"/**",
"/hi",
"/hi/**",
"/hi/path/to",
"/hi/:name/to",
"/:name",
"/:name/path/?",
"/:name/path/to",
"/:name/path/**",
"/:name/**",
当 URL.Path 为
"/xx/zzz/yyy"
时 "/:name/**
会被匹配, 它的层级比较深, 这符合贪心匹配原则.
使用者有可能困惑, 因为 "/**"
和 "/:name/**"
都可以匹配 "/xx/zzz/yyy"
. 记住贪心匹配原则, 否则避免这种用法即可. 后文会详细介绍路由风格.
注入
rivet.Context 支持注入(Injector), 有三个关键方法:
// MapTo 以 t 为 key 把变量 v 关联到 context. 相同 t 值只保留一个.
MapTo(v interface{}, t uint)
// Get 以类型标识 t 为 key, 返回关联到 context 的变量.
Get(t uint) interface{}
// Map 自动提取 v 的类型标识作为 t, 调用 MaptTo. 通常使用 Map.
Map(v interface{})
现实中会有一些需求, 比如服务器对不同用户在相同 URL.Path 下有不同响应, 也就是用户角色控制. 使用注入后会很简单.
// 用户角色控制示意, 简单的定义为 string
type Role string
/**
在 handler 函数中加上 rivet.Context 参数即可用注入标记用户角色,
*/
func UserRole(c rivet.Context) {
// Context.Request() 返回 *http.Request
req := c.Request()
// 通常根据 session 确定用户角色.
session := req.Cookie("session").Value
/**
这里只是示意代码, 现实中的逻辑更复杂.
用注入函数 Map, 把用户角色关联到上下文.
*/
switch session {
default: // 游客
c.Map(Role(""))
case "admin": // 管理员
c.Map(Role("admin"))
case "signOn": // 已经登录
c.Map(Role("signOn"))
}
}
/**
DelComments 删除评论, role 参数由前面的 UserRole 注入上下文.
*/
func DelComments(role Role, params rivet.Params, rw http.ResponseWriter) {
if role == "" {
// 拒绝游客
rw.WriteHeader(http.StatusForbidden)
return
}
if role == "admin" {
// 允许 admin
// do delete
return
}
// 其他角色,需要更多的判断
// do something
}
func main() {
// ...
//注册路由:
mux.Get("/del/comments/:id", UserRole, DelComments)
// ...
}
这个例子中, "/del/comments/:id"
被匹配后, 先执行 UserRole, 把用户角色关联到 Context, 因为 UserRole 没有对 http.ResponseWriter 进行写操作, DelComments 会被执行. Rivet 负责传递 DelComments 需要的参数 UserRole 等. DelComments 获得 role 变量进行相应的处理, 完成角色控制.
提示: 如果 Rivet 发现 ResponseWriter 写入任何内容, 认为响应已经完成, 不再执行后续 handler
定制
事实上, 上例中的 UserRole 很多地方都要用, 每次注册路由都带上 UserRole 很不方便. 通常在路由匹配之前执行 UserRole. 可以这样用:
// 定义自己的 rivet.Context 生成器
func MyRiveter(rw http.ResponseWriter, req *http.Request) rivet.Context {
c := new(rivet.NewContext(rw, req))
// 先执行角色控制
UserRole(c)
return c
}
func main() {
// 使用 MyRiveter
mux := rivet.NewRouter(MyRiveter)
mux.Get("/del/comments/:id", DelComments)
http.ListenAndServe(":3000", mux)
}
方法也很多, 这只是最简单的一种.
提示: 善用 Filter 可真正起到滤器请求的作用.
深度解耦
解耦使应用能切入到路由执行流程中的每一个环节, 达到高度定制. Rivet 在不失性能的前提下, 对解耦做了很多努力. 了解 Rivet 的类型和接口有助于深度定制路由流程.
- Params 保存 URL.Path 中的参数
- Filter 检查/转换 URL.Path 参数, 亦可过滤请求.
- Node 保存 handler, 每个 Node 都拥唯一 id.
- Trie 匹配 URL.Path, 调用 Filter, 调用 Params 生成器. 匹配到的 Trie.id 和 Node.id 是对应的.
- Context 维护上下文, 处理 handler. 内置 Rivet 实现了它.
- Router 路由管理器, 把上述对象联系起来, 完成路由功能.
他们是如何解耦:
Params 无其它依赖, 有 PathParams 风格可选. 自定义 ParamsReceiver 定制.
Filter 接口无其它依赖. 自定义 FilterBuilder 定制.
Node 接口依赖 Context. 自定义 NodeBuilder 定制. 可以建立独立的 Context.
Trie 是路由匹配的核心, 依赖 Filter, ParamsReceiver. 它们都可定制.
Context 接口依赖 ParamsReceiver, 这只是个函数, 最终也是无依赖的. Context 用了注入, 可能您的应用并不需要注入, 不用它即可.
Rivet 是内置的 Context 实现, 是个 struct, 可以扩展.
提示: 注入是透明的, 不使用不产生开销, 使用了开销也不高.
Router 依赖上述所有. 了解函数类型 NodeBuilder 和 Riveter 定制自己的 Node, Context.
定制使用大概分两类:
底层: 直接使用 Trie, 构建自己的 Node, ParamsReceiver, Context, Router.
需要了解 TypeIdOf, NewContext, NewNode, ParamsFunc, FilterFunc.
扩展: 使用 Router, 自定义 Context 生成器, 或者扩展 Rivet.
提示: 底层定制 Trie 需要 FilterBuilder, 如果 Path 参数无类型. 直接用 nil 替代, Trie 可以正常工作.
下文展示扩展定制方法.
自定义 Context 生成器:
// 自定义 Context 生成器, 实现真正的 http.Flusher
func MyRiveter(rw http.ResponseWriter, req *http.Request) rivet.Context {
// 构建自己的 http.Flusher
rw = MyResponseWriterFlusher(rw)
c := new(rivet.NewContext(rw, req)) // 依旧使用 rivet.Rivet
return c
}
rivet 内置的 ResponseWriteFakeFlusher 是个伪 http.Flusher, 只是有个 Flush() 方法, 没有真的实现 http.Flusher 功能. 如果您需要真正的 Flusher 需要自己实现.
实现自己的 Context 很容易, 善用 Next 和 Invoke 方法即可.
举例:
/**
扩展 Context, 实现 Before Handler.
*/
type MyContext struct {
rivet.Context
beforeIsRun true
}
/**
MyContext 生成器
使用:
reivt.Router(MyRiveter)
*/
func MyRiveter(res http.ResponseWriter, req *http.Request) rivet.Context {
c := new(MyContext)
c.Context = rivet.NewContext(res, req)
return c
}
func (c *MyContext) Next() {
if !beforeIsRun {
// 执行 Before Handler
// do something
beforeIsRun = true
}
c.Context.Next()
}
// 观察者模式
func Observer(c rivet.Context) {
defer func() {
if err := recover(); err != nil {
// 捕获 panic
// do something
return
}
// 其他操作, 比如写日志, 统计执行时间等等
// do something
}()
c.Next()
}
/**
调用 Context.Invoke, 插入执行另外的 handler.
MyInvoke 插入执行 SendStaticFile, 这和直接调用 SendStaticFile 不同.
这样的 SendStaticFile 可以使用上下文关联变量, 就像上文讲的角色控制.
而 MyInvoke 不必关心 SendStaticFile 所需要的参数, 那可以由别的代码负责.
*/
func MyInvoke(c rivet.Context) {
c.Invoke(SendStaticFile)
}
/**
发送静态文件, 参数 root 是前期代码关联好的.
现实中简单的改写 req.URL.Path, 无需 root 参数也是可行的.
*/
func SendStaticFile(root http.Dir, rw http.ResponseWriter, req *http.Request) {
// send ...
}
最佳实践
上面的代码类似中间件的作用, 从代码复用性来说, import 非官方包所引起的类型依赖, 都有碍于代码复用. 所以最佳形式的中间件不应该含有
import "github.com/typepress/rivet"
如果中间件用到什么参数直接在 Handler 函数声明中写参数类型就好, 关联变量到上下文是应用代码负责的. 对于 URL.Path 参数, rivet 定义了两种类型,Params 和 PathParams, 事实上如果中间件用到这两个类型, 您也无需 import rivet. 以最前面的 Hi Handler为例, 您可以这样写:
func Hi(params map[string]string, rw http.ResponseWriter) {
io.WriteString(rw, "Hi "+params.Get("who")) // 提取参数 who
}
rivet 对 map[string]string 和 rivet.PathParams 当作同类型处理. 同理 map[string]interface{} 和 rivet.Params 当作同类型处理. 也就是说最佳实践的标准:
当 Handler 不需要使用 Context.Map 的时候, 无需 import rivet. Context.Map 应该在应用代码中使用而不是可复用的中间件.
提示: 请仔细阅读 Scene 章节. 了解 NewContext 和 NewScene 的差别.
路由风格
Rivet 对路由 pattern 支持丰富.
示例:
"/news/:cat"
可匹配:
"/news/sprots"
"/news/health"
示例:
"/news/:cat/:id"
可匹配:
"/news/sprots/9527"
"/news/health/1024"
上面的路由只有参数名, 数据类型都是 string. Rivet 还支持带类型的 pattern.
示例:
"/news/:cat/:id uint"
":id uint" 表示参数名是 "id", 数据要符合 "uint" 的要求.
"uint" 是内置的 Filter class, 参见 FilterClass, 您可以注册新的 class.
示例: 可选尾斜线
"/news/?"
可匹配:
"/news"
"/news/"
提示: "/?" 只能在尾部出现.
除了可选尾斜线, 路由风格可归纳为:
"/path/to/prefix:pattern/:pattern/:"
其中 "path", "to","prefix" 是占位符, 表示固定字符, 称为定值. ":pattern" 格式为:
:name class arg1 arg2 argN
以 ":" 开始, 以 " " 作为分隔符.
第一段是参数名, 第二段是类型名, 后续为参数.
示例: ":cat string 6"
cat
为参数名, 如果省略只验证不提取参数, 形如 ": string 6"
string
为类型名, 可以自定义 class 注册到 FilterClass 变量.
6
为长度参数, 可以设置一个限制长度参数. 例如
":name string 5"
":name uint 9"
":name hex 32"
:name class
提取参数, 以 "name" 为 key, 根据 class 对值进行合法性检查.
:name
提取参数, 不对值进行合法检查, 值不能为空.
如果允许空值要使用 ":name *". "*" 是个 class, 允许空值.
:
不提取参数, 不检查值, 允许空值, 等同于 ": *".
::
只能用于模式尾部. 提取参数, 不检查值, 允许空值, 参数名为 "*".
例如:
"/path/to/::"
可匹配:
"/path/to/", "*" 为参数名, 值为 "".
"/path/to/paths", "*" 为参数名, 值为 "paths".
"/path/to/path/path", "*" 为参数名, 值为 "path/path".
*
"*" 可替代 ":" 作为开始定界符, 某些情况 "*" 更符合习惯, 如:
"/path/to*"
"/path/to/**"
提示: 含有 class 才会生成 Filter, 否则被优化处理. 式中以一个空格作为分割符, 连续空格会产生其他语义.
正则支持
事实上这只是一个名字为 "|" 的内建 Filter.
"/path/to/:id | ^id(\d+)$"
其中的 :id | ^id(\d+)$
是正则写法, |
是内建正则 Filter 的名字, 后跟正则表达式. 你也许主要到这个正则中有分组, 内建的正则 Filter 提取的就是最后一组匹配, 当然也可以不分组.
提示: 正则中不能含有 "/".
Scene
路由风格支持带类型的参数, Filter 检查时可能会对参数进行类型转换, interface{} 方便保存转换后的结果, 后续代码无需再次检查转换, 所以 Params 定义成这样:
type Params map[string]interface{}
一些应用场景无转换需求, 只需要简单定义:
type PathParams map[string]string
是的, 这种场景也很普遍. Scene 就是为此准备的 Context.
Scene 的使用很简单:
package main
import (
"io"
"net/http"
"github.com/typepress/rivet"
)
/**
params 类型为 PathParams, 是从 URL.Path 中提取到的参数.
PathParams 和 Scene 配套使用.
*/
func Hi(params rivet.PathParams, rw http.ResponseWriter) {
io.WriteString(rw, "Hi "+params["who"])
}
func main() {
// 传递 NewScene, 采用 PathParams 风格
mux := rivet.NewRouter(rivet.NewScene)
mux.Get("/:who", Hi) // 参数名设定为 "who"
http.ListenAndServe(":3000", mux)
}
提示: NewScene 以 PathParams 类型保存 URL.Path 参数, Params 初始值为 nil. 相反的, NewContext 以 Params 类型保存 URL.Path 参数, PathParams 初始值为 nil. 当获取 URL.Path 参数时, Rivet 根据类型自动做了转换, 以保障两种风格的 Handler 都可以获取 URL.Path 参数. 很明显使用 NewScene 风格便使用 Params 类型为参数, 获取到的仍旧是 string 类型. 交叉使用 Params, PathParams 增加了转换开销.
Acknowledgements
Inspiration from Julien Schmidt's httprouter, about Trie struct.
Trie 算法和结构灵感来自 Julien Schmidt's httprouter.
LICENSE
Copyright (c) 2013 Julien Schmidt. All rights reserved. Copyright (c) 2014 The TypePress Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
Documentation
¶
Index ¶
- Variables
- func TypeIdOf(v interface{}) uint
- type Context
- type Filter
- type FilterBuilder
- type FilterFunc
- type Handler
- type Node
- type NodeBuilder
- type Params
- type ParamsFunc
- type ParamsReceiver
- type PathParams
- type ResponseWriteFakeFlusher
- func (rw *ResponseWriteFakeFlusher) Flush()
- func (rw *ResponseWriteFakeFlusher) Size() int
- func (rw *ResponseWriteFakeFlusher) Status() int
- func (rw *ResponseWriteFakeFlusher) Write(b []byte) (int, error)
- func (rw *ResponseWriteFakeFlusher) WriteHeader(s int)
- func (rw *ResponseWriteFakeFlusher) Written() bool
- type ResponseWriter
- type Rivet
- func (r *Rivet) Get(t uint) (v interface{})
- func (c *Rivet) GetParams() Params
- func (c *Rivet) GetPathParams() PathParams
- func (c *Rivet) Handlers(handler ...interface{})
- func (c *Rivet) Invoke(handler interface{}) bool
- func (r *Rivet) Map(v interface{})
- func (r *Rivet) MapTo(v interface{}, t uint)
- func (c *Rivet) Next()
- func (c *Rivet) ParamsReceiver(name, text string, val interface{})
- func (c *Rivet) Request() *http.Request
- func (c *Rivet) Response() http.ResponseWriter
- func (c *Rivet) WriteString(data string) (int, error)
- type Riveter
- type Router
- func (r *Router) Any(pattern string, handler ...interface{}) Node
- func (r *Router) Delete(pattern string, handler ...interface{}) Node
- func (r *Router) Get(pattern string, handler ...interface{}) Node
- func (r *Router) Handle(method string, pattern string, h ...interface{}) Node
- func (r *Router) Head(pattern string, handler ...interface{}) Node
- func (r *Router) Match(method, urlPath string, rec ParamsReceiver, rw http.ResponseWriter, ...) Node
- func (r *Router) NodeBuilder(nb NodeBuilder)
- func (r *Router) NotFound(h ...interface{}) Node
- func (r *Router) Options(pattern string, handler ...interface{}) Node
- func (r *Router) Patch(pattern string, handler ...interface{}) Node
- func (r *Router) Post(pattern string, handler ...interface{}) Node
- func (r *Router) Put(pattern string, handler ...interface{}) Node
- func (r *Router) RootTrie(method string) *Trie
- func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request)
- type Scene
- type Trie
- func (t *Trie) Add(path string, newFilter FilterBuilder) *Trie
- func (p Trie) Filter(text string, rw http.ResponseWriter, req *http.Request) (string, interface{}, bool)
- func (t *Trie) GetId() int
- func (t *Trie) Match(path string, rec ParamsReceiver, rw http.ResponseWriter, req *http.Request) *Trie
- func (t *Trie) Print(prefix string) (count int)
- func (t *Trie) SetId(id int)
Constants ¶
This section is empty.
Variables ¶
var FilterClass = map[string]FilterBuilder{
"": builtinFilter,
"*": builtinFilter,
"string": builtinFilter,
"alpha": builtinFilter,
"alnum": builtinFilter,
"hex": builtinFilter,
"uint": builtinFilter,
"|": builtinFilter,
}
* FilterClass 保存 Fliter 生成器, 使用者可注册新的生成器.
内建 class 列表:
空, 占位 class. * Unicode characters, 允许空值, 等同于: ": *", 占位 class. string 非空 Unicode characters, 缺省值, 如果没有参数可省略. alpha [a-zA-Z]+ alnum [a-zA-Z]+[0-9]+ hex [a-z0-9]+ uint uint 可以使用 strconv.ParseUint 的 bitSize 参数 | 正则, 样例: ":id | ^id([0-9]+)$". 用 FindStringSubmatch 提取最后一个 Submatch.
其中: string, alpha, alnum, hex 可以加一个长度限制参数, 如:
":name string 10" 限制参数字符串字节长度不超过 10.
Functions ¶
func TypeIdOf ¶
func TypeIdOf(v interface{}) uint
* TypeIdOf 返回 v 的类型签名地址, 转换为 uint 类型. 内部使用 reflect 获取类型地址.
示例: 获取 fmt.Stringer 接口类型签名:
var v *fmt.Stringer _ = TypeIdOf(v) // 或者 _ = TypeIdOf((*fmt.Stringer)(nil))
获取 reflect.Type 本身的类型签名:
var rt *reflect.Type _ = TypeIdOf(rt) // reflect.Type 也是接口类型 // 或者 t := reflect.TypeOf(nil) _ = TypeIdOf(&t)
获取函数的参数类型签名:
t := reflect.TypeOf(fmt.Println) _ = TypeIdOf(t.In(0))
非接口类型:
var s string _ = TypeIdOf(s) // 等同 TypeIdOf("") var v AnyNotInterfaceType _ = TypeIdOf(v)
Types ¶
type Context ¶
type Context interface { // Context 要实现参数接收器. ParamsReceiver // Request 返回生成 Context 的 *http.Request. Request() *http.Request // Response 返回生成 Context 的 http.ResponseWriter. Response() http.ResponseWriter // WriteString 向 http.ResponseWriter 写入 data. WriteString(data string) (int, error) // GetParams 返回路由匹配时从 URL.Path 中提取的参数. GetParams() Params /** PathParams 返回路由匹配时从 URL.Path 中提取的原始参数. 需要与 Scene/NewScene 配套使用. */ GetPathParams() PathParams // Handlers 设置 Handler, 通常这只能使用一次 Handlers(handler ...interface{}) /** Invoke 处理 handler, 如果无法调用, 关联到 context. 如果 handler 可被调用, 但是无法获取其参数, 返回 false. 否则返回 true. */ Invoke(handler interface{}) bool // Next 遍历 Handlers 保存的 handler, 通过 Invoke 调用. Next() // Map 等同 MapTo(v, TypeIdOf(v)) Map(v interface{}) /** MapTo 以 t 为 key 把变量 v 关联到 context. 相同 t 值只保留一个. */ MapTo(v interface{}, t uint) /** Get 以类型标识 t 为 key, 获取关联到 context 的变量. 如果未找到, 返回 nil. */ Get(t uint) interface{} }
* Context 是 Request 上下文, 主要负责关联变量并调用 Handler. 事实上 Context 采用 All-In-One 的设计方式, 具体实现不必未完成所有接口, 使用方法配套即可.
func NewContext ¶
func NewContext(res http.ResponseWriter, req *http.Request) Context
NewContext 新建 *Rivet 实例实现的 Context.
type Filter ¶
type Filter interface { /** Filter 参数 text 举例: 有路由规则 "/blog/cat:id num 6". 实例 URL.Path "/blog/cat3282" 需要过滤. text 参数值是字符串 "3282". 参数 rw, req: Filter 可能需要 req 的信息, 甚至直接写 rw. 返回值: string 通过检查提取的字符串值. interface{} 通过检查/转换后的数据. bool 值表示是否通过过滤器. */ Filter(text string, rw http.ResponseWriter, req *http.Request) (string, interface{}, bool) }
* Filter 检验, 转换 URL.Path 参数, 亦可过滤 http.Request.
type FilterFunc ¶
* FilterFunc 包装函数符合 Filter 接口.
func (FilterFunc) Filter ¶
func (filter FilterFunc) Filter(text string, _ http.ResponseWriter, _ *http.Request) (string, interface{}, bool)
type Node ¶
type Node interface { /** Riveter 设置 Riveter. 此方法让 Node 可以拥有单独 Context. */ Riveter(riveter Riveter) /** Handlers 设置路由 Handler. */ Handlers(handler ...interface{}) /** Apply 调用 Context 的 Handlers 和 Next 方法. 如果设置了 Riveter, 可使用生成独立的 Context. */ Apply(context Context) /** Id 返回 Node 的识别 id, 0 表示 NotFound 节点. 此值由 NodeBuilder 确定. */ Id() int }
* Node 保存路由 Handler, 并调用 Context 的 Handlers 和 Next 方法.
type Params ¶
type Params map[string]interface{}
* Params 存储从 URL.Path 中提取的参数. 值可能经过 Filter 转换.
func (Params) ParamsReceiver ¶
ParamsReceiver 逐个接受从 URL.Path 中提取的参数.
type ParamsFunc ¶
type ParamsFunc func(key, text string, val interface{})
* ParamsFunc 包装函数符合 ParamsReceiver 接口.
func (ParamsFunc) ParamsReceiver ¶
func (rec ParamsFunc) ParamsReceiver(key, text string, val interface{})
type ParamsReceiver ¶
type ParamsReceiver interface { /** ParamsReceiver 逐个接受参数. 参数: name 参数名, "*" 代表 catch-All 模式的名字 text URL.Path 中的原始值. val 经 Filter 处理后的值. */ ParamsReceiver(name, text string, val interface{}) }
* ParamsReceiver 接收从 URL.Path 中提取的参数.
type PathParams ¶
* PathParams 存储从 URL.Path 中提取的原始参数. 与 Scene/NewScene 配套使用.
func (PathParams) ParamsReceiver ¶
func (p PathParams) ParamsReceiver(key, text string, _ interface{})
ParamsReceiver 逐个接受从 URL.Path 中提取的原始参数.
type ResponseWriteFakeFlusher ¶
type ResponseWriteFakeFlusher struct { http.ResponseWriter // contains filtered or unexported fields }
* ResponseWriteFakeFlusher 实现了 http.ResponseWriter 接口和伪 http.Flusher 接口. Flush() 是个方法, 是否支持 Flusher 取决于原 http.ResponseWriter 实例.
func (*ResponseWriteFakeFlusher) Flush ¶
func (rw *ResponseWriteFakeFlusher) Flush()
Flush() 是个伪方法, 是否支持 Flusher 取决于原 http.ResponseWriter 实例.
func (*ResponseWriteFakeFlusher) Size ¶
func (rw *ResponseWriteFakeFlusher) Size() int
* Size 返回通过 Write 的总字节数.
func (*ResponseWriteFakeFlusher) Status ¶
func (rw *ResponseWriteFakeFlusher) Status() int
* Status 返回通过 WriteHeader 设置的值.
func (*ResponseWriteFakeFlusher) Write ¶
func (rw *ResponseWriteFakeFlusher) Write(b []byte) (int, error)
* Write 向相应写入 b, 返回本次写入的字节和发生的错误.
func (*ResponseWriteFakeFlusher) WriteHeader ¶
func (rw *ResponseWriteFakeFlusher) WriteHeader(s int)
* WriteHeader 向相应发送状态码 s.
func (*ResponseWriteFakeFlusher) Written ¶
func (rw *ResponseWriteFakeFlusher) Written() bool
* Written 返回 Status()!=0 || Size()!=0 的结果
type ResponseWriter ¶
type ResponseWriter interface { http.ResponseWriter http.Flusher // Status 返回调用 WriteHeader 设定的值, 初始值为 0 Status() int // Size 返回调用 Write 写入的字节数, 初始值为 0 Size() int /** Written 返回是否已经写入了内容. 包括两种情况, WriteHeader 和 Write. 实现是如何判断的可能有差异. */ Written() bool }
* ResponseWriter 提供状态支持
func NewResponseWriterFakeFlusher ¶
func NewResponseWriterFakeFlusher(rw http.ResponseWriter) ResponseWriter
* NewResponseWriterFakeFlusher 返回 ResponseWriter 实例, 可能是伪 http.Flusher. 如果 rw 已经实现了 ResponseWriter 接口, 返回 rw.(ResponseWriter). 否则返回 &ResponseWriteFakeFlusher 伪 http.Flusher 实例.
type Rivet ¶
type Rivet struct { Params PathParams // contains filtered or unexported fields }
* Rivet 符合 Context 接口. 请使用 NewContext 生成实例.
func (*Rivet) Get ¶
* Get 以类型标识 t 为 key, 获取关联到 context 的变量. 如果未找到, 通常返回 nil, 特别的:
如果 t 代表 map[string]interface{}, 用 Params 标识再试一次. 如果 t 代表 map[string]string, 用 PathParams 标识再试一次.
这样做, 如果不用 Map 功能, 所写的 Handler 就不需要 import rivet.
func (*Rivet) GetPathParams ¶
func (c *Rivet) GetPathParams() PathParams
* PathParams 返回路由匹配时从 URL.Path 中提取的参数 此方法与 Scene/NewScene 配套使用.
func (*Rivet) Handlers ¶
func (c *Rivet) Handlers(handler ...interface{})
Handlers 设置 handler, 首次使用有效.
func (*Rivet) Invoke ¶
* Invoke 处理 handler.
参数:
handler 可以是任意值 如果 handler 可被调用, 准备相应参数, 并调用 handler. 否则 使用 Map 关联到 context.
返回:
如果 handler 可被调用, 但是无法获取其参数, 返回 false. 否则返回 true.
算法:
如果 handler 是函数或者是有 ServeHTTP 方法的对象, 准备参数并调用. 否则使用 Map 关联到 context. ServeHTTP 支持泛类型, 当然包括 http.Handler 实例. 下列 handler 类型使用 switch 匹配, 参数直接传递, 未用 Get 从 context 获取: func() func(Context) func(*http.Request) func(ResponseWriter) func(ResponseWriter, *http.Request) func(http.ResponseWriter) func(http.ResponseWriter, *http.Request) func(map[string]interface{}, http.ResponseWriter, *http.Request) func(Params, *http.Request) func(Params, ResponseWriter) func(Params, http.ResponseWriter) func(Params, ResponseWriter, *http.Request) func(Params, http.ResponseWriter, *http.Request) func(map[string]string, http.ResponseWriter, *http.Request) func(PathParams, *http.Request) func(PathParams, ResponseWriter) func(PathParams, http.ResponseWriter) func(PathParams, ResponseWriter, *http.Request) func(PathParams, http.ResponseWriter, *http.Request) http.Handler
提示:
Invoke 未捕获可能产生的 panic, 需要使用者处理.
func (*Rivet) Map ¶
func (r *Rivet) Map(v interface{})
* Map 等同 MapTo(v, TypeIdOf(v)). 以 v 的类型标识为 key. Rivet 自动 Map 的变量类型有:
Context Params PathParams ResponseWriter http.ResponseWriter *http.Request
func (*Rivet) MapTo ¶
* MapTo 以 t 为 key 把变量 v 关联到 context. 相同 t 值只保留一个. 调用者也许会自己定义一个值, 注意不要和真实类型标识冲突. 否则会导致不可预计的错误.
func (*Rivet) Next ¶
func (c *Rivet) Next()
* Next 遍历 Handlers 保存的 handler, 通过 Invoke 调用. 如果 ResponseWriter.Written() 为 true, 终止遍历. Next 最后会调用 ResponseWriter.Flush(), 清空 handler.
func (*Rivet) ParamsReceiver ¶
* ParamsReceiver 逐个接收从 URL.Path 中提取的参数. 此方法把参数值 val 保存在 Params 字段中. 把原始参数值 text 保存在 PathParams 字段中.
func (*Rivet) Response ¶
func (c *Rivet) Response() http.ResponseWriter
Response 返回生成 Context 的 http.ResponseWriter
type Riveter ¶
type Riveter func(http.ResponseWriter, *http.Request) Context
* Riveter 是 Context 生成器.
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
* Router 管理路由.
func NewRouter ¶
* NewRouter 新建 *Router, 并设置 NotFound Handler 为 http.NotFound. 参数:
rivet 用于生成 Context 实例, 如果为 nil 使用 NewContext 创建.
func (*Router) Handle ¶
* Handle 为 HTTP method request 设置路由的通用形式. 如果 method, pattern 对应的路由重复, 直接返回对应的节点. 否则添加新节点. 参数:
method "*" 等效 Any. 其它值不做处理, 直接和 http.Request.Method 比较. pattern 为空等效 NotFound 方法.
事实上 Router 不限制 method 的名称, 可随意定义.
func (*Router) Match ¶
func (r *Router) Match(method, urlPath string, rec ParamsReceiver, rw http.ResponseWriter, req *http.Request) Node
* Match 匹配路由节点. 如果匹配失败, 返回 NotFound 节点. 参数:
method Request.Method, 确定对应的 Root Trie. urlPath Request.URL.Path, 传递给 Trie. rec URL.Path 参数接收器, 传递给 Trie. rw http 响应, 传递给 Trie. req http 请求, 传递给 Trie.
func (*Router) NodeBuilder ¶
func (r *Router) NodeBuilder(nb NodeBuilder)
* 设置 NodeBuilder, 默认使用 NewNode.
type Scene ¶
type Scene struct {
*Rivet
}
* Scene 支持 Handler 参数类型为 PathParams 的风格. PathParams 省去了 URL.Path 参数转换的结果, 如果 Filter 中没有用到类型转换, 使用 Scene 是合适的.
func (Scene) ParamsReceiver ¶
* ParamsReceiver 逐个接收从 URL.Path 中提取的参数. 此方法把参数值 text 保存在 PathParams 字段中.
type Trie ¶
type Trie struct {
// contains filtered or unexported fields
}
* Trie 管理路由 patterns. Trie 不直接管理路由 Handler, 由使用者通过 SetId 进行组织管理. id 为 0 的节点保留给内部算法使用, 所以 0 值表示非路由节点.
请使用 NewRootTrie 获得根节点. 使用 Print 方法有助于了 Trie 的结构和算法.
func (*Trie) Add ¶
func (t *Trie) Add(path string, newFilter FilterBuilder) *Trie
* Add 添加路由 pattern 返回相应的节点.
参数:
path 路由 pattern. 必须以 "/" 开头. newFilter Filter 生成器, 如果为 nil, 用函数 NewFilter 代替.
返回:
返回对应 path 的节点, 如果 path 重复, 返回原有节点.
注意: 因为 Add 允许重复, 调用者应该先判断 GetId() 是否为 0, 再确定是否要 SetId.
func (*Trie) Match ¶
func (t *Trie) Match(path string, rec ParamsReceiver, rw http.ResponseWriter, req *http.Request) *Trie
* Match 匹配 URL.Path, 返回匹配到的节点. 参数:
path 待匹配的 URL.Path rec 指定参数接收器, 如果为 nil 表示丢弃参数. rw, req 供 Filter 使用, 如果 Filter 不需要的话, 可以为 nil
返回:
成功返回对应的节点, 该节点 GetId() 一定不为 0. 失败返回 nil.