mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-11-22 09:54:24 +01:00
Refactor web route (#24080)
The old code is unnecessarily complex, and has many misuses. Old code "wraps" a lot, wrap wrap wrap, it's difficult to understand which kind of handler is used. The new code uses a general approach, we do not need to write all kinds of handlers into the "wrapper", do not need to wrap them again and again. New code, there are only 2 concepts: 1. HandlerProvider: `func (h any) (handlerProvider func (next) http.Handler)`, it can be used as middleware 2. Use HandlerProvider to get the final HandlerFunc, and use it for `r.Get()` And we can decouple the route package from context package (see the TODO). # FAQ ## Is `reflect` safe? Yes, all handlers are checked during startup, see the `preCheckHandler` comment. If any handler is wrong, developers could know it in the first time. ## Does `reflect` affect performance? No. https://github.com/go-gitea/gitea/pull/24080#discussion_r1164825901 1. This reflect code only runs for each web handler call, handler is far more slower: 10ms-50ms 2. The reflect is pretty fast (comparing to other code): 0.000265ms 3. XORM has more reflect operations already
This commit is contained in:
parent
70fc47a22a
commit
b9a97ccd0e
200
modules/web/handler.go
Normal file
200
modules/web/handler.go
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
goctx "context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/web/routing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResponseStatusProvider is an interface to check whether the response has been written by the handler
|
||||||
|
type ResponseStatusProvider interface {
|
||||||
|
Written() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: decouple this from the context package, let the context package register these providers
|
||||||
|
var argTypeProvider = map[reflect.Type]func(req *http.Request) ResponseStatusProvider{
|
||||||
|
reflect.TypeOf(&context.APIContext{}): func(req *http.Request) ResponseStatusProvider { return context.GetAPIContext(req) },
|
||||||
|
reflect.TypeOf(&context.Context{}): func(req *http.Request) ResponseStatusProvider { return context.GetContext(req) },
|
||||||
|
reflect.TypeOf(&context.PrivateContext{}): func(req *http.Request) ResponseStatusProvider { return context.GetPrivateContext(req) },
|
||||||
|
}
|
||||||
|
|
||||||
|
// responseWriter is a wrapper of http.ResponseWriter, to check whether the response has been written
|
||||||
|
type responseWriter struct {
|
||||||
|
respWriter http.ResponseWriter
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ResponseStatusProvider = (*responseWriter)(nil)
|
||||||
|
|
||||||
|
func (r *responseWriter) Written() bool {
|
||||||
|
return r.status > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *responseWriter) Header() http.Header {
|
||||||
|
return r.respWriter.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *responseWriter) Write(bytes []byte) (int, error) {
|
||||||
|
if r.status == 0 {
|
||||||
|
r.status = http.StatusOK
|
||||||
|
}
|
||||||
|
return r.respWriter.Write(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *responseWriter) WriteHeader(statusCode int) {
|
||||||
|
r.status = statusCode
|
||||||
|
r.respWriter.WriteHeader(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
httpReqType = reflect.TypeOf((*http.Request)(nil))
|
||||||
|
respWriterType = reflect.TypeOf((*http.ResponseWriter)(nil)).Elem()
|
||||||
|
cancelFuncType = reflect.TypeOf((*goctx.CancelFunc)(nil)).Elem()
|
||||||
|
)
|
||||||
|
|
||||||
|
// preCheckHandler checks whether the handler is valid, developers could get first-time feedback, all mistakes could be found at startup
|
||||||
|
func preCheckHandler(fn reflect.Value, argsIn []reflect.Value) {
|
||||||
|
hasStatusProvider := false
|
||||||
|
for _, argIn := range argsIn {
|
||||||
|
if _, hasStatusProvider = argIn.Interface().(ResponseStatusProvider); hasStatusProvider {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasStatusProvider {
|
||||||
|
panic(fmt.Sprintf("handler should have at least one ResponseStatusProvider argument, but got %s", fn.Type()))
|
||||||
|
}
|
||||||
|
if fn.Type().NumOut() != 0 && fn.Type().NumIn() != 1 {
|
||||||
|
panic(fmt.Sprintf("handler should have no return value or only one argument, but got %s", fn.Type()))
|
||||||
|
}
|
||||||
|
if fn.Type().NumOut() == 1 && fn.Type().Out(0) != cancelFuncType {
|
||||||
|
panic(fmt.Sprintf("handler should return a cancel function, but got %s", fn.Type()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareHandleArgsIn(resp http.ResponseWriter, req *http.Request, fn reflect.Value) []reflect.Value {
|
||||||
|
isPreCheck := req == nil
|
||||||
|
|
||||||
|
argsIn := make([]reflect.Value, fn.Type().NumIn())
|
||||||
|
for i := 0; i < fn.Type().NumIn(); i++ {
|
||||||
|
argTyp := fn.Type().In(i)
|
||||||
|
switch argTyp {
|
||||||
|
case respWriterType:
|
||||||
|
argsIn[i] = reflect.ValueOf(resp)
|
||||||
|
case httpReqType:
|
||||||
|
argsIn[i] = reflect.ValueOf(req)
|
||||||
|
default:
|
||||||
|
if argFn, ok := argTypeProvider[argTyp]; ok {
|
||||||
|
if isPreCheck {
|
||||||
|
argsIn[i] = reflect.ValueOf(&responseWriter{})
|
||||||
|
} else {
|
||||||
|
argsIn[i] = reflect.ValueOf(argFn(req))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic(fmt.Sprintf("unsupported argument type: %s", argTyp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return argsIn
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleResponse(fn reflect.Value, ret []reflect.Value) goctx.CancelFunc {
|
||||||
|
if len(ret) == 1 {
|
||||||
|
if cancelFunc, ok := ret[0].Interface().(goctx.CancelFunc); ok {
|
||||||
|
return cancelFunc
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("unsupported return type: %s", ret[0].Type()))
|
||||||
|
} else if len(ret) > 1 {
|
||||||
|
panic(fmt.Sprintf("unsupported return values: %s", fn.Type()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasResponseBeenWritten(argsIn []reflect.Value) bool {
|
||||||
|
for _, argIn := range argsIn {
|
||||||
|
if statusProvider, ok := argIn.Interface().(ResponseStatusProvider); ok {
|
||||||
|
if statusProvider.Written() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
if hp, ok := handler.(func(next http.Handler) http.Handler); ok {
|
||||||
|
return hp
|
||||||
|
}
|
||||||
|
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) {
|
||||||
|
// wrap the response writer to check whether the response has been written
|
||||||
|
resp := respOrig
|
||||||
|
if _, ok := resp.(ResponseStatusProvider); !ok {
|
||||||
|
resp = &responseWriter{respWriter: resp}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare the arguments for the handler and do pre-check
|
||||||
|
argsIn := prepareHandleArgsIn(resp, req, fn)
|
||||||
|
if req == nil {
|
||||||
|
preCheckHandler(fn, argsIn)
|
||||||
|
return // it's doing pre-check, just return
|
||||||
|
}
|
||||||
|
|
||||||
|
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||||
|
ret := fn.Call(argsIn)
|
||||||
|
|
||||||
|
// handle the return value, and defer the cancel function if there is one
|
||||||
|
cancelFunc := handleResponse(fn, ret)
|
||||||
|
if cancelFunc != nil {
|
||||||
|
defer cancelFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the response has not been written, call the next handler
|
||||||
|
if next != nil && !hasResponseBeenWritten(argsIn) {
|
||||||
|
next.ServeHTTP(resp, req)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
provider(nil).ServeHTTP(nil, nil) // do a pre-check to make sure all arguments and return values are supported
|
||||||
|
return provider
|
||||||
|
}
|
||||||
|
|
||||||
|
// MiddlewareWithPrefix wraps a handler function at a prefix, and make it as a middleware
|
||||||
|
// TODO: this design is incorrect, the asset handler should not be a middleware
|
||||||
|
func MiddlewareWithPrefix(pathPrefix string, middleware func(handler http.Handler) http.Handler, handlerFunc http.HandlerFunc) func(next http.Handler) http.Handler {
|
||||||
|
funcInfo := routing.GetFuncInfo(handlerFunc)
|
||||||
|
handler := http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||||
|
handlerFunc(resp, req)
|
||||||
|
})
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
if !strings.HasPrefix(req.URL.Path, pathPrefix) {
|
||||||
|
next.ServeHTTP(resp, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if middleware != nil {
|
||||||
|
middleware(handler).ServeHTTP(resp, req)
|
||||||
|
} else {
|
||||||
|
handler.ServeHTTP(resp, req)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,6 @@
|
||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
goctx "context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -17,13 +15,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bind binding an obj to a handler
|
// Bind binding an obj to a handler
|
||||||
func Bind[T any](obj T) http.HandlerFunc {
|
func Bind[T any](_ T) any {
|
||||||
return Wrap(func(ctx *context.Context) {
|
return func(ctx *context.Context) {
|
||||||
theObj := new(T) // create a new form obj for every request but not use obj directly
|
theObj := new(T) // create a new form obj for every request but not use obj directly
|
||||||
binding.Bind(ctx.Req, theObj)
|
binding.Bind(ctx.Req, theObj)
|
||||||
SetForm(ctx, theObj)
|
SetForm(ctx, theObj)
|
||||||
middleware.AssignForm(theObj, ctx.Data)
|
middleware.AssignForm(theObj, ctx.Data)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetForm set the form object
|
// SetForm set the form object
|
||||||
|
@ -56,21 +54,12 @@ func NewRoute() *Route {
|
||||||
// Use supports two middlewares
|
// Use supports two middlewares
|
||||||
func (r *Route) Use(middlewares ...interface{}) {
|
func (r *Route) Use(middlewares ...interface{}) {
|
||||||
if r.curGroupPrefix != "" {
|
if r.curGroupPrefix != "" {
|
||||||
|
// FIXME: this behavior is incorrect, should use "With" instead
|
||||||
r.curMiddlewares = append(r.curMiddlewares, middlewares...)
|
r.curMiddlewares = append(r.curMiddlewares, middlewares...)
|
||||||
} else {
|
} else {
|
||||||
for _, middle := range middlewares {
|
// FIXME: another misuse, the "Use" with empty middlewares is called after "Mount"
|
||||||
switch t := middle.(type) {
|
for _, m := range middlewares {
|
||||||
case func(http.Handler) http.Handler:
|
r.R.Use(toHandlerProvider(m))
|
||||||
r.R.Use(t)
|
|
||||||
case func(*context.Context):
|
|
||||||
r.R.Use(Middle(t))
|
|
||||||
case func(*context.Context) goctx.CancelFunc:
|
|
||||||
r.R.Use(MiddleCancel(t))
|
|
||||||
case func(*context.APIContext):
|
|
||||||
r.R.Use(MiddleAPI(t))
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("Unsupported middleware type: %#v", t))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,6 +88,32 @@ func (r *Route) getPattern(pattern string) string {
|
||||||
return strings.TrimSuffix(newPattern, "/")
|
return strings.TrimSuffix(newPattern, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Route) wrapMiddlewareAndHandler(h []any) ([]func(http.Handler) http.Handler, http.HandlerFunc) {
|
||||||
|
handlerProviders := make([]func(http.Handler) http.Handler, 0, len(r.curMiddlewares)+len(h))
|
||||||
|
for _, m := range r.curMiddlewares {
|
||||||
|
handlerProviders = append(handlerProviders, toHandlerProvider(m))
|
||||||
|
}
|
||||||
|
for _, m := range h {
|
||||||
|
handlerProviders = append(handlerProviders, toHandlerProvider(m))
|
||||||
|
}
|
||||||
|
middlewares := handlerProviders[:len(handlerProviders)-1]
|
||||||
|
handlerFunc := handlerProviders[len(handlerProviders)-1](nil).ServeHTTP
|
||||||
|
return middlewares, handlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) Methods(method, pattern string, h []any) {
|
||||||
|
middlewares, handlerFunc := r.wrapMiddlewareAndHandler(h)
|
||||||
|
fullPattern := r.getPattern(pattern)
|
||||||
|
if strings.Contains(method, ",") {
|
||||||
|
methods := strings.Split(method, ",")
|
||||||
|
for _, method := range methods {
|
||||||
|
r.R.With(middlewares...).Method(strings.TrimSpace(method), fullPattern, handlerFunc)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.R.With(middlewares...).Method(method, fullPattern, handlerFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mount attaches another Route along ./pattern/*
|
// Mount attaches another Route along ./pattern/*
|
||||||
func (r *Route) Mount(pattern string, subR *Route) {
|
func (r *Route) Mount(pattern string, subR *Route) {
|
||||||
middlewares := make([]interface{}, len(r.curMiddlewares))
|
middlewares := make([]interface{}, len(r.curMiddlewares))
|
||||||
|
@ -109,81 +124,53 @@ func (r *Route) Mount(pattern string, subR *Route) {
|
||||||
|
|
||||||
// Any delegate requests for all methods
|
// Any delegate requests for all methods
|
||||||
func (r *Route) Any(pattern string, h ...interface{}) {
|
func (r *Route) Any(pattern string, h ...interface{}) {
|
||||||
middlewares := r.getMiddlewares(h)
|
middlewares, handlerFunc := r.wrapMiddlewareAndHandler(h)
|
||||||
r.R.HandleFunc(r.getPattern(pattern), Wrap(middlewares...))
|
r.R.With(middlewares...).HandleFunc(r.getPattern(pattern), handlerFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route delegate special methods
|
// RouteMethods delegate special methods, it is an alias of "Methods", while the "pattern" is the first parameter
|
||||||
func (r *Route) Route(pattern, methods string, h ...interface{}) {
|
func (r *Route) RouteMethods(pattern, methods string, h ...interface{}) {
|
||||||
p := r.getPattern(pattern)
|
r.Methods(methods, pattern, h)
|
||||||
ms := strings.Split(methods, ",")
|
|
||||||
middlewares := r.getMiddlewares(h)
|
|
||||||
for _, method := range ms {
|
|
||||||
r.R.MethodFunc(strings.TrimSpace(method), p, Wrap(middlewares...))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete delegate delete method
|
// Delete delegate delete method
|
||||||
func (r *Route) Delete(pattern string, h ...interface{}) {
|
func (r *Route) Delete(pattern string, h ...interface{}) {
|
||||||
middlewares := r.getMiddlewares(h)
|
r.Methods("DELETE", pattern, h)
|
||||||
r.R.Delete(r.getPattern(pattern), Wrap(middlewares...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Route) getMiddlewares(h []interface{}) []interface{} {
|
|
||||||
middlewares := make([]interface{}, len(r.curMiddlewares), len(r.curMiddlewares)+len(h))
|
|
||||||
copy(middlewares, r.curMiddlewares)
|
|
||||||
middlewares = append(middlewares, h...)
|
|
||||||
return middlewares
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get delegate get method
|
// Get delegate get method
|
||||||
func (r *Route) Get(pattern string, h ...interface{}) {
|
func (r *Route) Get(pattern string, h ...interface{}) {
|
||||||
middlewares := r.getMiddlewares(h)
|
r.Methods("GET", pattern, h)
|
||||||
r.R.Get(r.getPattern(pattern), Wrap(middlewares...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options delegate options method
|
|
||||||
func (r *Route) Options(pattern string, h ...interface{}) {
|
|
||||||
middlewares := r.getMiddlewares(h)
|
|
||||||
r.R.Options(r.getPattern(pattern), Wrap(middlewares...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOptions delegate get and options method
|
// GetOptions delegate get and options method
|
||||||
func (r *Route) GetOptions(pattern string, h ...interface{}) {
|
func (r *Route) GetOptions(pattern string, h ...interface{}) {
|
||||||
middlewares := r.getMiddlewares(h)
|
r.Methods("GET,OPTIONS", pattern, h)
|
||||||
r.R.Get(r.getPattern(pattern), Wrap(middlewares...))
|
|
||||||
r.R.Options(r.getPattern(pattern), Wrap(middlewares...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostOptions delegate post and options method
|
// PostOptions delegate post and options method
|
||||||
func (r *Route) PostOptions(pattern string, h ...interface{}) {
|
func (r *Route) PostOptions(pattern string, h ...interface{}) {
|
||||||
middlewares := r.getMiddlewares(h)
|
r.Methods("POST,OPTIONS", pattern, h)
|
||||||
r.R.Post(r.getPattern(pattern), Wrap(middlewares...))
|
|
||||||
r.R.Options(r.getPattern(pattern), Wrap(middlewares...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Head delegate head method
|
// Head delegate head method
|
||||||
func (r *Route) Head(pattern string, h ...interface{}) {
|
func (r *Route) Head(pattern string, h ...interface{}) {
|
||||||
middlewares := r.getMiddlewares(h)
|
r.Methods("HEAD", pattern, h)
|
||||||
r.R.Head(r.getPattern(pattern), Wrap(middlewares...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post delegate post method
|
// Post delegate post method
|
||||||
func (r *Route) Post(pattern string, h ...interface{}) {
|
func (r *Route) Post(pattern string, h ...interface{}) {
|
||||||
middlewares := r.getMiddlewares(h)
|
r.Methods("POST", pattern, h)
|
||||||
r.R.Post(r.getPattern(pattern), Wrap(middlewares...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put delegate put method
|
// Put delegate put method
|
||||||
func (r *Route) Put(pattern string, h ...interface{}) {
|
func (r *Route) Put(pattern string, h ...interface{}) {
|
||||||
middlewares := r.getMiddlewares(h)
|
r.Methods("PUT", pattern, h)
|
||||||
r.R.Put(r.getPattern(pattern), Wrap(middlewares...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch delegate patch method
|
// Patch delegate patch method
|
||||||
func (r *Route) Patch(pattern string, h ...interface{}) {
|
func (r *Route) Patch(pattern string, h ...interface{}) {
|
||||||
middlewares := r.getMiddlewares(h)
|
r.Methods("PATCH", pattern, h)
|
||||||
r.R.Patch(r.getPattern(pattern), Wrap(middlewares...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP implements http.Handler
|
// ServeHTTP implements http.Handler
|
||||||
|
@ -191,19 +178,12 @@ func (r *Route) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
r.R.ServeHTTP(w, req)
|
r.R.ServeHTTP(w, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotFound defines a handler to respond whenever a route could
|
// NotFound defines a handler to respond whenever a route could not be found.
|
||||||
// not be found.
|
|
||||||
func (r *Route) NotFound(h http.HandlerFunc) {
|
func (r *Route) NotFound(h http.HandlerFunc) {
|
||||||
r.R.NotFound(h)
|
r.R.NotFound(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MethodNotAllowed defines a handler to respond whenever a method is
|
// Combo delegates requests to Combo
|
||||||
// not allowed.
|
|
||||||
func (r *Route) MethodNotAllowed(h http.HandlerFunc) {
|
|
||||||
r.R.MethodNotAllowed(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combo deletegate requests to Combo
|
|
||||||
func (r *Route) Combo(pattern string, h ...interface{}) *Combo {
|
func (r *Route) Combo(pattern string, h ...interface{}) *Combo {
|
||||||
return &Combo{r, pattern, h}
|
return &Combo{r, pattern, h}
|
||||||
}
|
}
|
||||||
|
@ -215,31 +195,31 @@ type Combo struct {
|
||||||
h []interface{}
|
h []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get deletegate Get method
|
// Get delegates Get method
|
||||||
func (c *Combo) Get(h ...interface{}) *Combo {
|
func (c *Combo) Get(h ...interface{}) *Combo {
|
||||||
c.r.Get(c.pattern, append(c.h, h...)...)
|
c.r.Get(c.pattern, append(c.h, h...)...)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post deletegate Post method
|
// Post delegates Post method
|
||||||
func (c *Combo) Post(h ...interface{}) *Combo {
|
func (c *Combo) Post(h ...interface{}) *Combo {
|
||||||
c.r.Post(c.pattern, append(c.h, h...)...)
|
c.r.Post(c.pattern, append(c.h, h...)...)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletegate Delete method
|
// Delete delegates Delete method
|
||||||
func (c *Combo) Delete(h ...interface{}) *Combo {
|
func (c *Combo) Delete(h ...interface{}) *Combo {
|
||||||
c.r.Delete(c.pattern, append(c.h, h...)...)
|
c.r.Delete(c.pattern, append(c.h, h...)...)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put deletegate Put method
|
// Put delegates Put method
|
||||||
func (c *Combo) Put(h ...interface{}) *Combo {
|
func (c *Combo) Put(h ...interface{}) *Combo {
|
||||||
c.r.Put(c.pattern, append(c.h, h...)...)
|
c.r.Put(c.pattern, append(c.h, h...)...)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch deletegate Patch method
|
// Patch delegates Patch method
|
||||||
func (c *Combo) Patch(h ...interface{}) *Combo {
|
func (c *Combo) Patch(h ...interface{}) *Combo {
|
||||||
c.r.Patch(c.pattern, append(c.h, h...)...)
|
c.r.Patch(c.pattern, append(c.h, h...)...)
|
||||||
return c
|
return c
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
chi "github.com/go-chi/chi/v5"
|
chi "github.com/go-chi/chi/v5"
|
||||||
|
@ -39,7 +40,7 @@ func TestRoute2(t *testing.T) {
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
recorder.Body = buff
|
recorder.Body = buff
|
||||||
|
|
||||||
var route int
|
hit := -1
|
||||||
|
|
||||||
r := NewRoute()
|
r := NewRoute()
|
||||||
r.Group("/{username}/{reponame}", func() {
|
r.Group("/{username}/{reponame}", func() {
|
||||||
|
@ -51,7 +52,7 @@ func TestRoute2(t *testing.T) {
|
||||||
assert.EqualValues(t, "gitea", reponame)
|
assert.EqualValues(t, "gitea", reponame)
|
||||||
tp := chi.URLParam(req, "type")
|
tp := chi.URLParam(req, "type")
|
||||||
assert.EqualValues(t, "issues", tp)
|
assert.EqualValues(t, "issues", tp)
|
||||||
route = 0
|
hit = 0
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Get("/{type:issues|pulls}/{index}", func(resp http.ResponseWriter, req *http.Request) {
|
r.Get("/{type:issues|pulls}/{index}", func(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -63,10 +64,13 @@ func TestRoute2(t *testing.T) {
|
||||||
assert.EqualValues(t, "issues", tp)
|
assert.EqualValues(t, "issues", tp)
|
||||||
index := chi.URLParam(req, "index")
|
index := chi.URLParam(req, "index")
|
||||||
assert.EqualValues(t, "1", index)
|
assert.EqualValues(t, "1", index)
|
||||||
route = 1
|
hit = 1
|
||||||
})
|
})
|
||||||
}, func(resp http.ResponseWriter, req *http.Request) {
|
}, func(resp http.ResponseWriter, req *http.Request) {
|
||||||
resp.WriteHeader(http.StatusOK)
|
if stop, err := strconv.Atoi(req.FormValue("stop")); err == nil {
|
||||||
|
hit = stop
|
||||||
|
resp.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Group("/issues/{index}", func() {
|
r.Group("/issues/{index}", func() {
|
||||||
|
@ -77,7 +81,7 @@ func TestRoute2(t *testing.T) {
|
||||||
assert.EqualValues(t, "gitea", reponame)
|
assert.EqualValues(t, "gitea", reponame)
|
||||||
index := chi.URLParam(req, "index")
|
index := chi.URLParam(req, "index")
|
||||||
assert.EqualValues(t, "1", index)
|
assert.EqualValues(t, "1", index)
|
||||||
route = 2
|
hit = 2
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -86,19 +90,25 @@ func TestRoute2(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
r.ServeHTTP(recorder, req)
|
r.ServeHTTP(recorder, req)
|
||||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||||
assert.EqualValues(t, 0, route)
|
assert.EqualValues(t, 0, hit)
|
||||||
|
|
||||||
req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1", nil)
|
req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1", nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
r.ServeHTTP(recorder, req)
|
r.ServeHTTP(recorder, req)
|
||||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||||
assert.EqualValues(t, 1, route)
|
assert.EqualValues(t, 1, hit)
|
||||||
|
|
||||||
|
req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1?stop=100", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
r.ServeHTTP(recorder, req)
|
||||||
|
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||||
|
assert.EqualValues(t, 100, hit)
|
||||||
|
|
||||||
req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1/view", nil)
|
req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1/view", nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
r.ServeHTTP(recorder, req)
|
r.ServeHTTP(recorder, req)
|
||||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||||
assert.EqualValues(t, 2, route)
|
assert.EqualValues(t, 2, hit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRoute3(t *testing.T) {
|
func TestRoute3(t *testing.T) {
|
||||||
|
@ -106,7 +116,7 @@ func TestRoute3(t *testing.T) {
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
recorder.Body = buff
|
recorder.Body = buff
|
||||||
|
|
||||||
var route int
|
hit := -1
|
||||||
|
|
||||||
m := NewRoute()
|
m := NewRoute()
|
||||||
r := NewRoute()
|
r := NewRoute()
|
||||||
|
@ -116,20 +126,20 @@ func TestRoute3(t *testing.T) {
|
||||||
m.Group("/{username}/{reponame}", func() {
|
m.Group("/{username}/{reponame}", func() {
|
||||||
m.Group("/branch_protections", func() {
|
m.Group("/branch_protections", func() {
|
||||||
m.Get("", func(resp http.ResponseWriter, req *http.Request) {
|
m.Get("", func(resp http.ResponseWriter, req *http.Request) {
|
||||||
route = 0
|
hit = 0
|
||||||
})
|
})
|
||||||
m.Post("", func(resp http.ResponseWriter, req *http.Request) {
|
m.Post("", func(resp http.ResponseWriter, req *http.Request) {
|
||||||
route = 1
|
hit = 1
|
||||||
})
|
})
|
||||||
m.Group("/{name}", func() {
|
m.Group("/{name}", func() {
|
||||||
m.Get("", func(resp http.ResponseWriter, req *http.Request) {
|
m.Get("", func(resp http.ResponseWriter, req *http.Request) {
|
||||||
route = 2
|
hit = 2
|
||||||
})
|
})
|
||||||
m.Patch("", func(resp http.ResponseWriter, req *http.Request) {
|
m.Patch("", func(resp http.ResponseWriter, req *http.Request) {
|
||||||
route = 3
|
hit = 3
|
||||||
})
|
})
|
||||||
m.Delete("", func(resp http.ResponseWriter, req *http.Request) {
|
m.Delete("", func(resp http.ResponseWriter, req *http.Request) {
|
||||||
route = 4
|
hit = 4
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -140,29 +150,29 @@ func TestRoute3(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
r.ServeHTTP(recorder, req)
|
r.ServeHTTP(recorder, req)
|
||||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||||
assert.EqualValues(t, 0, route)
|
assert.EqualValues(t, 0, hit)
|
||||||
|
|
||||||
req, err = http.NewRequest("POST", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections", nil)
|
req, err = http.NewRequest("POST", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections", nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
r.ServeHTTP(recorder, req)
|
r.ServeHTTP(recorder, req)
|
||||||
assert.EqualValues(t, http.StatusOK, recorder.Code, http.StatusOK)
|
assert.EqualValues(t, http.StatusOK, recorder.Code, http.StatusOK)
|
||||||
assert.EqualValues(t, 1, route)
|
assert.EqualValues(t, 1, hit)
|
||||||
|
|
||||||
req, err = http.NewRequest("GET", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
|
req, err = http.NewRequest("GET", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
r.ServeHTTP(recorder, req)
|
r.ServeHTTP(recorder, req)
|
||||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||||
assert.EqualValues(t, 2, route)
|
assert.EqualValues(t, 2, hit)
|
||||||
|
|
||||||
req, err = http.NewRequest("PATCH", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
|
req, err = http.NewRequest("PATCH", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
r.ServeHTTP(recorder, req)
|
r.ServeHTTP(recorder, req)
|
||||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||||
assert.EqualValues(t, 3, route)
|
assert.EqualValues(t, 3, hit)
|
||||||
|
|
||||||
req, err = http.NewRequest("DELETE", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
|
req, err = http.NewRequest("DELETE", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
r.ServeHTTP(recorder, req)
|
r.ServeHTTP(recorder, req)
|
||||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||||
assert.EqualValues(t, 4, route)
|
assert.EqualValues(t, 4, hit)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
goctx "context"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/context"
|
|
||||||
"code.gitea.io/gitea/modules/web/routing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wrap converts all kinds of routes to standard library one
|
|
||||||
func Wrap(handlers ...interface{}) http.HandlerFunc {
|
|
||||||
if len(handlers) == 0 {
|
|
||||||
panic("No handlers found")
|
|
||||||
}
|
|
||||||
|
|
||||||
ourHandlers := make([]wrappedHandlerFunc, 0, len(handlers))
|
|
||||||
|
|
||||||
for _, handler := range handlers {
|
|
||||||
ourHandlers = append(ourHandlers, convertHandler(handler))
|
|
||||||
}
|
|
||||||
return wrapInternal(ourHandlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapInternal(handlers []wrappedHandlerFunc) http.HandlerFunc {
|
|
||||||
return func(resp http.ResponseWriter, req *http.Request) {
|
|
||||||
var defers []func()
|
|
||||||
defer func() {
|
|
||||||
for i := len(defers) - 1; i >= 0; i-- {
|
|
||||||
defers[i]()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for i := 0; i < len(handlers); i++ {
|
|
||||||
handler := handlers[i]
|
|
||||||
others := handlers[i+1:]
|
|
||||||
done, deferrable := handler(resp, req, others...)
|
|
||||||
if deferrable != nil {
|
|
||||||
defers = append(defers, deferrable)
|
|
||||||
}
|
|
||||||
if done {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middle wrap a context function as a chi middleware
|
|
||||||
func Middle(f func(ctx *context.Context)) func(next http.Handler) http.Handler {
|
|
||||||
funcInfo := routing.GetFuncInfo(f)
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
ctx := context.GetContext(req)
|
|
||||||
f(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MiddleCancel wrap a context function as a chi middleware
|
|
||||||
func MiddleCancel(f func(ctx *context.Context) goctx.CancelFunc) func(netx http.Handler) http.Handler {
|
|
||||||
funcInfo := routing.GetFuncInfo(f)
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
ctx := context.GetContext(req)
|
|
||||||
cancel := f(ctx)
|
|
||||||
if cancel != nil {
|
|
||||||
defer cancel()
|
|
||||||
}
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MiddleAPI wrap a context function as a chi middleware
|
|
||||||
func MiddleAPI(f func(ctx *context.APIContext)) func(next http.Handler) http.Handler {
|
|
||||||
funcInfo := routing.GetFuncInfo(f)
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
ctx := context.GetAPIContext(req)
|
|
||||||
f(ctx)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapWithPrefix wraps a provided handler function at a prefix
|
|
||||||
func WrapWithPrefix(pathPrefix string, handler http.HandlerFunc, friendlyName ...string) func(next http.Handler) http.Handler {
|
|
||||||
funcInfo := routing.GetFuncInfo(handler, friendlyName...)
|
|
||||||
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
|
||||||
if !strings.HasPrefix(req.URL.Path, pathPrefix) {
|
|
||||||
next.ServeHTTP(resp, req)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
handler(resp, req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
goctx "context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/context"
|
|
||||||
"code.gitea.io/gitea/modules/web/routing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type wrappedHandlerFunc func(resp http.ResponseWriter, req *http.Request, others ...wrappedHandlerFunc) (done bool, deferrable func())
|
|
||||||
|
|
||||||
func convertHandler(handler interface{}) wrappedHandlerFunc {
|
|
||||||
funcInfo := routing.GetFuncInfo(handler)
|
|
||||||
switch t := handler.(type) {
|
|
||||||
case http.HandlerFunc:
|
|
||||||
return func(resp http.ResponseWriter, req *http.Request, others ...wrappedHandlerFunc) (done bool, deferrable func()) {
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
if _, ok := resp.(context.ResponseWriter); !ok {
|
|
||||||
resp = context.NewResponse(resp)
|
|
||||||
}
|
|
||||||
t(resp, req)
|
|
||||||
if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
|
|
||||||
done = true
|
|
||||||
}
|
|
||||||
return done, deferrable
|
|
||||||
}
|
|
||||||
case func(http.ResponseWriter, *http.Request):
|
|
||||||
return func(resp http.ResponseWriter, req *http.Request, others ...wrappedHandlerFunc) (done bool, deferrable func()) {
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
t(resp, req)
|
|
||||||
if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
|
|
||||||
done = true
|
|
||||||
}
|
|
||||||
return done, deferrable
|
|
||||||
}
|
|
||||||
|
|
||||||
case func(ctx *context.Context):
|
|
||||||
return func(resp http.ResponseWriter, req *http.Request, others ...wrappedHandlerFunc) (done bool, deferrable func()) {
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
ctx := context.GetContext(req)
|
|
||||||
t(ctx)
|
|
||||||
done = ctx.Written()
|
|
||||||
return done, deferrable
|
|
||||||
}
|
|
||||||
case func(ctx *context.Context) goctx.CancelFunc:
|
|
||||||
return func(resp http.ResponseWriter, req *http.Request, others ...wrappedHandlerFunc) (done bool, deferrable func()) {
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
ctx := context.GetContext(req)
|
|
||||||
deferrable = t(ctx)
|
|
||||||
done = ctx.Written()
|
|
||||||
return done, deferrable
|
|
||||||
}
|
|
||||||
case func(*context.APIContext):
|
|
||||||
return func(resp http.ResponseWriter, req *http.Request, others ...wrappedHandlerFunc) (done bool, deferrable func()) {
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
ctx := context.GetAPIContext(req)
|
|
||||||
t(ctx)
|
|
||||||
done = ctx.Written()
|
|
||||||
return done, deferrable
|
|
||||||
}
|
|
||||||
case func(*context.APIContext) goctx.CancelFunc:
|
|
||||||
return func(resp http.ResponseWriter, req *http.Request, others ...wrappedHandlerFunc) (done bool, deferrable func()) {
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
ctx := context.GetAPIContext(req)
|
|
||||||
deferrable = t(ctx)
|
|
||||||
done = ctx.Written()
|
|
||||||
return done, deferrable
|
|
||||||
}
|
|
||||||
case func(*context.PrivateContext):
|
|
||||||
return func(resp http.ResponseWriter, req *http.Request, others ...wrappedHandlerFunc) (done bool, deferrable func()) {
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
ctx := context.GetPrivateContext(req)
|
|
||||||
t(ctx)
|
|
||||||
done = ctx.Written()
|
|
||||||
return done, deferrable
|
|
||||||
}
|
|
||||||
case func(*context.PrivateContext) goctx.CancelFunc:
|
|
||||||
return func(resp http.ResponseWriter, req *http.Request, others ...wrappedHandlerFunc) (done bool, deferrable func()) {
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
ctx := context.GetPrivateContext(req)
|
|
||||||
deferrable = t(ctx)
|
|
||||||
done = ctx.Written()
|
|
||||||
return done, deferrable
|
|
||||||
}
|
|
||||||
case func(http.Handler) http.Handler:
|
|
||||||
return func(resp http.ResponseWriter, req *http.Request, others ...wrappedHandlerFunc) (done bool, deferrable func()) {
|
|
||||||
next := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})
|
|
||||||
if len(others) > 0 {
|
|
||||||
next = wrapInternal(others)
|
|
||||||
}
|
|
||||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
|
||||||
if _, ok := resp.(context.ResponseWriter); !ok {
|
|
||||||
resp = context.NewResponse(resp)
|
|
||||||
}
|
|
||||||
t(next).ServeHTTP(resp, req)
|
|
||||||
if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
|
|
||||||
done = true
|
|
||||||
}
|
|
||||||
return done, deferrable
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("Unsupported handler type: %#v", t))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -480,7 +480,7 @@ func ContainerRoutes(ctx gocontext.Context) *web.Route {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manual mapping of routes because {image} can contain slashes which chi does not support
|
// Manual mapping of routes because {image} can contain slashes which chi does not support
|
||||||
r.Route("/*", "HEAD,GET,POST,PUT,PATCH,DELETE", func(ctx *context.Context) {
|
r.RouteMethods("/*", "HEAD,GET,POST,PUT,PATCH,DELETE", func(ctx *context.Context) {
|
||||||
path := ctx.Params("*")
|
path := ctx.Params("*")
|
||||||
isHead := ctx.Req.Method == "HEAD"
|
isHead := ctx.Req.Method == "HEAD"
|
||||||
isGet := ctx.Req.Method == "GET"
|
isGet := ctx.Req.Method == "GET"
|
||||||
|
|
|
@ -634,8 +634,8 @@ func mustEnableAttachments(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// bind binding an obj to a func(ctx *context.APIContext)
|
// bind binding an obj to a func(ctx *context.APIContext)
|
||||||
func bind[T any](obj T) http.HandlerFunc {
|
func bind[T any](_ T) any {
|
||||||
return web.Wrap(func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
theObj := new(T) // create a new form obj for every request but not use obj directly
|
theObj := new(T) // create a new form obj for every request but not use obj directly
|
||||||
errs := binding.Bind(ctx.Req, theObj)
|
errs := binding.Bind(ctx.Req, theObj)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
|
@ -643,7 +643,7 @@ func bind[T any](obj T) http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
web.SetForm(ctx, theObj)
|
web.SetForm(ctx, theObj)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored
|
// The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored
|
||||||
|
|
|
@ -81,14 +81,14 @@ func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routes registers the install routes
|
// Routes registers the installation routes
|
||||||
func Routes(ctx goctx.Context) *web.Route {
|
func Routes(ctx goctx.Context) *web.Route {
|
||||||
r := web.NewRoute()
|
r := web.NewRoute()
|
||||||
for _, middle := range common.Middlewares() {
|
for _, middle := range common.Middlewares() {
|
||||||
r.Use(middle)
|
r.Use(middle)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Use(web.WrapWithPrefix("/assets/", public.AssetsHandlerFunc("/assets/"), "AssetsHandler"))
|
r.Use(web.MiddlewareWithPrefix("/assets/", nil, public.AssetsHandlerFunc("/assets/")))
|
||||||
|
|
||||||
r.Use(session.Sessioner(session.Options{
|
r.Use(session.Sessioner(session.Options{
|
||||||
Provider: setting.SessionConfig.Provider,
|
Provider: setting.SessionConfig.Provider,
|
||||||
|
@ -109,7 +109,7 @@ func Routes(ctx goctx.Context) *web.Route {
|
||||||
r.Get("/post-install", InstallDone)
|
r.Get("/post-install", InstallDone)
|
||||||
r.Get("/api/healthz", healthcheck.Check)
|
r.Get("/api/healthz", healthcheck.Check)
|
||||||
|
|
||||||
r.NotFound(web.Wrap(installNotFound))
|
r.NotFound(installNotFound)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,12 +38,12 @@ func CheckInternalToken(next http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// bind binding an obj to a handler
|
// bind binding an obj to a handler
|
||||||
func bind[T any](obj T) http.HandlerFunc {
|
func bind[T any](_ T) any {
|
||||||
return web.Wrap(func(ctx *context.PrivateContext) {
|
return func(ctx *context.PrivateContext) {
|
||||||
theObj := new(T) // create a new form obj for every request but not use obj directly
|
theObj := new(T) // create a new form obj for every request but not use obj directly
|
||||||
binding.Bind(ctx.Req, theObj)
|
binding.Bind(ctx.Req, theObj)
|
||||||
web.SetForm(ctx, theObj)
|
web.SetForm(ctx, theObj)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routes registers all internal APIs routes to web application.
|
// Routes registers all internal APIs routes to web application.
|
||||||
|
|
|
@ -103,7 +103,7 @@ func buildAuthGroup() *auth_service.Group {
|
||||||
func Routes(ctx gocontext.Context) *web.Route {
|
func Routes(ctx gocontext.Context) *web.Route {
|
||||||
routes := web.NewRoute()
|
routes := web.NewRoute()
|
||||||
|
|
||||||
routes.Use(web.WrapWithPrefix("/assets/", web.Wrap(CorsHandler(), public.AssetsHandlerFunc("/assets/")), "AssetsHandler"))
|
routes.Use(web.MiddlewareWithPrefix("/assets/", CorsHandler(), public.AssetsHandlerFunc("/assets/")))
|
||||||
|
|
||||||
sessioner := session.Sessioner(session.Options{
|
sessioner := session.Sessioner(session.Options{
|
||||||
Provider: setting.SessionConfig.Provider,
|
Provider: setting.SessionConfig.Provider,
|
||||||
|
@ -123,8 +123,8 @@ func Routes(ctx gocontext.Context) *web.Route {
|
||||||
routes.Use(Recovery(ctx))
|
routes.Use(Recovery(ctx))
|
||||||
|
|
||||||
// We use r.Route here over r.Use because this prevents requests that are not for avatars having to go through this additional handler
|
// We use r.Route here over r.Use because this prevents requests that are not for avatars having to go through this additional handler
|
||||||
routes.Route("/avatars/*", "GET, HEAD", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
|
routes.RouteMethods("/avatars/*", "GET, HEAD", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
|
||||||
routes.Route("/repo-avatars/*", "GET, HEAD", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
|
routes.RouteMethods("/repo-avatars/*", "GET, HEAD", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
|
||||||
|
|
||||||
// for health check - doesn't need to be passed through gzip handler
|
// for health check - doesn't need to be passed through gzip handler
|
||||||
routes.Head("/", func(w http.ResponseWriter, req *http.Request) {
|
routes.Head("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -153,7 +153,7 @@ func Routes(ctx gocontext.Context) *web.Route {
|
||||||
|
|
||||||
if setting.Service.EnableCaptcha {
|
if setting.Service.EnableCaptcha {
|
||||||
// The captcha http.Handler should only fire on /captcha/* so we can just mount this on that url
|
// The captcha http.Handler should only fire on /captcha/* so we can just mount this on that url
|
||||||
routes.Route("/captcha/*", "GET,HEAD", append(common, captcha.Captchaer(context.GetImageCaptcha()))...)
|
routes.RouteMethods("/captcha/*", "GET,HEAD", append(common, captcha.Captchaer(context.GetImageCaptcha()))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.HasRobotsTxt {
|
if setting.HasRobotsTxt {
|
||||||
|
@ -856,7 +856,7 @@ func RegisterRoutes(m *web.Route) {
|
||||||
m.Post("/delete", org.SecretsDelete)
|
m.Post("/delete", org.SecretsDelete)
|
||||||
})
|
})
|
||||||
|
|
||||||
m.Route("/delete", "GET,POST", org.SettingsDelete)
|
m.RouteMethods("/delete", "GET,POST", org.SettingsDelete)
|
||||||
|
|
||||||
m.Group("/packages", func() {
|
m.Group("/packages", func() {
|
||||||
m.Get("", org.Packages)
|
m.Get("", org.Packages)
|
||||||
|
|
Loading…
Reference in a new issue