Skip to content

Commit a4c41bb

Browse files
committed
Merge remote-tracking branch 'giteaofficial/main'
* giteaofficial/main: Fix label color, fix divider in dropdown (go-gitea#24215) [skip ci] Updated translations via Crowdin Refactor web route (go-gitea#24080) Fix unclear "Owner" concept (go-gitea#24233) Introduce eslint-plugin-no-jquery/no-event-shorthand (go-gitea#24198) Use secondary pointing menu for tabs on user/organization home page (go-gitea#24162)
2 parents 0b7bce9 + 948a9ee commit a4c41bb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+661
-508
lines changed

.eslintrc.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ plugins:
1212
- eslint-plugin-unicorn
1313
- eslint-plugin-import
1414
- eslint-plugin-jquery
15+
- eslint-plugin-no-jquery
1516
- eslint-plugin-sonarjs
1617
- eslint-plugin-custom-elements
1718

@@ -192,6 +193,7 @@ rules:
192193
jquery/no-val: [0]
193194
jquery/no-when: [2]
194195
jquery/no-wrap: [2]
196+
no-jquery/no-event-shorthand: [2]
195197
key-spacing: [2]
196198
keyword-spacing: [2]
197199
line-comment-position: [0]

modules/web/handler.go

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package web
5+
6+
import (
7+
goctx "context"
8+
"fmt"
9+
"net/http"
10+
"reflect"
11+
"strings"
12+
13+
"code.gitea.io/gitea/modules/context"
14+
"code.gitea.io/gitea/modules/web/routing"
15+
)
16+
17+
// ResponseStatusProvider is an interface to check whether the response has been written by the handler
18+
type ResponseStatusProvider interface {
19+
Written() bool
20+
}
21+
22+
// TODO: decouple this from the context package, let the context package register these providers
23+
var argTypeProvider = map[reflect.Type]func(req *http.Request) ResponseStatusProvider{
24+
reflect.TypeOf(&context.APIContext{}): func(req *http.Request) ResponseStatusProvider { return context.GetAPIContext(req) },
25+
reflect.TypeOf(&context.Context{}): func(req *http.Request) ResponseStatusProvider { return context.GetContext(req) },
26+
reflect.TypeOf(&context.PrivateContext{}): func(req *http.Request) ResponseStatusProvider { return context.GetPrivateContext(req) },
27+
}
28+
29+
// responseWriter is a wrapper of http.ResponseWriter, to check whether the response has been written
30+
type responseWriter struct {
31+
respWriter http.ResponseWriter
32+
status int
33+
}
34+
35+
var _ ResponseStatusProvider = (*responseWriter)(nil)
36+
37+
func (r *responseWriter) Written() bool {
38+
return r.status > 0
39+
}
40+
41+
func (r *responseWriter) Header() http.Header {
42+
return r.respWriter.Header()
43+
}
44+
45+
func (r *responseWriter) Write(bytes []byte) (int, error) {
46+
if r.status == 0 {
47+
r.status = http.StatusOK
48+
}
49+
return r.respWriter.Write(bytes)
50+
}
51+
52+
func (r *responseWriter) WriteHeader(statusCode int) {
53+
r.status = statusCode
54+
r.respWriter.WriteHeader(statusCode)
55+
}
56+
57+
var (
58+
httpReqType = reflect.TypeOf((*http.Request)(nil))
59+
respWriterType = reflect.TypeOf((*http.ResponseWriter)(nil)).Elem()
60+
cancelFuncType = reflect.TypeOf((*goctx.CancelFunc)(nil)).Elem()
61+
)
62+
63+
// preCheckHandler checks whether the handler is valid, developers could get first-time feedback, all mistakes could be found at startup
64+
func preCheckHandler(fn reflect.Value, argsIn []reflect.Value) {
65+
hasStatusProvider := false
66+
for _, argIn := range argsIn {
67+
if _, hasStatusProvider = argIn.Interface().(ResponseStatusProvider); hasStatusProvider {
68+
break
69+
}
70+
}
71+
if !hasStatusProvider {
72+
panic(fmt.Sprintf("handler should have at least one ResponseStatusProvider argument, but got %s", fn.Type()))
73+
}
74+
if fn.Type().NumOut() != 0 && fn.Type().NumIn() != 1 {
75+
panic(fmt.Sprintf("handler should have no return value or only one argument, but got %s", fn.Type()))
76+
}
77+
if fn.Type().NumOut() == 1 && fn.Type().Out(0) != cancelFuncType {
78+
panic(fmt.Sprintf("handler should return a cancel function, but got %s", fn.Type()))
79+
}
80+
}
81+
82+
func prepareHandleArgsIn(resp http.ResponseWriter, req *http.Request, fn reflect.Value) []reflect.Value {
83+
isPreCheck := req == nil
84+
85+
argsIn := make([]reflect.Value, fn.Type().NumIn())
86+
for i := 0; i < fn.Type().NumIn(); i++ {
87+
argTyp := fn.Type().In(i)
88+
switch argTyp {
89+
case respWriterType:
90+
argsIn[i] = reflect.ValueOf(resp)
91+
case httpReqType:
92+
argsIn[i] = reflect.ValueOf(req)
93+
default:
94+
if argFn, ok := argTypeProvider[argTyp]; ok {
95+
if isPreCheck {
96+
argsIn[i] = reflect.ValueOf(&responseWriter{})
97+
} else {
98+
argsIn[i] = reflect.ValueOf(argFn(req))
99+
}
100+
} else {
101+
panic(fmt.Sprintf("unsupported argument type: %s", argTyp))
102+
}
103+
}
104+
}
105+
return argsIn
106+
}
107+
108+
func handleResponse(fn reflect.Value, ret []reflect.Value) goctx.CancelFunc {
109+
if len(ret) == 1 {
110+
if cancelFunc, ok := ret[0].Interface().(goctx.CancelFunc); ok {
111+
return cancelFunc
112+
}
113+
panic(fmt.Sprintf("unsupported return type: %s", ret[0].Type()))
114+
} else if len(ret) > 1 {
115+
panic(fmt.Sprintf("unsupported return values: %s", fn.Type()))
116+
}
117+
return nil
118+
}
119+
120+
func hasResponseBeenWritten(argsIn []reflect.Value) bool {
121+
for _, argIn := range argsIn {
122+
if statusProvider, ok := argIn.Interface().(ResponseStatusProvider); ok {
123+
if statusProvider.Written() {
124+
return true
125+
}
126+
}
127+
}
128+
return false
129+
}
130+
131+
// toHandlerProvider converts a handler to a handler provider
132+
// A handler provider is a function that takes a "next" http.Handler, it can be used as a middleware
133+
func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
134+
if hp, ok := handler.(func(next http.Handler) http.Handler); ok {
135+
return hp
136+
}
137+
138+
funcInfo := routing.GetFuncInfo(handler)
139+
fn := reflect.ValueOf(handler)
140+
if fn.Type().Kind() != reflect.Func {
141+
panic(fmt.Sprintf("handler must be a function, but got %s", fn.Type()))
142+
}
143+
144+
provider := func(next http.Handler) http.Handler {
145+
return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) {
146+
// wrap the response writer to check whether the response has been written
147+
resp := respOrig
148+
if _, ok := resp.(ResponseStatusProvider); !ok {
149+
resp = &responseWriter{respWriter: resp}
150+
}
151+
152+
// prepare the arguments for the handler and do pre-check
153+
argsIn := prepareHandleArgsIn(resp, req, fn)
154+
if req == nil {
155+
preCheckHandler(fn, argsIn)
156+
return // it's doing pre-check, just return
157+
}
158+
159+
routing.UpdateFuncInfo(req.Context(), funcInfo)
160+
ret := fn.Call(argsIn)
161+
162+
// handle the return value, and defer the cancel function if there is one
163+
cancelFunc := handleResponse(fn, ret)
164+
if cancelFunc != nil {
165+
defer cancelFunc()
166+
}
167+
168+
// if the response has not been written, call the next handler
169+
if next != nil && !hasResponseBeenWritten(argsIn) {
170+
next.ServeHTTP(resp, req)
171+
}
172+
})
173+
}
174+
175+
provider(nil).ServeHTTP(nil, nil) // do a pre-check to make sure all arguments and return values are supported
176+
return provider
177+
}
178+
179+
// MiddlewareWithPrefix wraps a handler function at a prefix, and make it as a middleware
180+
// TODO: this design is incorrect, the asset handler should not be a middleware
181+
func MiddlewareWithPrefix(pathPrefix string, middleware func(handler http.Handler) http.Handler, handlerFunc http.HandlerFunc) func(next http.Handler) http.Handler {
182+
funcInfo := routing.GetFuncInfo(handlerFunc)
183+
handler := http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
184+
routing.UpdateFuncInfo(req.Context(), funcInfo)
185+
handlerFunc(resp, req)
186+
})
187+
return func(next http.Handler) http.Handler {
188+
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
189+
if !strings.HasPrefix(req.URL.Path, pathPrefix) {
190+
next.ServeHTTP(resp, req)
191+
return
192+
}
193+
if middleware != nil {
194+
middleware(handler).ServeHTTP(resp, req)
195+
} else {
196+
handler.ServeHTTP(resp, req)
197+
}
198+
})
199+
}
200+
}

0 commit comments

Comments
 (0)