-
-
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.
Refactor arch route handlers (#32993)
- Loading branch information
1 parent
254314b
commit e435b19
Showing
5 changed files
with
260 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,135 @@ | ||
// 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 | ||
matchers []*routerPathMatcher | ||
} | ||
|
||
func (g *RouterPathGroup) ServeHTTP(resp http.ResponseWriter, req *http.Request) { | ||
chiCtx := chi.RouteContext(req.Context()) | ||
path := chiCtx.URLParam(g.pathParam) | ||
for _, m := range g.matchers { | ||
if m.matchPath(chiCtx, path) { | ||
handler := m.handlerFunc | ||
for i := len(m.middlewares) - 1; i >= 0; i-- { | ||
handler = m.middlewares[i](handler).ServeHTTP | ||
} | ||
handler(resp, req) | ||
return | ||
} | ||
} | ||
g.r.chiRouter.NotFoundHandler().ServeHTTP(resp, req) | ||
} | ||
|
||
// MatchPath matches the request method, and uses regexp to match the path. | ||
// The pattern uses "<...>" to define path parameters, for example: "/<name>" (different from chi router) | ||
// It is only designed to resolve some special cases which chi router can't handle. | ||
// For most cases, it shouldn't be used because it needs to iterate all rules to find the matched one (inefficient). | ||
func (g *RouterPathGroup) MatchPath(methods, pattern string, h ...any) { | ||
g.matchers = append(g.matchers, 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.