-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
550abdb
commit 99eab04
Showing
5 changed files
with
249 additions
and
190 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Copyright 2024 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package web | ||
|
||
// Combo represents a tiny group routes with same pattern | ||
type Combo struct { | ||
r *Router | ||
pattern string | ||
h []any | ||
} | ||
|
||
// Get delegates Get method | ||
func (c *Combo) Get(h ...any) *Combo { | ||
c.r.Get(c.pattern, append(c.h, h...)...) | ||
return c | ||
} | ||
|
||
// Post delegates Post method | ||
func (c *Combo) Post(h ...any) *Combo { | ||
c.r.Post(c.pattern, append(c.h, h...)...) | ||
return c | ||
} | ||
|
||
// Delete delegates Delete method | ||
func (c *Combo) Delete(h ...any) *Combo { | ||
c.r.Delete(c.pattern, append(c.h, h...)...) | ||
return c | ||
} | ||
|
||
// Put delegates Put method | ||
func (c *Combo) Put(h ...any) *Combo { | ||
c.r.Put(c.pattern, append(c.h, h...)...) | ||
return c | ||
} | ||
|
||
// Patch delegates Patch method | ||
func (c *Combo) Patch(h ...any) *Combo { | ||
c.r.Patch(c.pattern, append(c.h, h...)...) | ||
return c | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
// Copyright 2024 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package web | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"regexp" | ||
"strings" | ||
|
||
"code.gitea.io/gitea/modules/container" | ||
"code.gitea.io/gitea/modules/util" | ||
|
||
"github.com/go-chi/chi/v5" | ||
) | ||
|
||
type RouterPathGroup struct { | ||
r *Router | ||
pathParam string | ||
processors []*routerPathMatcher | ||
} | ||
|
||
func (g *RouterPathGroup) ServeHTTP(resp http.ResponseWriter, req *http.Request) { | ||
chiCtx := chi.RouteContext(req.Context()) | ||
path := chiCtx.URLParam(g.pathParam) | ||
for _, p := range g.processors { | ||
if p.matchPath(chiCtx, path) { | ||
handler := p.handlerFunc | ||
for i := len(p.middlewares) - 1; i >= 0; i-- { | ||
handler = p.middlewares[i](handler).ServeHTTP | ||
} | ||
handler(resp, req) | ||
return | ||
} | ||
} | ||
g.r.chiRouter.NotFoundHandler().ServeHTTP(resp, req) | ||
} | ||
|
||
func (g *RouterPathGroup) MatchPath(methods, pattern string, h ...any) { | ||
g.processors = append(g.processors, newRouterPathMatcher(methods, pattern, h...)) | ||
} | ||
|
||
type routerPathParam struct { | ||
name string | ||
captureGroup int | ||
} | ||
|
||
type routerPathMatcher struct { | ||
methods container.Set[string] | ||
re *regexp.Regexp | ||
params []routerPathParam | ||
middlewares []func(http.Handler) http.Handler | ||
handlerFunc http.HandlerFunc | ||
} | ||
|
||
func (p *routerPathMatcher) matchPath(chiCtx *chi.Context, path string) bool { | ||
if !p.methods.Contains(chiCtx.RouteMethod) { | ||
return false | ||
} | ||
if !strings.HasPrefix(path, "/") { | ||
path = "/" + path | ||
} | ||
pathMatches := p.re.FindStringSubmatchIndex(path) // Golang regexp match pairs [start, end, start, end, ...] | ||
if pathMatches == nil { | ||
return false | ||
} | ||
var paramMatches [][]int | ||
for i := 2; i < len(pathMatches); { | ||
paramMatches = append(paramMatches, []int{pathMatches[i], pathMatches[i+1]}) | ||
pmIdx := len(paramMatches) - 1 | ||
end := pathMatches[i+1] | ||
i += 2 | ||
for ; i < len(pathMatches); i += 2 { | ||
if pathMatches[i] >= end { | ||
break | ||
} | ||
paramMatches[pmIdx] = append(paramMatches[pmIdx], pathMatches[i], pathMatches[i+1]) | ||
} | ||
} | ||
for i, pm := range paramMatches { | ||
groupIdx := p.params[i].captureGroup * 2 | ||
chiCtx.URLParams.Add(p.params[i].name, path[pm[groupIdx]:pm[groupIdx+1]]) | ||
} | ||
return true | ||
} | ||
|
||
func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher { | ||
middlewares, handlerFunc := wrapMiddlewareAndHandler(nil, h) | ||
p := &routerPathMatcher{methods: make(container.Set[string]), middlewares: middlewares, handlerFunc: handlerFunc} | ||
for _, method := range strings.Split(methods, ",") { | ||
p.methods.Add(strings.TrimSpace(method)) | ||
} | ||
re := []byte{'^'} | ||
lastEnd := 0 | ||
for lastEnd < len(pattern) { | ||
start := strings.IndexByte(pattern[lastEnd:], '<') | ||
if start == -1 { | ||
re = append(re, pattern[lastEnd:]...) | ||
break | ||
} | ||
end := strings.IndexByte(pattern[lastEnd+start:], '>') | ||
if end == -1 { | ||
panic(fmt.Sprintf("invalid pattern: %s", pattern)) | ||
} | ||
re = append(re, pattern[lastEnd:lastEnd+start]...) | ||
partName, partExp, _ := strings.Cut(pattern[lastEnd+start+1:lastEnd+start+end], ":") | ||
lastEnd += start + end + 1 | ||
|
||
// TODO: it could support to specify a "capture group" for the name, for example: "/<name[2]:(\d)-(\d)>" | ||
// it is not used so no need to implement it now | ||
param := routerPathParam{} | ||
if partExp == "*" { | ||
re = append(re, "(.*?)/?"...) | ||
if lastEnd < len(pattern) && pattern[lastEnd] == '/' { | ||
lastEnd++ // the "*" pattern is able to handle the last slash, so skip it | ||
} | ||
} else { | ||
partExp = util.IfZero(partExp, "[^/]+") | ||
re = append(re, '(') | ||
re = append(re, partExp...) | ||
re = append(re, ')') | ||
} | ||
param.name = partName | ||
p.params = append(p.params, param) | ||
} | ||
re = append(re, '$') | ||
reStr := string(re) | ||
p.re = regexp.MustCompile(reStr) | ||
return p | ||
} |
Oops, something went wrong.