Skip to content

Commit 99eab04

Browse files
committed
fix
1 parent 550abdb commit 99eab04

File tree

5 files changed

+249
-190
lines changed

5 files changed

+249
-190
lines changed

modules/web/route.go renamed to modules/web/router.go

Lines changed: 16 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,14 @@
44
package web
55

66
import (
7-
"fmt"
87
"net/http"
98
"net/url"
109
"reflect"
11-
"regexp"
1210
"strings"
1311

14-
"code.gitea.io/gitea/modules/container"
1512
"code.gitea.io/gitea/modules/htmlutil"
1613
"code.gitea.io/gitea/modules/reqctx"
1714
"code.gitea.io/gitea/modules/setting"
18-
"code.gitea.io/gitea/modules/util"
1915
"code.gitea.io/gitea/modules/web/middleware"
2016

2117
"gitea.com/go-chi/binding"
@@ -45,7 +41,7 @@ func GetForm(dataStore reqctx.RequestDataStore) any {
4541

4642
// Router defines a route based on chi's router
4743
type Router struct {
48-
chiRouter chi.Router
44+
chiRouter *chi.Mux
4945
curGroupPrefix string
5046
curMiddlewares []any
5147
}
@@ -97,16 +93,21 @@ func isNilOrFuncNil(v any) bool {
9793
return r.Kind() == reflect.Func && r.IsNil()
9894
}
9995

100-
func (r *Router) wrapMiddlewareAndHandler(h []any) ([]func(http.Handler) http.Handler, http.HandlerFunc) {
101-
handlerProviders := make([]func(http.Handler) http.Handler, 0, len(r.curMiddlewares)+len(h)+1)
102-
for _, m := range r.curMiddlewares {
96+
func wrapMiddlewareAndHandler(curMiddlewares, h []any) ([]func(http.Handler) http.Handler, http.HandlerFunc) {
97+
handlerProviders := make([]func(http.Handler) http.Handler, 0, len(curMiddlewares)+len(h)+1)
98+
for _, m := range curMiddlewares {
10399
if !isNilOrFuncNil(m) {
104100
handlerProviders = append(handlerProviders, toHandlerProvider(m))
105101
}
106102
}
107-
for _, m := range h {
103+
if len(h) == 0 {
104+
panic("no endpoint handler provided")
105+
}
106+
for i, m := range h {
108107
if !isNilOrFuncNil(m) {
109108
handlerProviders = append(handlerProviders, toHandlerProvider(m))
109+
} else if i == len(h)-1 {
110+
panic("endpoint handler can't be nil")
110111
}
111112
}
112113
middlewares := handlerProviders[:len(handlerProviders)-1]
@@ -121,7 +122,7 @@ func (r *Router) wrapMiddlewareAndHandler(h []any) ([]func(http.Handler) http.Ha
121122
// Methods adds the same handlers for multiple http "methods" (separated by ",").
122123
// If any method is invalid, the lower level router will panic.
123124
func (r *Router) Methods(methods, pattern string, h ...any) {
124-
middlewares, handlerFunc := r.wrapMiddlewareAndHandler(h)
125+
middlewares, handlerFunc := wrapMiddlewareAndHandler(r.curMiddlewares, h)
125126
fullPattern := r.getPattern(pattern)
126127
if strings.Contains(methods, ",") {
127128
methods := strings.Split(methods, ",")
@@ -141,7 +142,7 @@ func (r *Router) Mount(pattern string, subRouter *Router) {
141142

142143
// Any delegate requests for all methods
143144
func (r *Router) Any(pattern string, h ...any) {
144-
middlewares, handlerFunc := r.wrapMiddlewareAndHandler(h)
145+
middlewares, handlerFunc := wrapMiddlewareAndHandler(r.curMiddlewares, h)
145146
r.chiRouter.With(middlewares...).HandleFunc(r.getPattern(pattern), handlerFunc)
146147
}
147148

@@ -185,17 +186,6 @@ func (r *Router) NotFound(h http.HandlerFunc) {
185186
r.chiRouter.NotFound(h)
186187
}
187188

188-
type pathProcessorParam struct {
189-
name string
190-
captureGroup int
191-
}
192-
193-
type PathProcessor struct {
194-
methods container.Set[string]
195-
re *regexp.Regexp
196-
params []pathProcessorParam
197-
}
198-
199189
func (r *Router) normalizeRequestPath(resp http.ResponseWriter, req *http.Request, next http.Handler) {
200190
normalized := false
201191
normalizedPath := req.URL.EscapedPath()
@@ -253,121 +243,13 @@ func (r *Router) normalizeRequestPath(resp http.ResponseWriter, req *http.Reques
253243
next.ServeHTTP(resp, req)
254244
}
255245

256-
func (p *PathProcessor) ProcessRequestPath(chiCtx *chi.Context, path string) bool {
257-
if !p.methods.Contains(chiCtx.RouteMethod) {
258-
return false
259-
}
260-
if !strings.HasPrefix(path, "/") {
261-
path = "/" + path
262-
}
263-
pathMatches := p.re.FindStringSubmatchIndex(path) // Golang regexp match pairs [start, end, start, end, ...]
264-
if pathMatches == nil {
265-
return false
266-
}
267-
var paramMatches [][]int
268-
for i := 2; i < len(pathMatches); {
269-
paramMatches = append(paramMatches, []int{pathMatches[i], pathMatches[i+1]})
270-
pmIdx := len(paramMatches) - 1
271-
end := pathMatches[i+1]
272-
i += 2
273-
for ; i < len(pathMatches); i += 2 {
274-
if pathMatches[i] >= end {
275-
break
276-
}
277-
paramMatches[pmIdx] = append(paramMatches[pmIdx], pathMatches[i], pathMatches[i+1])
278-
}
279-
}
280-
for i, pm := range paramMatches {
281-
groupIdx := p.params[i].captureGroup * 2
282-
chiCtx.URLParams.Add(p.params[i].name, path[pm[groupIdx]:pm[groupIdx+1]])
283-
}
284-
return true
285-
}
286-
287-
func NewPathProcessor(methods, pattern string) *PathProcessor {
288-
p := &PathProcessor{methods: make(container.Set[string])}
289-
for _, method := range strings.Split(methods, ",") {
290-
p.methods.Add(strings.TrimSpace(method))
291-
}
292-
re := []byte{'^'}
293-
lastEnd := 0
294-
for lastEnd < len(pattern) {
295-
start := strings.IndexByte(pattern[lastEnd:], '<')
296-
if start == -1 {
297-
re = append(re, pattern[lastEnd:]...)
298-
break
299-
}
300-
end := strings.IndexByte(pattern[lastEnd+start:], '>')
301-
if end == -1 {
302-
panic(fmt.Sprintf("invalid pattern: %s", pattern))
303-
}
304-
re = append(re, pattern[lastEnd:lastEnd+start]...)
305-
partName, partExp, _ := strings.Cut(pattern[lastEnd+start+1:lastEnd+start+end], ":")
306-
lastEnd += start + end + 1
307-
308-
// TODO: it could support to specify a "capture group" for the name, for example: "/<name[2]:(\d)-(\d)>"
309-
// it is not used so no need to implement it now
310-
param := pathProcessorParam{}
311-
if partExp == "*" {
312-
re = append(re, "(.*?)/?"...)
313-
if lastEnd < len(pattern) {
314-
if pattern[lastEnd] == '/' {
315-
lastEnd++
316-
}
317-
}
318-
} else {
319-
partExp = util.IfZero(partExp, "[^/]+")
320-
re = append(re, '(')
321-
re = append(re, partExp...)
322-
re = append(re, ')')
323-
}
324-
param.name = partName
325-
p.params = append(p.params, param)
326-
}
327-
re = append(re, '$')
328-
reStr := string(re)
329-
p.re = regexp.MustCompile(reStr)
330-
return p
331-
}
332-
333246
// Combo delegates requests to Combo
334247
func (r *Router) Combo(pattern string, h ...any) *Combo {
335248
return &Combo{r, pattern, h}
336249
}
337250

338-
// Combo represents a tiny group routes with same pattern
339-
type Combo struct {
340-
r *Router
341-
pattern string
342-
h []any
343-
}
344-
345-
// Get delegates Get method
346-
func (c *Combo) Get(h ...any) *Combo {
347-
c.r.Get(c.pattern, append(c.h, h...)...)
348-
return c
349-
}
350-
351-
// Post delegates Post method
352-
func (c *Combo) Post(h ...any) *Combo {
353-
c.r.Post(c.pattern, append(c.h, h...)...)
354-
return c
355-
}
356-
357-
// Delete delegates Delete method
358-
func (c *Combo) Delete(h ...any) *Combo {
359-
c.r.Delete(c.pattern, append(c.h, h...)...)
360-
return c
361-
}
362-
363-
// Put delegates Put method
364-
func (c *Combo) Put(h ...any) *Combo {
365-
c.r.Put(c.pattern, append(c.h, h...)...)
366-
return c
367-
}
368-
369-
// Patch delegates Patch method
370-
func (c *Combo) Patch(h ...any) *Combo {
371-
c.r.Patch(c.pattern, append(c.h, h...)...)
372-
return c
251+
func (r *Router) PathGroup(pattern string, fn func(g *RouterPathGroup), h ...any) {
252+
g := &RouterPathGroup{r: r, pathParam: "*"}
253+
fn(g)
254+
r.Any(pattern, append(h, g.ServeHTTP)...)
373255
}

modules/web/router_combo.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package web
5+
6+
// Combo represents a tiny group routes with same pattern
7+
type Combo struct {
8+
r *Router
9+
pattern string
10+
h []any
11+
}
12+
13+
// Get delegates Get method
14+
func (c *Combo) Get(h ...any) *Combo {
15+
c.r.Get(c.pattern, append(c.h, h...)...)
16+
return c
17+
}
18+
19+
// Post delegates Post method
20+
func (c *Combo) Post(h ...any) *Combo {
21+
c.r.Post(c.pattern, append(c.h, h...)...)
22+
return c
23+
}
24+
25+
// Delete delegates Delete method
26+
func (c *Combo) Delete(h ...any) *Combo {
27+
c.r.Delete(c.pattern, append(c.h, h...)...)
28+
return c
29+
}
30+
31+
// Put delegates Put method
32+
func (c *Combo) Put(h ...any) *Combo {
33+
c.r.Put(c.pattern, append(c.h, h...)...)
34+
return c
35+
}
36+
37+
// Patch delegates Patch method
38+
func (c *Combo) Patch(h ...any) *Combo {
39+
c.r.Patch(c.pattern, append(c.h, h...)...)
40+
return c
41+
}

modules/web/router_path.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package web
5+
6+
import (
7+
"fmt"
8+
"net/http"
9+
"regexp"
10+
"strings"
11+
12+
"code.gitea.io/gitea/modules/container"
13+
"code.gitea.io/gitea/modules/util"
14+
15+
"github.com/go-chi/chi/v5"
16+
)
17+
18+
type RouterPathGroup struct {
19+
r *Router
20+
pathParam string
21+
processors []*routerPathMatcher
22+
}
23+
24+
func (g *RouterPathGroup) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
25+
chiCtx := chi.RouteContext(req.Context())
26+
path := chiCtx.URLParam(g.pathParam)
27+
for _, p := range g.processors {
28+
if p.matchPath(chiCtx, path) {
29+
handler := p.handlerFunc
30+
for i := len(p.middlewares) - 1; i >= 0; i-- {
31+
handler = p.middlewares[i](handler).ServeHTTP
32+
}
33+
handler(resp, req)
34+
return
35+
}
36+
}
37+
g.r.chiRouter.NotFoundHandler().ServeHTTP(resp, req)
38+
}
39+
40+
func (g *RouterPathGroup) MatchPath(methods, pattern string, h ...any) {
41+
g.processors = append(g.processors, newRouterPathMatcher(methods, pattern, h...))
42+
}
43+
44+
type routerPathParam struct {
45+
name string
46+
captureGroup int
47+
}
48+
49+
type routerPathMatcher struct {
50+
methods container.Set[string]
51+
re *regexp.Regexp
52+
params []routerPathParam
53+
middlewares []func(http.Handler) http.Handler
54+
handlerFunc http.HandlerFunc
55+
}
56+
57+
func (p *routerPathMatcher) matchPath(chiCtx *chi.Context, path string) bool {
58+
if !p.methods.Contains(chiCtx.RouteMethod) {
59+
return false
60+
}
61+
if !strings.HasPrefix(path, "/") {
62+
path = "/" + path
63+
}
64+
pathMatches := p.re.FindStringSubmatchIndex(path) // Golang regexp match pairs [start, end, start, end, ...]
65+
if pathMatches == nil {
66+
return false
67+
}
68+
var paramMatches [][]int
69+
for i := 2; i < len(pathMatches); {
70+
paramMatches = append(paramMatches, []int{pathMatches[i], pathMatches[i+1]})
71+
pmIdx := len(paramMatches) - 1
72+
end := pathMatches[i+1]
73+
i += 2
74+
for ; i < len(pathMatches); i += 2 {
75+
if pathMatches[i] >= end {
76+
break
77+
}
78+
paramMatches[pmIdx] = append(paramMatches[pmIdx], pathMatches[i], pathMatches[i+1])
79+
}
80+
}
81+
for i, pm := range paramMatches {
82+
groupIdx := p.params[i].captureGroup * 2
83+
chiCtx.URLParams.Add(p.params[i].name, path[pm[groupIdx]:pm[groupIdx+1]])
84+
}
85+
return true
86+
}
87+
88+
func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher {
89+
middlewares, handlerFunc := wrapMiddlewareAndHandler(nil, h)
90+
p := &routerPathMatcher{methods: make(container.Set[string]), middlewares: middlewares, handlerFunc: handlerFunc}
91+
for _, method := range strings.Split(methods, ",") {
92+
p.methods.Add(strings.TrimSpace(method))
93+
}
94+
re := []byte{'^'}
95+
lastEnd := 0
96+
for lastEnd < len(pattern) {
97+
start := strings.IndexByte(pattern[lastEnd:], '<')
98+
if start == -1 {
99+
re = append(re, pattern[lastEnd:]...)
100+
break
101+
}
102+
end := strings.IndexByte(pattern[lastEnd+start:], '>')
103+
if end == -1 {
104+
panic(fmt.Sprintf("invalid pattern: %s", pattern))
105+
}
106+
re = append(re, pattern[lastEnd:lastEnd+start]...)
107+
partName, partExp, _ := strings.Cut(pattern[lastEnd+start+1:lastEnd+start+end], ":")
108+
lastEnd += start + end + 1
109+
110+
// TODO: it could support to specify a "capture group" for the name, for example: "/<name[2]:(\d)-(\d)>"
111+
// it is not used so no need to implement it now
112+
param := routerPathParam{}
113+
if partExp == "*" {
114+
re = append(re, "(.*?)/?"...)
115+
if lastEnd < len(pattern) && pattern[lastEnd] == '/' {
116+
lastEnd++ // the "*" pattern is able to handle the last slash, so skip it
117+
}
118+
} else {
119+
partExp = util.IfZero(partExp, "[^/]+")
120+
re = append(re, '(')
121+
re = append(re, partExp...)
122+
re = append(re, ')')
123+
}
124+
param.name = partName
125+
p.params = append(p.params, param)
126+
}
127+
re = append(re, '$')
128+
reStr := string(re)
129+
p.re = regexp.MustCompile(reStr)
130+
return p
131+
}

0 commit comments

Comments
 (0)