Skip to content

Commit

Permalink
docs: 更新文档内容
Browse files Browse the repository at this point in the history
  • Loading branch information
caixw committed May 25, 2024
1 parent d528b3c commit bc9cfbd
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 61 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ r.Do()
### 拦截器

正常情况下,`/posts/{id:\d+}` 或是 `/posts/{id:[0-9]+}` 会被当作正则表达式处理,
但是正则表达式的性能并不是很好,这个时候我们可以通过在 `NewRouter` 传递 `Interceptor` 进行拦截:
但是正则表达式的性能并不是很好,这个时候我们可以通过在 `NewRouter` 传递 `WithInterceptor` 进行拦截:

```go
import "github.com/issue9/mux/v9"
Expand All @@ -159,7 +159,7 @@ func digit(path string) bool {
}

// 路由中的 \d+ 和 [0-9]+ 均采用 digit 函数进行处理,不再是正则表达式。
interceptor := mux.Interceptor(digit, "\\d+", "[0-9]+")
interceptor := mux.WithInterceptor(digit, "\\d+", "[0-9]+")
r := mux.NewRouter(..., interceptor)
```

Expand Down
22 changes: 11 additions & 11 deletions header/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ const (
From = "From" // 发起此请求的用户的邮件地址 From: user@example.com
Host = "Host" // 服务器的域名(用于虚拟主机),以及服务器所监听的传输控制协议端口号。如果所请求的端口是对应的服务的标准端口,则端口号可被省略。自超文件传输协议版本1.1(HTTP/1.1)开始便是必需字段。 Host: zh.wikipedia.org:80 Host: zh.wikipedia.org
IfMatch = "If-Match" // 仅当客户端提供的实体与服务器上对应的实体相匹配时,才进行对应的操作。主要作用时,用作像 PUT 这样的方法中,仅当从用户上次更新某个资源以来,该资源未被修改的情况下,才更新该资源。 If-Match: "737060cd8c284d8af7ad3082f209582d"
IfModifiedSince = "If-Modified-Since" // 允许在对应的内容未被修改的情况下返回304未修改( 304 Not Modified ) If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT
IfNoneMatch = "If-None-Match" // 允许在对应的内容未被修改的情况下返回304未修改( 304 Not Modified ),参考 超文本传输协议 的实体标记 If-None-Match: "737060cd8c284d8af7ad3082f209582d"
IfModifiedSince = "If-Modified-Since" // 允许在对应的内容未被修改的情况下返回 304 未修改( 304 Not Modified ) If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT
IfNoneMatch = "If-None-Match" // 允许在对应的内容未被修改的情况下返回 304 未修改( 304 Not Modified ),参考 超文本传输协议 的实体标记 If-None-Match: "737060cd8c284d8af7ad3082f209582d"
IfRange = "If-Range" // 如果该实体未被修改过,则向我发送我所缺少的那一个或多个部分;否则,发送整个新的实体 If-Range: "737060cd8c284d8af7ad3082f209582d"
IfUnmodifiedSince = "If-Unmodified-Since" // 仅当该实体自某个特定时间已来未被修改的情况下,才发送回应。 If-Unmodified-Since: Sat, 29 Oct 1994 19:43:31 GMT
MaxForwards = "Max-Forwards" // 限制该消息可被代理及网关转发的次数。 Max-Forwards: 10
Expand Down Expand Up @@ -59,7 +59,7 @@ const (
AcceptPatch = "Accept-Patch" // 指定服务器支持的文件格式类型。 Accept-Patch: text/example;charset=utf-8
AcceptRanges = "Accept-Ranges" // 这个服务器支持哪些种类的部分内容范围 Accept-Ranges: bytes
Age = "Age" // 这个对象在代理缓存中存在的时间,以秒为单位 Age: 12
Allow = "Allow" // 对于特定资源有效的动作。针对HTTP/405这一错误代码而使用 Allow: GET, HEAD
Allow = "Allow" // 对于特定资源有效的动作。针对 HTTP/405 这一错误代码而使用 Allow: GET, HEAD
ContentDisposition = "Content-Disposition" // 一个可以让客户端下载文件并建议文件名的头部。文件名需要用双引号包裹。 Content-Disposition: attachment; filename="fname.ext"
ContentEncoding = "Content-Encoding" // 在数据上使用的编码类型。参考 超文本传输协议压缩 。 Content-Encoding: gzip
ContentLanguage = "Content-Language" // 内容所使用的语言 Content-Language: da
Expand Down Expand Up @@ -125,15 +125,15 @@ const (
//
// deny:该页面不允许在 frame 中展示,即使是同域名内。
// sameorigin:该页面允许同域名内在 frame 中展示。
// allow-from uri:该页面允许在指定uri的 frame 中展示。
// allow-from uri:该页面允许在指定 uri 的 frame 中展示。
// allowall:允许任意位置的frame显示,非标准值。
// X-Frame-Options: deny 过时的
XFrameOptions = "X-Frame-Options"

// 非标准报头

XRequestedWith = "X-Requested-With" // 主要用于标识 Ajax 及可扩展标记语言 请求。大部分的JavaScript框架会发送这个字段,且将其值设置为 XMLHttpRequest X-Requested-With: XMLHttpRequest
DNT = "DNT" // 请求某个网页应用程序停止跟踪某个用户。在火狐浏览器中,相当于X-Do-Not-Track协议头字段(自 Firefox/4.0 Beta 11 版开始支持)。Safari 和 Internet Explorer 9 也支持这个字段。2011年3月7日,草案提交IETF。 万维网协会的跟踪保护工作组正在就此制作一项规范。 DNT: 1 (DNT启用) DNT: 0 (DNT被禁用)
XRequestedWith = "X-Requested-With" // 主要用于标识 Ajax 及可扩展标记语言 请求。大部分的 JavaScript 框架会发送这个字段,且将其值设置为 XMLHttpRequest X-Requested-With: XMLHttpRequest
DNT = "DNT" // 请求某个网页应用程序停止跟踪某个用户。在火狐浏览器中,相当于 X-Do-Not-Track 协议头字段(自 Firefox/4.0 Beta 11 版开始支持)。Safari 和 Internet Explorer 9 也支持这个字段。2011年3月7日,草案提交IETF。 万维网协会的跟踪保护工作组正在就此制作一项规范。 DNT: 1 (DNT启用) DNT: 0 (DNT被禁用)
XForwardedFor = "X-Forwarded-For" // 一个事实标准 ,用于标识某个通过超文本传输协议代理或负载均衡连接到某个网页服务器的客户端的原始互联网地址 X-Forwarded-For: client1, proxy1, proxy2 X-Forwarded-For: 129.78.138.66, 129.78.64.103
XForwardedHost = "X-Forwarded-Host" // 一个事实标准 ,用于识别客户端原本发出的 Host 请求头部。 X-Forwarded-Host: zh.wikipedia.org:80 X-Forwarded-Host: zh.wikipedia.org
XForwardedProto = "X-Forwarded-Proto" // 一个事实标准,用于标识某个超文本传输协议请求最初所使用的协议。 X-Forwarded-Proto: https
Expand All @@ -142,9 +142,9 @@ const (
XForwardedSSL = "X-Forwarded-Ssl"
XUrlScheme = "X-Url-Scheme"
FrontEndHttps = "Front-End-Https" // 被微软的服务器和负载均衡器所使用的非标准头部字段。 Front-End-Https: on
XHttpMethodOverride = "X-Http-Method-Override" // 请求某个网页应用程序使用该协议头字段中指定的方法(一般是PUT或DELETE)来覆盖掉在请求中所指定的方法(一般是POST)。当某个浏览器或防火墙阻止直接发送PUT 或DELETE 方法时(注意,这可能是因为软件中的某个漏洞,因而需要修复,也可能是因为某个配置选项就是如此要求的,因而不应当设法绕过),可使用这种方式。 X-HTTP-Method-Override: DELETE
XATTDeviceId = "X-ATT-Deviceid" // 使服务器更容易解读AT&T设备User-Agent字段中常见的设备型号、固件信息。 X-Att-Deviceid: GT-P7320/P7320XXLPG
XWapProfile = "X-Wap-Profile" // 链接到互联网上的一个XML文件,其完整、仔细地描述了正在连接的设备。右侧以为AT&T Samsung Galaxy S2提供的XML文件为例。 x-wap-profile: http://wap.samsungmobile.com/uaprof/SGH-I777.xml
XHttpMethodOverride = "X-Http-Method-Override" // 请求某个网页应用程序使用该协议头字段中指定的方法(一般是PUT或DELETE)来覆盖掉在请求中所指定的方法(一般是 POST)。当某个浏览器或防火墙阻止直接发送PUT 或DELETE 方法时(注意,这可能是因为软件中的某个漏洞,因而需要修复,也可能是因为某个配置选项就是如此要求的,因而不应当设法绕过),可使用这种方式。 X-HTTP-Method-Override: DELETE
XATTDeviceId = "X-ATT-Deviceid" // 使服务器更容易解读 AT&T 设备 User-Agent 字段中常见的设备型号、固件信息。 X-Att-Deviceid: GT-P7320/P7320XXLPG
XWapProfile = "X-Wap-Profile" // 链接到互联网上的一个 XML 文件,其完整、仔细地描述了正在连接的设备。右侧以为 AT&T Samsung Galaxy S2 提供的 XML 文件为例。 x-wap-profile: http://wap.samsungmobile.com/uaprof/SGH-I777.xml
ProxyConnection = "Proxy-Connection" // 该字段源于早期超文本传输协议版本实现中的错误。与标准的连接(Connection)字段的功能完全相同。 Proxy-Connection: keep-alive
XCsrfToken = "X-Csrf-Token" // 用于防止 跨站请求伪造。 辅助用的头部有 X-CSRFToken 或 X-XSRF-TOKEN X-Csrf-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql
XXSSProtection = "X-XSS-Protection" // 跨站脚本攻击 (XSS)过滤器 X-XSS-Protection: 1; mode=block
Expand All @@ -154,12 +154,12 @@ const (
XContentSecurityPolicy = "X-Content-Security-Policy"
XWebKitCSP = "X-WebKit-CSP"

XContentTypeOptions = "X-Content-Type-Options" // 唯一允许的数值为"nosniff",防止 Internet Explorer 对文件进行MIME类型嗅探。这也对 Google Chrome 下载扩展时适用。X-Content-Type-Options: nosniff
XContentTypeOptions = "X-Content-Type-Options" // 唯一允许的数值为 "nosniff",防止 Internet Explorer 对文件进行MIME类型嗅探。这也对 Google Chrome 下载扩展时适用。X-Content-Type-Options: nosniff
XPoweredBy = "X-Powered-By" // 表明用于支持当前网页应用程序的技术(例如:PHP)(版本号细节通常放置在 X-Runtime 或 X-Version 中) X-Powered-By: PHP/5.4.0
XUACompatible = "X-UA-Compatible" // 推荐指定的渲染引擎(通常是向后兼容模式)来显示内容。也用于激活 Internet Explorer 中的 Chrome Frame。 X-UA-Compatible: IE=EmulateIE7 X-UA-Compatible: IE=edge X-UA-Compatible: Chrome=1
XContentDuration = "X-Content-Duration" // 指出音视频的长度,单位为秒。只受 Gecko 内核浏览器支持。 X-Content-Duration: 42.666
FeaturePolicy = "Feature-Policy" // 管控特定应用程序接口 Feature-Policy: vibrate 'none'; geolocation 'none'
PermissionsPolicy = "Permissions-Policy" // 管控特定应用程序接口为W3C标准 替代Feature-Policy Permissions-Policy: microphone=(),geolocation=(),camera=()
PermissionsPolicy = "Permissions-Policy" // 管控特定应用程序接口为 W3C 标准 替代 Feature-Policy Permissions-Policy: microphone=(),geolocation=(),camera=()
XPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies" // Flash 的跨网站攻击防御 X-Permitted-Cross-Domain-Policies: none
ReferrerPolicy = "Referrer-Policy" // 保护信息泄漏 //Referrer-Policy: origin-when-cross-origin
ExpectCT = "Expect-CT" // 防止欺骗 SSL,单位为秒 Expect-CT: max-age=31536000, enforce
Expand Down
38 changes: 25 additions & 13 deletions internal/syntax/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@ func BenchmarkSegment_Match_Named(b *testing.B) {
seg, err := i.NewSegment("{id}/author")
a.NotError(err).NotNil(seg)

ctx := types.NewContext()
var ok bool
for range b.N {
p := types.NewContext()
p.Path = "100000/author"
a.True(seg.Match(p)).Equal(p.MustString("id", "not-exits"), "100000")
ctx.Reset()
ctx.Path = "100000/author"
ok = seg.Match(ctx)
}
a.True(ok).Equal(ctx.MustString("id", "not-exits"), "100000") // 相同的数据,仅判断最后一次数据
}

func BenchmarkSegment_Match_Named_withMatcher(b *testing.B) {
func BenchmarkSegment_Match_Named_withInterceptors(b *testing.B) {
a := assert.New(b, false)
i := NewInterceptors()
i.Add(MatchDigit, "digit")
Expand All @@ -36,11 +39,14 @@ func BenchmarkSegment_Match_Named_withMatcher(b *testing.B) {
seg, err := i.NewSegment("{id:digit}/author")
a.NotError(err).NotNil(seg)

ctx := types.NewContext()
var ok bool
for range b.N {
p := types.NewContext()
p.Path = "100000/author"
a.True(seg.Match(p)).Equal(p.MustString("id", "not-exits"), "100000")
ctx.Reset()
ctx.Path = "100000/author"
ok = seg.Match(ctx)
}
a.True(ok).Equal(ctx.MustString("id", "not-exits"), "100000")
}

func BenchmarkSegment_Match_String(b *testing.B) {
Expand All @@ -51,11 +57,14 @@ func BenchmarkSegment_Match_String(b *testing.B) {
seg, err := i.NewSegment("/posts/author")
a.NotError(err).NotNil(seg)

ctx := types.NewContext()
var ok bool
for range b.N {
p := types.NewContext()
p.Path = "/posts/author"
a.True(seg.Match(p)).Zero(p.Count())
ctx.Reset()
ctx.Path = "/posts/author"
ok = seg.Match(ctx)
}
a.True(ok).Zero(ctx.Count())
}

func BenchmarkSegment_Match_Regexp(b *testing.B) {
Expand All @@ -66,9 +75,12 @@ func BenchmarkSegment_Match_Regexp(b *testing.B) {
seg, err := i.NewSegment("{id:\\d+}/author")
a.NotError(err).NotNil(seg)

ctx := types.NewContext()
var ok bool
for range b.N {
p := types.NewContext()
p.Path = "1/author"
a.True(seg.Match(p)).Equal(p.MustString("id", "not-exists"), "1")
ctx.Reset()
ctx.Path = "1/author"
ok = seg.Match(ctx)
}
a.True(ok).Equal(ctx.MustString("id", "not-exists"), "1")
}
34 changes: 17 additions & 17 deletions internal/syntax/segment.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type Segment struct {
matcher InterceptorFunc
}

// NewSegment 声明新的 Segment 变量
// NewSegment 声明新的 [Segment] 变量
//
// 如果为非字符串类型的内容,应该是以 { 符号开头才是合法的。
func (i *Interceptors) NewSegment(val string) (*Segment, error) {
Expand Down Expand Up @@ -195,33 +195,33 @@ func (seg *Segment) Valid(pattern string) bool {
// Match 路径是否与当前节点匹配
//
// 如果正确匹配,则将剩余的未匹配字符串写入到 p.Path 并返回 true。
func (seg *Segment) Match(p *types.Context) bool {
func (seg *Segment) Match(ctx *types.Context) bool {
switch seg.Type {
case String:
if strings.HasPrefix(p.Path, seg.Value) {
p.Path = p.Path[len(seg.Value):]
if strings.HasPrefix(ctx.Path, seg.Value) {
ctx.Path = ctx.Path[len(seg.Value):]
return true
}
case Interceptor, Named:
if seg.Endpoint {
if seg.matcher(p.Path) {
if seg.matcher(ctx.Path) {
if !seg.ignoreName {
p.Set(seg.Name, p.Path)
ctx.Set(seg.Name, ctx.Path)
}
p.Path = p.Path[:0]
ctx.Path = ctx.Path[:0]
return true
}
} else if index := strings.Index(p.Path, seg.Suffix); index >= 0 {
} else if index := strings.Index(ctx.Path, seg.Suffix); index >= 0 {
for {
if val := p.Path[:index]; seg.matcher(val) {
if val := ctx.Path[:index]; seg.matcher(val) {
if !seg.ignoreName {
p.Set(seg.Name, val)
ctx.Set(seg.Name, val)
}
p.Path = p.Path[index+len(seg.Suffix):]
ctx.Path = ctx.Path[index+len(seg.Suffix):]
return true
}

i := strings.Index(p.Path[index+len(seg.Suffix):], seg.Suffix)
i := strings.Index(ctx.Path[index+len(seg.Suffix):], seg.Suffix)
if i < 0 {
return false
}
Expand All @@ -230,13 +230,13 @@ func (seg *Segment) Match(p *types.Context) bool {
}
case Regexp:
if seg.ignoreName {
if loc := seg.expr.FindStringIndex(p.Path); loc != nil && loc[0] == 0 {
p.Path = p.Path[loc[1]:]
if loc := seg.expr.FindStringIndex(ctx.Path); loc != nil && loc[0] == 0 {
ctx.Path = ctx.Path[loc[1]:]
return true
}
} else if loc := seg.expr.FindStringSubmatchIndex(p.Path); loc != nil && loc[0] == 0 {
p.Set(seg.Name, p.Path[:loc[3]]) // 只有 ignoreName == false,才会有捕获的值
p.Path = p.Path[loc[1]:]
} else if loc := seg.expr.FindStringSubmatchIndex(ctx.Path); loc != nil && loc[0] == 0 {
ctx.Set(seg.Name, ctx.Path[:loc[3]]) // 只有 ignoreName == false,才会有捕获的值
ctx.Path = ctx.Path[loc[1]:]
return true
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/syntax/syntax.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (i *Interceptors) URL(buf *errwrap.StringBuilder, pattern string, ps map[st
return nil
}

// Split 将字符串解析成 Segment 数组
// Split 将字符串解析成 [Segment] 数组
//
// 以 { 为分界线进行分割。比如
//
Expand Down
18 changes: 10 additions & 8 deletions internal/tree/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ func BenchmarkTree_Handler(b *testing.B) {
8: "/posts/2.html/2/author",
}

for i := 0; i < b.N; i++ {
ctx := types.NewContext()
for i := range b.N {
ctx.Reset()
index := i % len(paths)
p := types.NewContext()
p.Path = paths[index]
node, h, ok := tree.Handler(p, http.MethodGet)
ctx.Path = paths[index]
node, h, ok := tree.Handler(ctx, http.MethodGet)
a.True(ok).
NotNil(node).
NotNil(h)
Expand Down Expand Up @@ -79,11 +80,12 @@ func BenchmarkTree_ServeHTTP(b *testing.B) {
7: "/posts/2.html/2/author",
}

for i := 0; i < b.N; i++ {
ctx := types.NewContext()
for i := range b.N {
ctx.Reset()
index := i % len(paths)
p := types.NewContext()
p.Path = paths[index]
node, h, ok := tree.Handler(p, http.MethodGet)
ctx.Path = paths[index]
node, h, ok := tree.Handler(ctx, http.MethodGet)
a.True(ok).
NotNil(node).
NotNil(h)
Expand Down
6 changes: 3 additions & 3 deletions internal/tree/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

const (
indexesSize = 5 // Node.children 的数量只有达到此值时,才会为其建立 indexes 索引表。
handlersSize = 7 // Node.handlers 的初始容量
handlersSize = 3 // Node.handlers 的初始容量
)

type node[T any] struct {
Expand All @@ -26,7 +26,7 @@ type node[T any] struct {
methodIndex int // 在 methodIndexes 中的索引值
handlers map[string]T

// 保存着 *Node 实例在 children 中的下标。
// 保存着 node 实例在 children 中的下标。
//
// 所有节点类型为字符串的子节点,其首字符必定是不同的(相同的都提升到父节点中),
// 根据此特性,可以将所有字符串类型的首字符做个索引,这样字符串类型节点的比较,
Expand Down Expand Up @@ -192,7 +192,7 @@ func (n *node[T]) matchChildren(ctx *types.Context) *node[T] {

path := ctx.Path

if !child.segment.Match(ctx) {
if !child.segment.Match(ctx) { // 这会修改 ctx.Path 的值
goto LOOP
}
if nn := child.matchChildren(ctx); nn != nil {
Expand Down
5 changes: 1 addition & 4 deletions internal/tree/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@ import (

"github.com/issue9/mux/v9/header"
"github.com/issue9/mux/v9/internal/syntax"
"github.com/issue9/mux/v9/internal/trace"
"github.com/issue9/mux/v9/types"
)

func TestTrace(w http.ResponseWriter, r *http.Request) { trace.Trace(w, r, true) }

// NewTestTree 返回以 [http.Handler] 作为参数实例化的 Tree
// NewTestTree 返回以 [http.Handler] 作为参数实例化的 [Tree]
func NewTestTree(a *assert.Assertion, lock bool, trace http.Handler, i *syntax.Interceptors) *Tree[http.Handler] {
t := New("", lock, i, http.NotFoundHandler(), trace, BuildTestNodeHandlerFunc(http.StatusMethodNotAllowed), BuildTestNodeHandlerFunc(http.StatusOK))
a.NotNil(t)
Expand Down
Loading

0 comments on commit bc9cfbd

Please sign in to comment.