forked from hinterland/hearth
feat: vendor gitea 1.16.2
This commit is contained in:
@@ -4,7 +4,9 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
@@ -47,6 +49,13 @@ func (r *responseWriter) WriteHeader(statusCode int) {
|
||||
r.respWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (r *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hj, ok := r.respWriter.(http.Hijacker); ok {
|
||||
return hj.Hijack()
|
||||
}
|
||||
return nil, nil, http.ErrNotSupported
|
||||
}
|
||||
|
||||
var (
|
||||
httpReqType = reflect.TypeFor[*http.Request]()
|
||||
respWriterType = reflect.TypeFor[http.ResponseWriter]()
|
||||
@@ -70,7 +79,8 @@ func preCheckHandler(fn reflect.Value, argsIn []reflect.Value) {
|
||||
|
||||
func prepareHandleArgsIn(resp http.ResponseWriter, req *http.Request, fn reflect.Value, fnInfo *routing.FuncInfo) []reflect.Value {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if recovered := recover(); recovered != nil {
|
||||
err := fmt.Errorf("%v\n%s", recovered, log.Stack(2))
|
||||
log.Error("unable to prepare handler arguments for %s: %v", fnInfo.String(), err)
|
||||
panic(err)
|
||||
}
|
||||
@@ -117,7 +127,17 @@ func hasResponseBeenWritten(argsIn []reflect.Value) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func wrapHandlerProvider[T http.Handler](hp func(next http.Handler) T, funcInfo *routing.FuncInfo) func(next http.Handler) http.Handler {
|
||||
type middlewareProvider = func(next http.Handler) http.Handler
|
||||
|
||||
func executeMiddlewaresHandler(w http.ResponseWriter, r *http.Request, middlewares []middlewareProvider, endpoint http.HandlerFunc) {
|
||||
handler := endpoint
|
||||
for i := len(middlewares) - 1; i >= 0; i-- {
|
||||
handler = middlewares[i](handler).ServeHTTP
|
||||
}
|
||||
handler(w, r)
|
||||
}
|
||||
|
||||
func wrapHandlerProvider[T http.Handler](hp func(next http.Handler) T, funcInfo *routing.FuncInfo) middlewareProvider {
|
||||
return func(next http.Handler) http.Handler {
|
||||
h := hp(next) // this handle could be dynamically generated, so we can't use it for debug info
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
@@ -129,14 +149,14 @@ func wrapHandlerProvider[T http.Handler](hp func(next http.Handler) T, funcInfo
|
||||
|
||||
// toHandlerProvider converts a handler to a handler provider
|
||||
// A handler provider is a function that takes a "next" http.Handler, it can be used as a middleware
|
||||
func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
|
||||
func toHandlerProvider(handler any) middlewareProvider {
|
||||
funcInfo := routing.GetFuncInfo(handler)
|
||||
fn := reflect.ValueOf(handler)
|
||||
if fn.Type().Kind() != reflect.Func {
|
||||
panic(fmt.Sprintf("handler must be a function, but got %s", fn.Type()))
|
||||
}
|
||||
|
||||
if hp, ok := handler.(func(next http.Handler) http.Handler); ok {
|
||||
if hp, ok := handler.(middlewareProvider); ok {
|
||||
return wrapHandlerProvider(hp, funcInfo)
|
||||
} else if hp, ok := handler.(func(http.Handler) http.HandlerFunc); ok {
|
||||
return wrapHandlerProvider(hp, funcInfo)
|
||||
|
||||
@@ -138,6 +138,8 @@ func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Lo
|
||||
data["ErrorMsg"] = trName + l.TrString("form.username_error")
|
||||
case validation.ErrInvalidGroupTeamMap:
|
||||
data["ErrorMsg"] = trName + l.TrString("form.invalid_group_team_map_error", errs[0].Message)
|
||||
case validation.ErrInvalidBadgeSlug:
|
||||
data["ErrorMsg"] = trName + l.TrString("form.invalid_slug_error")
|
||||
default:
|
||||
msg := errs[0].Classification
|
||||
if msg != "" && errs[0].Message != "" {
|
||||
|
||||
@@ -11,16 +11,31 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/session"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
const (
|
||||
CookieWebBannerDismissed = "gitea_disbnr"
|
||||
CookieTheme = "gitea_theme"
|
||||
cookieRedirectTo = "redirect_to"
|
||||
)
|
||||
|
||||
func GetRedirectToCookie(req *http.Request) string {
|
||||
return GetSiteCookie(req, cookieRedirectTo)
|
||||
}
|
||||
|
||||
// SetRedirectToCookie convenience function to set the RedirectTo cookie consistently
|
||||
func SetRedirectToCookie(resp http.ResponseWriter, value string) {
|
||||
SetSiteCookie(resp, "redirect_to", value, 0)
|
||||
SetSiteCookie(resp, cookieRedirectTo, value, 0)
|
||||
}
|
||||
|
||||
// DeleteRedirectToCookie convenience function to delete most cookies consistently
|
||||
func DeleteRedirectToCookie(resp http.ResponseWriter) {
|
||||
SetSiteCookie(resp, "redirect_to", "", -1)
|
||||
SetSiteCookie(resp, cookieRedirectTo, "", -1)
|
||||
}
|
||||
|
||||
func RedirectLinkUserLogin(req *http.Request) string {
|
||||
return setting.AppSubURL + "/user/login?redirect_to=" + url.QueryEscape(setting.AppSubURL+req.URL.RequestURI())
|
||||
}
|
||||
|
||||
// GetSiteCookie returns given cookie value from request header.
|
||||
@@ -39,11 +54,13 @@ func SetSiteCookie(resp http.ResponseWriter, name, value string, maxAge int) {
|
||||
// These are more specific than cookies without a trailing /, so
|
||||
// we need to delete these if they exist.
|
||||
deleteLegacySiteCookie(resp, name)
|
||||
|
||||
// HINT: INSTALL-PAGE-COOKIE-INIT: the cookie system is not properly initialized on the Install page, so there is no CookiePath
|
||||
cookie := &http.Cookie{
|
||||
Name: name,
|
||||
Value: url.QueryEscape(value),
|
||||
MaxAge: maxAge,
|
||||
Path: setting.SessionConfig.CookiePath,
|
||||
Path: util.IfZero(setting.SessionConfig.CookiePath, "/"),
|
||||
Domain: setting.SessionConfig.Domain,
|
||||
Secure: setting.SessionConfig.Secure,
|
||||
HttpOnly: true,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/public"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
@@ -36,5 +37,6 @@ func CommonTemplateContextData() reqctx.ContextData {
|
||||
"PageStartTime": time.Now(),
|
||||
|
||||
"RunModeIsProd": setting.IsProd,
|
||||
"ViteModeIsDev": public.IsViteDevMode(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,13 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// PreMiddlewareProvider is a special middleware provider which will be executed
|
||||
// before other middlewares on the same "routing" level (AfterRouting/Group/Methods/Any, but not BeforeRouting).
|
||||
// A route can do something (e.g.: set middleware options) at the place where it is declared,
|
||||
// and the code will be executed before other middlewares which are added before the declaration.
|
||||
// Use cases: mark a route with some meta info, set some options for middlewares, etc.
|
||||
type PreMiddlewareProvider func(next http.Handler) http.Handler
|
||||
|
||||
// Bind binding an obj to a handler's context data
|
||||
func Bind[T any](_ T) http.HandlerFunc {
|
||||
return func(resp http.ResponseWriter, req *http.Request) {
|
||||
@@ -41,7 +48,10 @@ func GetForm(dataStore reqctx.RequestDataStore) any {
|
||||
|
||||
// Router defines a route based on chi's router
|
||||
type Router struct {
|
||||
chiRouter *chi.Mux
|
||||
chiRouter *chi.Mux
|
||||
|
||||
afterRouting []any
|
||||
|
||||
curGroupPrefix string
|
||||
curMiddlewares []any
|
||||
}
|
||||
@@ -52,8 +62,9 @@ func NewRouter() *Router {
|
||||
return &Router{chiRouter: r}
|
||||
}
|
||||
|
||||
// Use supports two middlewares
|
||||
func (r *Router) Use(middlewares ...any) {
|
||||
// BeforeRouting adds middlewares which will be executed before the request path gets routed
|
||||
// It should only be used for framework-level global middlewares when it needs to change request method & path.
|
||||
func (r *Router) BeforeRouting(middlewares ...any) {
|
||||
for _, m := range middlewares {
|
||||
if !isNilOrFuncNil(m) {
|
||||
r.chiRouter.Use(toHandlerProvider(m))
|
||||
@@ -61,7 +72,13 @@ func (r *Router) Use(middlewares ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
// Group mounts a sub-Router along a `pattern` string.
|
||||
// AfterRouting adds middlewares which will be executed after the request path gets routed
|
||||
// It can see the routed path and resolved path parameters
|
||||
func (r *Router) AfterRouting(middlewares ...any) {
|
||||
r.afterRouting = append(r.afterRouting, middlewares...)
|
||||
}
|
||||
|
||||
// Group mounts a sub-router along a "pattern" string.
|
||||
func (r *Router) Group(pattern string, fn func(), middlewares ...any) {
|
||||
previousGroupPrefix := r.curGroupPrefix
|
||||
previousMiddlewares := r.curMiddlewares
|
||||
@@ -93,36 +110,54 @@ func isNilOrFuncNil(v any) bool {
|
||||
return r.Kind() == reflect.Func && r.IsNil()
|
||||
}
|
||||
|
||||
func wrapMiddlewareAndHandler(curMiddlewares, h []any) ([]func(http.Handler) http.Handler, http.HandlerFunc) {
|
||||
handlerProviders := make([]func(http.Handler) http.Handler, 0, len(curMiddlewares)+len(h)+1)
|
||||
for _, m := range curMiddlewares {
|
||||
if !isNilOrFuncNil(m) {
|
||||
handlerProviders = append(handlerProviders, toHandlerProvider(m))
|
||||
func wrapMiddlewareAppendPre(all []middlewareProvider, middlewares []any) []middlewareProvider {
|
||||
for _, m := range middlewares {
|
||||
if h, ok := m.(PreMiddlewareProvider); ok && h != nil {
|
||||
all = append(all, toHandlerProvider(middlewareProvider(h)))
|
||||
}
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
func wrapMiddlewareAppendNormal(all []middlewareProvider, middlewares []any) []middlewareProvider {
|
||||
for _, m := range middlewares {
|
||||
if _, ok := m.(PreMiddlewareProvider); !ok && !isNilOrFuncNil(m) {
|
||||
all = append(all, toHandlerProvider(m))
|
||||
}
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
func wrapMiddlewareAndHandler(useMiddlewares, curMiddlewares, h []any) (_ []middlewareProvider, _ http.HandlerFunc, hasPreMiddlewares bool) {
|
||||
if len(h) == 0 {
|
||||
panic("no endpoint handler provided")
|
||||
}
|
||||
for i, m := range h {
|
||||
if !isNilOrFuncNil(m) {
|
||||
handlerProviders = append(handlerProviders, toHandlerProvider(m))
|
||||
} else if i == len(h)-1 {
|
||||
panic("endpoint handler can't be nil")
|
||||
}
|
||||
if isNilOrFuncNil(h[len(h)-1]) {
|
||||
panic("endpoint handler can't be nil")
|
||||
}
|
||||
|
||||
handlerProviders := make([]middlewareProvider, 0, len(useMiddlewares)+len(curMiddlewares)+len(h)+1)
|
||||
handlerProviders = wrapMiddlewareAppendPre(handlerProviders, useMiddlewares)
|
||||
handlerProviders = wrapMiddlewareAppendPre(handlerProviders, curMiddlewares)
|
||||
handlerProviders = wrapMiddlewareAppendPre(handlerProviders, h)
|
||||
hasPreMiddlewares = len(handlerProviders) > 0
|
||||
handlerProviders = wrapMiddlewareAppendNormal(handlerProviders, useMiddlewares)
|
||||
handlerProviders = wrapMiddlewareAppendNormal(handlerProviders, curMiddlewares)
|
||||
handlerProviders = wrapMiddlewareAppendNormal(handlerProviders, h)
|
||||
|
||||
middlewares := handlerProviders[:len(handlerProviders)-1]
|
||||
handlerFunc := handlerProviders[len(handlerProviders)-1](nil).ServeHTTP
|
||||
mockPoint := RouterMockPoint(MockAfterMiddlewares)
|
||||
if mockPoint != nil {
|
||||
middlewares = append(middlewares, mockPoint)
|
||||
}
|
||||
return middlewares, handlerFunc
|
||||
return middlewares, handlerFunc, hasPreMiddlewares
|
||||
}
|
||||
|
||||
// Methods adds the same handlers for multiple http "methods" (separated by ",").
|
||||
// If any method is invalid, the lower level router will panic.
|
||||
func (r *Router) Methods(methods, pattern string, h ...any) {
|
||||
middlewares, handlerFunc := wrapMiddlewareAndHandler(r.curMiddlewares, h)
|
||||
middlewares, handlerFunc, _ := wrapMiddlewareAndHandler(r.afterRouting, r.curMiddlewares, h)
|
||||
fullPattern := r.getPattern(pattern)
|
||||
if strings.Contains(methods, ",") {
|
||||
methods := strings.SplitSeq(methods, ",")
|
||||
@@ -134,15 +169,19 @@ func (r *Router) Methods(methods, pattern string, h ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mount attaches another Router along ./pattern/*
|
||||
// Mount attaches another Router along "/pattern/*"
|
||||
func (r *Router) Mount(pattern string, subRouter *Router) {
|
||||
subRouter.Use(r.curMiddlewares...)
|
||||
r.chiRouter.Mount(r.getPattern(pattern), subRouter.chiRouter)
|
||||
handlerProviders := make([]middlewareProvider, 0, len(r.afterRouting)+len(r.curMiddlewares))
|
||||
handlerProviders = wrapMiddlewareAppendPre(handlerProviders, r.afterRouting)
|
||||
handlerProviders = wrapMiddlewareAppendPre(handlerProviders, r.curMiddlewares)
|
||||
handlerProviders = wrapMiddlewareAppendNormal(handlerProviders, r.afterRouting)
|
||||
handlerProviders = wrapMiddlewareAppendNormal(handlerProviders, r.curMiddlewares)
|
||||
r.chiRouter.With(handlerProviders...).Mount(r.getPattern(pattern), subRouter.chiRouter)
|
||||
}
|
||||
|
||||
// Any delegate requests for all methods
|
||||
func (r *Router) Any(pattern string, h ...any) {
|
||||
middlewares, handlerFunc := wrapMiddlewareAndHandler(r.curMiddlewares, h)
|
||||
middlewares, handlerFunc, _ := wrapMiddlewareAndHandler(r.afterRouting, r.curMiddlewares, h)
|
||||
r.chiRouter.With(middlewares...).HandleFunc(r.getPattern(pattern), handlerFunc)
|
||||
}
|
||||
|
||||
@@ -178,12 +217,16 @@ func (r *Router) Patch(pattern string, h ...any) {
|
||||
|
||||
// ServeHTTP implements http.Handler
|
||||
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// TODO: need to move it to the top-level common middleware, otherwise each "Mount" will cause it to be executed multiple times, which is inefficient.
|
||||
r.normalizeRequestPath(w, req, r.chiRouter)
|
||||
}
|
||||
|
||||
// NotFound defines a handler to respond whenever a route could not be found.
|
||||
func (r *Router) NotFound(h http.HandlerFunc) {
|
||||
r.chiRouter.NotFound(h)
|
||||
middlewares, handlerFunc, _ := wrapMiddlewareAndHandler(r.afterRouting, r.curMiddlewares, []any{h})
|
||||
r.chiRouter.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
||||
executeMiddlewaresHandler(w, r, middlewares, handlerFunc)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Router) normalizeRequestPath(resp http.ResponseWriter, req *http.Request, next http.Handler) {
|
||||
|
||||
@@ -27,11 +27,7 @@ func (g *RouterPathGroup) ServeHTTP(resp http.ResponseWriter, req *http.Request)
|
||||
for _, m := range g.matchers {
|
||||
if m.matchPath(chiCtx, path) {
|
||||
chiCtx.RoutePatterns = append(chiCtx.RoutePatterns, m.pattern)
|
||||
handler := m.handlerFunc
|
||||
for i := len(m.middlewares) - 1; i >= 0; i-- {
|
||||
handler = m.middlewares[i](handler).ServeHTTP
|
||||
}
|
||||
handler(resp, req)
|
||||
executeMiddlewaresHandler(resp, req, m.middlewares, m.handlerFunc)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -59,6 +55,7 @@ func (g *RouterPathGroup) MatchPattern(methods string, pattern *RouterPathGroupP
|
||||
|
||||
type routerPathParam struct {
|
||||
name string
|
||||
pathSepEnd bool
|
||||
captureGroup int
|
||||
}
|
||||
|
||||
@@ -67,7 +64,7 @@ type routerPathMatcher struct {
|
||||
pattern string
|
||||
re *regexp.Regexp
|
||||
params []routerPathParam
|
||||
middlewares []func(http.Handler) http.Handler
|
||||
middlewares []middlewareProvider
|
||||
handlerFunc http.HandlerFunc
|
||||
}
|
||||
|
||||
@@ -97,7 +94,15 @@ func (p *routerPathMatcher) matchPath(chiCtx *chi.Context, path string) bool {
|
||||
}
|
||||
for i, pm := range paramMatches {
|
||||
groupIdx := p.params[i].captureGroup * 2
|
||||
chiCtx.URLParams.Add(p.params[i].name, path[pm[groupIdx]:pm[groupIdx+1]])
|
||||
if pm[groupIdx] == -1 || pm[groupIdx+1] == -1 {
|
||||
chiCtx.URLParams.Add(p.params[i].name, "")
|
||||
continue
|
||||
}
|
||||
val := path[pm[groupIdx]:pm[groupIdx+1]]
|
||||
if p.params[i].pathSepEnd {
|
||||
val = strings.TrimSuffix(val, "/")
|
||||
}
|
||||
chiCtx.URLParams.Add(p.params[i].name, val)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -111,7 +116,10 @@ func isValidMethod(name string) bool {
|
||||
}
|
||||
|
||||
func newRouterPathMatcher(methods string, patternRegexp *RouterPathGroupPattern, h ...any) *routerPathMatcher {
|
||||
middlewares, handlerFunc := wrapMiddlewareAndHandler(patternRegexp.middlewares, h)
|
||||
middlewares, handlerFunc, hasPreMiddlewares := wrapMiddlewareAndHandler(nil, patternRegexp.middlewares, h)
|
||||
if hasPreMiddlewares {
|
||||
panic("pre-middlewares are not supported in router path matcher")
|
||||
}
|
||||
p := &routerPathMatcher{methods: make(container.Set[string]), middlewares: middlewares, handlerFunc: handlerFunc}
|
||||
for method := range strings.SplitSeq(methods, ",") {
|
||||
method = strings.TrimSpace(method)
|
||||
@@ -146,11 +154,19 @@ func patternRegexp(pattern string, h ...any) *RouterPathGroupPattern {
|
||||
// it is not used so no need to implement it now
|
||||
param := routerPathParam{}
|
||||
if partExp == "*" {
|
||||
re = append(re, "(.*?)/?"...)
|
||||
// "<part:*>" is a shorthand for optionally matching any string (but not greedy)
|
||||
partExp = ".*?"
|
||||
if lastEnd < len(pattern) && pattern[lastEnd] == '/' {
|
||||
lastEnd++ // the "*" pattern is able to handle the last slash, so skip it
|
||||
// if this param part ends with path separator "/", then consider it together: "(.*?/)"
|
||||
partExp += "/"
|
||||
param.pathSepEnd = true
|
||||
lastEnd++
|
||||
}
|
||||
re = append(re, '(')
|
||||
re = append(re, partExp...)
|
||||
re = append(re, ')', '?') // the wildcard matching is optional
|
||||
} else {
|
||||
// the pattern is user-provided regexp, defaults to a path part (separated by "/")
|
||||
partExp = util.IfZero(partExp, "[^/]+")
|
||||
re = append(re, '(')
|
||||
re = append(re, partExp...)
|
||||
|
||||
@@ -30,12 +30,78 @@ func chiURLParamsToMap(chiCtx *chi.Context) map[string]string {
|
||||
return util.Iif(len(m) == 0, nil, m)
|
||||
}
|
||||
|
||||
type testResult struct {
|
||||
method string
|
||||
pathParams map[string]string
|
||||
handlerMarks []string
|
||||
chiRoutePattern *string
|
||||
}
|
||||
|
||||
type testRecorder struct {
|
||||
res testResult
|
||||
}
|
||||
|
||||
func (r *testRecorder) reset() {
|
||||
r.res = testResult{}
|
||||
}
|
||||
|
||||
func (r *testRecorder) handle(optMark ...string) func(resp http.ResponseWriter, req *http.Request) {
|
||||
mark := util.OptionalArg(optMark, "")
|
||||
return func(resp http.ResponseWriter, req *http.Request) {
|
||||
chiCtx := chi.RouteContext(req.Context())
|
||||
r.res.method = req.Method
|
||||
r.res.pathParams = chiURLParamsToMap(chiCtx)
|
||||
r.res.chiRoutePattern = new(chiCtx.RoutePattern())
|
||||
if mark != "" {
|
||||
r.res.handlerMarks = append(r.res.handlerMarks, mark)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *testRecorder) provider(optMark ...string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
r.handle(optMark...)(resp, req)
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *testRecorder) stop(optMark ...string) func(resp http.ResponseWriter, req *http.Request) {
|
||||
mark := util.OptionalArg(optMark, "")
|
||||
return func(resp http.ResponseWriter, req *http.Request) {
|
||||
if stop := req.FormValue("stop"); stop != "" && (mark == "" || mark == stop) {
|
||||
r.handle(stop)(resp, req)
|
||||
resp.WriteHeader(http.StatusOK)
|
||||
} else if mark != "" {
|
||||
r.res.handlerMarks = append(r.res.handlerMarks, mark)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *testRecorder) test(t *testing.T, rt *Router, methodPath string, expected testResult) {
|
||||
r.reset()
|
||||
methodPathFields := strings.Fields(methodPath)
|
||||
req, err := http.NewRequest(methodPathFields[0], methodPathFields[1], nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
httpRecorder := httptest.NewRecorder()
|
||||
httpRecorder.Body = buff
|
||||
rt.ServeHTTP(httpRecorder, req)
|
||||
if expected.chiRoutePattern == nil {
|
||||
r.res.chiRoutePattern = nil
|
||||
}
|
||||
assert.Equal(t, expected, r.res)
|
||||
}
|
||||
|
||||
func TestPathProcessor(t *testing.T) {
|
||||
testProcess := func(pattern, uri string, expectedPathParams map[string]string) {
|
||||
chiCtx := chi.NewRouteContext()
|
||||
chiCtx.RouteMethod = "GET"
|
||||
p := newRouterPathMatcher("GET", patternRegexp(pattern), http.NotFound)
|
||||
assert.True(t, p.matchPath(chiCtx, uri), "use pattern %s to process uri %s", pattern, uri)
|
||||
shouldProcess := expectedPathParams != nil
|
||||
assert.Equal(t, shouldProcess, p.matchPath(chiCtx, uri), "use pattern %s to process uri %s", pattern, uri)
|
||||
assert.Equal(t, expectedPathParams, chiURLParamsToMap(chiCtx), "use pattern %s to process uri %s", pattern, uri)
|
||||
}
|
||||
|
||||
@@ -48,45 +114,17 @@ func TestPathProcessor(t *testing.T) {
|
||||
testProcess("/<p1:*>/<p2>", "/a", map[string]string{"p1": "", "p2": "a"})
|
||||
testProcess("/<p1:*>/<p2>", "/a/b", map[string]string{"p1": "a", "p2": "b"})
|
||||
testProcess("/<p1:*>/<p2>", "/a/b/c", map[string]string{"p1": "a/b", "p2": "c"})
|
||||
testProcess("/<p1:*>/part/<p2>", "/a/part/c", map[string]string{"p1": "a", "p2": "c"})
|
||||
testProcess("/<p1:*>/part/<p2>", "/part/c", map[string]string{"p1": "", "p2": "c"})
|
||||
testProcess("/<p1:*>/part/<p2>", "/a/other-part/c", nil)
|
||||
testProcess("/<p1:*>-part/<p2>", "/a-other-part/c", map[string]string{"p1": "a-other", "p2": "c"})
|
||||
}
|
||||
|
||||
func TestRouter(t *testing.T) {
|
||||
buff := &bytes.Buffer{}
|
||||
recorder := httptest.NewRecorder()
|
||||
recorder.Body = buff
|
||||
|
||||
type resultStruct struct {
|
||||
method string
|
||||
pathParams map[string]string
|
||||
handlerMarks []string
|
||||
chiRoutePattern *string
|
||||
}
|
||||
|
||||
var res resultStruct
|
||||
h := func(optMark ...string) func(resp http.ResponseWriter, req *http.Request) {
|
||||
mark := util.OptionalArg(optMark, "")
|
||||
return func(resp http.ResponseWriter, req *http.Request) {
|
||||
chiCtx := chi.RouteContext(req.Context())
|
||||
res.method = req.Method
|
||||
res.pathParams = chiURLParamsToMap(chiCtx)
|
||||
res.chiRoutePattern = util.ToPointer(chiCtx.RoutePattern())
|
||||
if mark != "" {
|
||||
res.handlerMarks = append(res.handlerMarks, mark)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stopMark := func(optMark ...string) func(resp http.ResponseWriter, req *http.Request) {
|
||||
mark := util.OptionalArg(optMark, "")
|
||||
return func(resp http.ResponseWriter, req *http.Request) {
|
||||
if stop := req.FormValue("stop"); stop != "" && (mark == "" || mark == stop) {
|
||||
h(stop)(resp, req)
|
||||
resp.WriteHeader(http.StatusOK)
|
||||
} else if mark != "" {
|
||||
res.handlerMarks = append(res.handlerMarks, mark)
|
||||
}
|
||||
}
|
||||
}
|
||||
type resultStruct = testResult
|
||||
resRecorder := &testRecorder{}
|
||||
h := resRecorder.handle
|
||||
stopMark := resRecorder.stop
|
||||
|
||||
r := NewRouter()
|
||||
r.NotFound(h("not-found:/"))
|
||||
@@ -123,15 +161,7 @@ func TestRouter(t *testing.T) {
|
||||
|
||||
testRoute := func(t *testing.T, methodPath string, expected resultStruct) {
|
||||
t.Run(methodPath, func(t *testing.T) {
|
||||
res = resultStruct{}
|
||||
methodPathFields := strings.Fields(methodPath)
|
||||
req, err := http.NewRequest(methodPathFields[0], methodPathFields[1], nil)
|
||||
assert.NoError(t, err)
|
||||
r.ServeHTTP(recorder, req)
|
||||
if expected.chiRoutePattern == nil {
|
||||
res.chiRoutePattern = nil
|
||||
}
|
||||
assert.Equal(t, expected, res)
|
||||
resRecorder.test(t, r, methodPath, expected)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -139,7 +169,7 @@ func TestRouter(t *testing.T) {
|
||||
testRoute(t, "GET /the-user/the-repo/other", resultStruct{
|
||||
method: "GET",
|
||||
handlerMarks: []string{"not-found:/"},
|
||||
chiRoutePattern: util.ToPointer(""),
|
||||
chiRoutePattern: new(""),
|
||||
})
|
||||
testRoute(t, "GET /the-user/the-repo/pulls", resultStruct{
|
||||
method: "GET",
|
||||
@@ -150,7 +180,7 @@ func TestRouter(t *testing.T) {
|
||||
method: "GET",
|
||||
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
|
||||
handlerMarks: []string{"view-issue"},
|
||||
chiRoutePattern: util.ToPointer("/{username}/{reponame}/{type:issues|pulls}/{index}"),
|
||||
chiRoutePattern: new("/{username}/{reponame}/{type:issues|pulls}/{index}"),
|
||||
})
|
||||
testRoute(t, "GET /the-user/the-repo/issues/123?stop=hijack", resultStruct{
|
||||
method: "GET",
|
||||
@@ -228,7 +258,7 @@ func TestRouter(t *testing.T) {
|
||||
method: "GET",
|
||||
pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
|
||||
handlerMarks: []string{"s1", "s2", "s3"},
|
||||
chiRoutePattern: util.ToPointer("/api/v1/repos/{username}/{reponame}/branches/<dir:*>/<file:[a-z]{1,2}>"),
|
||||
chiRoutePattern: new("/api/v1/repos/{username}/{reponame}/branches/<dir:*>/<file:[a-z]{1,2}>"),
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -273,3 +303,39 @@ func TestRouteNormalizePath(t *testing.T) {
|
||||
testPath("/v2/", paths{EscapedPath: "/v2", RawPath: "/v2", Path: "/v2"})
|
||||
testPath("/v2/%2f", paths{EscapedPath: "/v2/%2f", RawPath: "/v2/%2f", Path: "/v2//"})
|
||||
}
|
||||
|
||||
func TestPreMiddlewareProvider(t *testing.T) {
|
||||
resRecorder := &testRecorder{}
|
||||
h := resRecorder.handle
|
||||
p := resRecorder.provider
|
||||
|
||||
root := NewRouter()
|
||||
root.BeforeRouting(h("before-root"))
|
||||
root.AfterRouting(h("root"))
|
||||
root.Get("/a/1", h("mid"), PreMiddlewareProvider(p("pre-root")), h("end1"))
|
||||
|
||||
sub := NewRouter()
|
||||
sub.BeforeRouting(h("before-sub"))
|
||||
sub.AfterRouting(h("sub"))
|
||||
sub.Get("/2", h("mid"), PreMiddlewareProvider(p("pre-sub")), h("end2"))
|
||||
sub.NotFound(h("not-found"))
|
||||
|
||||
root.Mount("/a", sub)
|
||||
|
||||
resRecorder.test(t, root, "GET /a/1", testResult{
|
||||
method: "GET",
|
||||
handlerMarks: []string{"before-root", "pre-root", "root", "mid", "end1"},
|
||||
})
|
||||
resRecorder.test(t, root, "GET /a/2", testResult{
|
||||
method: "GET",
|
||||
handlerMarks: []string{"before-root", "root", "before-sub", "pre-sub", "sub", "mid", "end2"},
|
||||
})
|
||||
resRecorder.test(t, root, "GET /no-such", testResult{
|
||||
method: "GET",
|
||||
handlerMarks: []string{"before-root"},
|
||||
})
|
||||
resRecorder.test(t, root, "GET /a/no-such", testResult{
|
||||
method: "GET",
|
||||
handlerMarks: []string{"before-root", "root", "before-sub", "sub", "not-found"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/gtprof"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
)
|
||||
|
||||
@@ -40,11 +41,23 @@ func MarkLongPolling(resp http.ResponseWriter, req *http.Request) {
|
||||
|
||||
record.lock.Lock()
|
||||
record.isLongPolling = true
|
||||
record.logLevel = log.TRACE
|
||||
record.lock.Unlock()
|
||||
}
|
||||
|
||||
func MarkLogLevelTrace(resp http.ResponseWriter, req *http.Request) {
|
||||
record, ok := req.Context().Value(contextKey).(*requestRecord)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
record.lock.Lock()
|
||||
record.logLevel = log.TRACE
|
||||
record.lock.Unlock()
|
||||
}
|
||||
|
||||
// UpdatePanicError updates a context's error info, a panic may be recovered by other middlewares, but we still need to know that.
|
||||
func UpdatePanicError(ctx context.Context, err any) {
|
||||
func UpdatePanicError(ctx context.Context, err error) {
|
||||
record, ok := ctx.Value(contextKey).(*requestRecord)
|
||||
if !ok {
|
||||
return
|
||||
|
||||
@@ -5,7 +5,6 @@ package routing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -36,17 +35,8 @@ var (
|
||||
|
||||
func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) {
|
||||
const callerName = "HTTPRequest"
|
||||
logTrace := func(fmt string, args ...any) {
|
||||
logger.Log(2, &log.Event{Level: log.TRACE, Caller: callerName}, fmt, args...)
|
||||
}
|
||||
logInfo := func(fmt string, args ...any) {
|
||||
logger.Log(2, &log.Event{Level: log.INFO, Caller: callerName}, fmt, args...)
|
||||
}
|
||||
logWarn := func(fmt string, args ...any) {
|
||||
logger.Log(2, &log.Event{Level: log.WARN, Caller: callerName}, fmt, args...)
|
||||
}
|
||||
logError := func(fmt string, args ...any) {
|
||||
logger.Log(2, &log.Event{Level: log.ERROR, Caller: callerName}, fmt, args...)
|
||||
logRequest := func(level log.Level, fmt string, args ...any) {
|
||||
logger.Log(2, &log.Event{Level: level, Caller: callerName}, fmt, args...)
|
||||
}
|
||||
return func(trigger Event, record *requestRecord) {
|
||||
if trigger == StartEvent {
|
||||
@@ -57,7 +47,7 @@ func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) {
|
||||
}
|
||||
// when a request starts, we have no information about the handler function information, we only have the request path
|
||||
req := record.request
|
||||
logTrace("router: %s %v %s for %s", startMessage, log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr)
|
||||
logRequest(log.TRACE, "router: %s %v %s for %s", startMessage, log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -73,12 +63,12 @@ func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) {
|
||||
|
||||
if trigger == StillExecutingEvent {
|
||||
message := slowMessage
|
||||
logf := logWarn
|
||||
logLevel := log.WARN
|
||||
if isLongPolling {
|
||||
logf = logInfo
|
||||
logLevel = log.INFO
|
||||
message = pollingMessage
|
||||
}
|
||||
logf("router: %s %v %s for %s, elapsed %v @ %s",
|
||||
logRequest(logLevel, "router: %s %v %s for %s, elapsed %v @ %s",
|
||||
message,
|
||||
log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr,
|
||||
log.ColoredTime(time.Since(record.startTime)),
|
||||
@@ -88,7 +78,7 @@ func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) {
|
||||
}
|
||||
|
||||
if panicErr != nil {
|
||||
logWarn("router: %s %v %s for %s, panic in %v @ %s, err=%v",
|
||||
logRequest(log.WARN, "router: %s %v %s for %s, panic in %v @ %s, err=%v",
|
||||
failedMessage,
|
||||
log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr,
|
||||
log.ColoredTime(time.Since(record.startTime)),
|
||||
@@ -102,21 +92,22 @@ func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) {
|
||||
if v, ok := record.responseWriter.(types.ResponseStatusProvider); ok {
|
||||
status = v.WrittenStatus()
|
||||
}
|
||||
logf := logInfo
|
||||
logLevel := record.logLevel
|
||||
if logLevel == log.UNDEFINED {
|
||||
logLevel = log.INFO
|
||||
}
|
||||
// lower the log level for some specific requests, in most cases these logs are not useful
|
||||
if status > 0 && status < 400 &&
|
||||
strings.HasPrefix(req.RequestURI, "/assets/") /* static assets */ ||
|
||||
req.RequestURI == "/user/events" /* Server-Sent Events (SSE) handler */ ||
|
||||
req.RequestURI == "/api/actions/runner.v1.RunnerService/FetchTask" /* Actions Runner polling */ {
|
||||
logf = logTrace
|
||||
logLevel = log.TRACE
|
||||
}
|
||||
message := completedMessage
|
||||
if isUnknownHandler {
|
||||
logf = logError
|
||||
logLevel = log.ERROR
|
||||
message = unknownHandlerMessage
|
||||
}
|
||||
|
||||
logf("router: %s %v %s for %s, %v %v in %v @ %s",
|
||||
logRequest(logLevel, "router: %s %v %s for %s, %v %v in %v @ %s",
|
||||
message,
|
||||
log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr,
|
||||
log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(status)), log.ColoredTime(time.Since(record.startTime)),
|
||||
|
||||
@@ -5,11 +5,13 @@ package routing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
)
|
||||
|
||||
@@ -99,7 +101,7 @@ func (manager *requestRecordsManager) handler(next http.Handler) http.Handler {
|
||||
localPanicErr := recover()
|
||||
if localPanicErr != nil {
|
||||
record.lock.Lock()
|
||||
record.panicError = localPanicErr
|
||||
record.panicError = fmt.Errorf("%v\n%s", localPanicErr, log.Stack(2))
|
||||
record.lock.Unlock()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
type requestRecord struct {
|
||||
@@ -23,6 +25,7 @@ type requestRecord struct {
|
||||
|
||||
// mutable fields
|
||||
isLongPolling bool
|
||||
logLevel log.Level
|
||||
funcInfo *FuncInfo
|
||||
panicError any
|
||||
panicError error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user