mirror of https://github.com/go-gitea/gitea
Refactor arch route handlers (#32993)
parent
254314be5f
commit
e435b1900a
@ -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 |
||||||
|
} |
@ -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 |
||||||
|
} |
Loading…
Reference in new issue