-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathhandler.go
More file actions
232 lines (215 loc) · 6.63 KB
/
handler.go
File metadata and controls
232 lines (215 loc) · 6.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package celeris
import (
"context"
"errors"
"fmt"
"runtime/debug"
"strings"
"time"
"github.com/goceleris/celeris/protocol/h2/stream"
)
type routerAdapter struct {
server *Server
notFoundChain []HandlerFunc
methodNotAllowedChain []HandlerFunc
errorHandler func(*Context, error)
}
func (a *routerAdapter) HandleStream(_ context.Context, s *stream.Stream) error {
c := acquireContext(s)
c.startTime = time.Now()
c.trustedNets = a.server.trustedNets
if a.server.config.MaxFormSize != 0 {
c.maxFormSize = a.server.config.MaxFormSize
}
// WriteTimeout is enforced at the engine level via periodic timeout checks
// (epoll/iouring) or http.Server.WriteTimeout (std), avoiding per-request
// timer allocations.
defer a.recoverAndRelease(c, s)
// Run pre-routing middleware before route lookup. Pre-middleware may modify
// c.method or c.path (e.g. URL rewriting, method override). If any
// pre-middleware aborts, skip routing entirely.
if len(a.server.preMiddleware) > 0 {
c.handlers = a.server.preMiddleware
c.index = -1
// Error AND abort: error wins — handleError still runs, then we
// flush any buffered body.
if err := c.Next(); err != nil {
a.handleError(c, s, err)
if c.buffered && !c.written {
c.bufferDepth = 1
_ = c.FlushResponse()
}
return nil
}
// Pure abort (handler wrote a response and called Abort with no
// error): skip routing and flush.
if c.IsAborted() {
if c.buffered && !c.written {
c.bufferDepth = 1
_ = c.FlushResponse()
}
return nil
}
// Reset for the actual handler chain.
c.handlers = nil
c.index = -1
}
handlers, fullPath := a.server.router.find(c.method, c.path, &c.params)
if handlers == nil {
a.handleUnmatched(c, s)
return nil
}
c.handlers = handlers
c.fullPath = fullPath
if err := c.Next(); err != nil {
a.handleError(c, s, err)
}
if c.buffered && !c.written {
c.bufferDepth = 1
_ = c.FlushResponse()
}
return nil
}
// recoverAndRelease handles panic recovery and context release. Extracted to a
// separate noinline function so that HandleStream's stack frame is not inflated
// by the deferred closure and debug.Stack() call (P5).
//
// Layering with middleware/recovery: this is the last-resort safety net.
// Panics from user handlers normally hit middleware/recovery (when
// installed) inside the chain, which converts them to errors before
// they reach this function. recover() here is for catastrophic cases
// where recovery middleware itself panics, isn't installed, or where
// pre-routing middleware panics outside the route chain. Custom panic
// handling (Sentry, structured 500 responses, etc.) belongs in
// middleware/recovery — this function's a.handlePanic is intentionally
// minimal.
//
//go:noinline
func (a *routerAdapter) recoverAndRelease(c *Context, s *stream.Stream) {
if r := recover(); r != nil {
a.handlePanic(c, s, r)
}
if c.detached {
go func() {
<-c.detachDone
if a.server.collector != nil {
// Read the snapshot captured by Detach's done() callback
// to avoid racing late writes from a handler that touched
// the Context after calling done().
status := 200
var elapsed time.Duration
if snap := c.detachSnap; snap != nil {
if snap.status != 0 {
status = snap.status
}
elapsed = snap.elapsed
}
a.server.collector.RecordRequest(elapsed, status)
}
releaseContext(c)
}()
return
}
if a.server.collector != nil {
status := c.statusCode
if status == 0 {
status = 200
}
a.server.collector.RecordRequest(time.Since(c.startTime), status)
}
releaseContext(c)
}
// handlePanic logs the panic and writes a 500 response. Separated from
// recoverAndRelease so debug.Stack() only runs when a panic actually occurs.
//
//go:noinline
func (a *routerAdapter) handlePanic(c *Context, s *stream.Stream, r any) {
a.server.logger().Error("handler panic recovered",
"error", fmt.Sprint(r),
"method", c.method,
"path", c.path,
"stack", string(debug.Stack()),
)
c.statusCode = 500
if !c.written && s.ResponseWriter != nil {
hdrs := make([][2]string, 0, len(c.respHeaders)+2)
hdrs = append(hdrs, c.respHeaders...)
hdrs = append(hdrs, [2]string{"content-type", "text/plain"})
hdrs = append(hdrs, [2]string{"cache-control", "no-store"})
_ = s.ResponseWriter.WriteResponse(s, 500, hdrs, []byte("Internal Server Error"))
c.written = true
}
}
func (a *routerAdapter) handleUnmatched(c *Context, s *stream.Stream) {
allowed := a.server.router.allowedMethods(c.path, c.method)
if len(allowed) > 0 {
c.statusCode = 405
c.fullPath = "<method-not-allowed>"
allowVal := strings.Join(allowed, ", ")
chain := a.methodNotAllowedChain
if chain == nil && a.server.methodNotAllowedHandler != nil {
chain = []HandlerFunc{a.server.methodNotAllowedHandler}
}
if chain != nil {
c.SetHeader("allow", allowVal)
c.handlers = chain
a.handleError(c, s, c.Next())
}
if !c.written && s.ResponseWriter != nil {
hdrs := make([][2]string, 0, len(c.respHeaders)+2)
hdrs = append(hdrs, c.respHeaders...)
hdrs = append(hdrs, [2]string{"content-type", "text/plain"})
hdrs = append(hdrs, [2]string{"allow", allowVal})
_ = s.ResponseWriter.WriteResponse(s, 405, hdrs, []byte("405 Method Not Allowed"))
c.written = true
}
} else {
c.statusCode = 404
c.fullPath = "<unmatched>"
chain := a.notFoundChain
if chain == nil && a.server.notFoundHandler != nil {
chain = []HandlerFunc{a.server.notFoundHandler}
}
if chain != nil {
c.handlers = chain
a.handleError(c, s, c.Next())
}
if !c.written && s.ResponseWriter != nil {
hdrs := make([][2]string, 0, len(c.respHeaders)+1)
hdrs = append(hdrs, c.respHeaders...)
hdrs = append(hdrs, [2]string{"content-type", "text/plain"})
_ = s.ResponseWriter.WriteResponse(s, 404, hdrs, []byte("404 Not Found"))
c.written = true
}
}
}
func (a *routerAdapter) handleError(c *Context, s *stream.Stream, err error) {
if err == nil || c.written {
return
}
if a.errorHandler != nil {
a.errorHandler(c, err)
if c.written {
return
}
}
hdrs := make([][2]string, 0, len(c.respHeaders)+2)
hdrs = append(hdrs, c.respHeaders...)
hdrs = append(hdrs, [2]string{"content-type", "text/plain"})
hdrs = append(hdrs, [2]string{"cache-control", "no-store"})
var he *HTTPError
if errors.As(err, &he) {
c.statusCode = he.Code
if s.ResponseWriter != nil {
_ = s.ResponseWriter.WriteResponse(s, he.Code, hdrs, []byte(he.Message))
c.written = true
}
} else {
c.statusCode = 500
if s.ResponseWriter != nil {
_ = s.ResponseWriter.WriteResponse(s, 500, hdrs, []byte("Internal Server Error"))
c.written = true
}
}
}
var _ stream.Handler = (*routerAdapter)(nil)