rivet

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 26, 2014 License: BSD-3-Clause Imports: 8 Imported by: 34

README

Rivet

Go Walker status

专注路由. 简洁, 贪心匹配, 支持注入, 可定制, 深度解耦的 http 路由管理器.

examples 目录中有几个例子, 方便您了解 Rivet.

任何问题, 分享可至 issues , wiki

这里有个路由专项评测 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 依赖上述所有. 了解函数类型 NodeBuilderRiveter 定制自己的 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 定义了两种类型,ParamsPathParams, 事实上如果中间件用到这两个类型, 您也无需 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

Constants

This section is empty.

Variables

View Source
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.

func NewScene

func NewScene(res http.ResponseWriter, req *http.Request) Context

NewScene 新建 *Scene 实例实现的 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.

func NewFilter

func NewFilter(class string, args ...string) Filter

* NewFilter 是缺省的 FilterBuilder. 通过调用 FilterClass 中与 class 对应的 FilterBuilder 生成一个 Filter. 如果相应的 FilterBuilder 或生成的 Filter 为 nil, 发生 panic.

type FilterBuilder

type FilterBuilder func(class string, args ...string) Filter

* FilterBuilder 是 Filter 生成器. 参数:

class 为 Filter 类型名.
args  为参数.

type FilterFunc

type FilterFunc func(text string) (string, interface{}, bool)

* FilterFunc 包装函数符合 Filter 接口.

func (FilterFunc) Filter

func (filter FilterFunc) Filter(text string,
	_ http.ResponseWriter, _ *http.Request) (string, interface{}, bool)

type Handler

type Handler interface{}

* Handler 被设计成 interface{} 的别名, 接收任意值.

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 方法.

func NewNode

func NewNode(id int) Node

* NewNode 返回内建的 Node 实例. 参数:

id  识别号码

type NodeBuilder

type NodeBuilder func(id int) Node

* NodeBuilder 是 Node 生成器. 参数:

id  识别号码

type Params

type Params map[string]interface{}

* Params 存储从 URL.Path 中提取的参数. 值可能经过 Filter 转换.

func (Params) Get

func (p Params) Get(key string) (s string)

Get 返回 key 所对应值的字符串形式

func (Params) ParamsReceiver

func (p Params) ParamsReceiver(key, _ string, val interface{})

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

type PathParams map[string]string

* PathParams 存储从 URL.Path 中提取的原始参数. 与 Scene/NewScene 配套使用.

func (PathParams) Get

func (p PathParams) Get(key string) string

Get 返回 key 对应值

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

func (r *Rivet) Get(t uint) (v interface{})

* Get 以类型标识 t 为 key, 获取关联到 context 的变量. 如果未找到, 通常返回 nil, 特别的:

如果 t 代表 map[string]interface{}, 用 Params 标识再试一次.
如果 t 代表 map[string]string, 用 PathParams 标识再试一次.

这样做, 如果不用 Map 功能, 所写的 Handler 就不需要 import rivet.

func (*Rivet) GetParams

func (c *Rivet) GetParams() Params

GetParams 返回路由匹配时从 URL.Path 中提取的参数

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

func (c *Rivet) Invoke(handler interface{}) bool

* 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

func (r *Rivet) MapTo(v interface{}, t uint)

* 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

func (c *Rivet) ParamsReceiver(name, text string, val interface{})

* ParamsReceiver 逐个接收从 URL.Path 中提取的参数. 此方法把参数值 val 保存在 Params 字段中. 把原始参数值 text 保存在 PathParams 字段中.

func (*Rivet) Request

func (c *Rivet) Request() *http.Request

Request 返回生成 Context 的 *http.Request

func (*Rivet) Response

func (c *Rivet) Response() http.ResponseWriter

Response 返回生成 Context 的 http.ResponseWriter

func (*Rivet) WriteString

func (c *Rivet) WriteString(data string) (int, error)

WriteString 向 http.ResponseWriter 写入 data.

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

func NewRouter(rivet Riveter) *Router

* NewRouter 新建 *Router, 并设置 NotFound Handler 为 http.NotFound. 参数:

rivet 用于生成 Context 实例, 如果为 nil 使用 NewContext 创建.

func (*Router) Any

func (r *Router) Any(pattern string, handler ...interface{}) Node

Any 为任意 HTTP method request 设置路由.

func (*Router) Delete

func (r *Router) Delete(pattern string, handler ...interface{}) Node

Delete 为 HTTP DELETE request 设置路由

func (*Router) Get

func (r *Router) Get(pattern string, handler ...interface{}) Node

Get 为 HTTP GET request 设置路由

func (*Router) Handle

func (r *Router) Handle(method string, pattern string, h ...interface{}) Node

* Handle 为 HTTP method request 设置路由的通用形式. 如果 method, pattern 对应的路由重复, 直接返回对应的节点. 否则添加新节点. 参数:

method  "*" 等效 Any. 其它值不做处理, 直接和 http.Request.Method 比较.
pattern 为空等效 NotFound 方法.

事实上 Router 不限制 method 的名称, 可随意定义.

func (*Router) Head

func (r *Router) Head(pattern string, handler ...interface{}) Node

Head 为 HTTP HEAD request 设置路由

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.

func (*Router) NotFound

func (r *Router) NotFound(h ...interface{}) Node

NotFound 设置匹配失败路由, 此路由只有一个. Node.Id() 固定为 0.

func (*Router) Options

func (r *Router) Options(pattern string, handler ...interface{}) Node

Options 为 HTTP OPTIONS request 设置路由

func (*Router) Patch

func (r *Router) Patch(pattern string, handler ...interface{}) Node

Patch 为 HTTP PATCH request 设置路由

func (*Router) Post

func (r *Router) Post(pattern string, handler ...interface{}) Node

Post 为 HTTP POST request 设置路由

func (*Router) Put

func (r *Router) Put(pattern string, handler ...interface{}) Node

Put 为 HTTP PUT request 设置路由

func (*Router) RootTrie

func (r *Router) RootTrie(method string) *Trie

* RootTrie 返回 method 对应的 *Trie 根节点.

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request)

http.Handler

type Scene

type Scene struct {
	*Rivet
}

* Scene 支持 Handler 参数类型为 PathParams 的风格. PathParams 省去了 URL.Path 参数转换的结果, 如果 Filter 中没有用到类型转换, 使用 Scene 是合适的.

func (Scene) ParamsReceiver

func (c Scene) ParamsReceiver(name, text string, _ interface{})

* 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 NewRootTrie

func NewRootTrie() *Trie

* NewRootTrie 返回新的根节点 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) Filter

func (p Trie) Filter(text string,
	rw http.ResponseWriter, req *http.Request) (string, interface{}, bool)

func (*Trie) GetId

func (t *Trie) GetId() int

* GetId 返回节点 id.

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.

func (*Trie) Print

func (t *Trie) Print(prefix string) (count int)

* Print 输出 Trie 结构信息.

参数:

prefix 行前缀

返回:

节点下所有路由的数量.

输出格式:

id max[RPG*?] 缩进'path'.slash [indices].len names

其中:

max     此分支内最大斜线个数
R       表示路由, GetId() 非 0.
P       表示模式节点
G       表示模式分组
*       表示节点或下属节点含有 "/**" 模式.
?       尾斜线匹配. "/?"
.slash  节点 path 分段中的斜线数量
indices 子节点首字符组成的索引.
.len    子节点数量
names   是参数名 map.

func (*Trie) SetId

func (t *Trie) SetId(id int)

* SetId 设置节点 id. 设置条件为:

id != 0 && t != nil && t.id == 0

其中:

t.id == 0 为内部管理节点

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL