-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathctx.go
More file actions
1127 lines (992 loc) · 38.5 KB
/
Copy pathctx.go
File metadata and controls
1127 lines (992 loc) · 38.5 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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 GitHub Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io
package fiber
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"io"
"maps"
"mime/multipart"
"net"
"sync"
"sync/atomic"
"time"
"github.com/gofiber/utils/v2"
"github.com/valyala/bytebufferpool"
"github.com/valyala/fasthttp"
)
const (
schemeHTTP = "http"
schemeHTTPS = "https"
)
const (
// maxParams defines the maximum number of parameters per route.
maxParams = 30
maxDetectionPaths = 3
)
var (
_ io.Writer = (*DefaultCtx)(nil) // Compile-time check
_ context.Context = (*DefaultCtx)(nil) // Compile-time check
emptyRouteHandlers [0]Handler
emptyRouteParams [0]string
)
// The contextKey type is unexported to prevent collisions with context keys defined in
// other packages.
type contextKey int
// userContextKey define the key name for storing context.Context in *fasthttp.RequestCtx
const (
userContextKey contextKey = iota // __local_user_context__
)
// DefaultCtx is the default implementation of the Ctx interface
// generation tool `go install github.com/vburenin/ifacemaker@f30b6f9bdbed4b5c4804ec9ba4a04a999525c202`
// https://github.com/vburenin/ifacemaker/blob/f30b6f9bdbed4b5c4804ec9ba4a04a999525c202/ifacemaker.go#L14-L31
//
//go:generate ifacemaker --file ctx.go --file req.go --file res.go --struct DefaultCtx --iface Ctx --pkg fiber --promoted --output ctx_interface_gen.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on."
type DefaultCtx struct {
handlerCtx CustomCtx // Active custom context implementation, if any
DefaultReq // Default request api
DefaultRes // Default response api
app *App // Reference to *App
route *Route // Reference to *Route
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
bind *Bind // Default bind reference
redirect *Redirect // Default redirect reference
reclaim *reclaimLatch // Coordinates safe pool reclamation of an abandoned ctx; nil on the hot path
viewBindMap Map // Default view map to bind template engine
values [maxParams]string // Route parameter values
baseURI string // HTTP base uri
pathOriginal string // Original HTTP path
flashMessages redirectionMsgs // Flash messages
path []byte // HTTP path with the modifications by the configuration
detectionPath []byte // Route detection path
treePathHash int // Hash of the path for the search in the tree
pathSlashes int // Number of '/' in the detection path, used to quick-reject routes
pathPrint uint64 // Fingerprint of the detection path; 0 until the route scan hashes it
indexRoute int // Index of the current route
indexHandler int // Index of the current handler
firstMatchIndex int // Pre-resolved endpoint index from the SkipUnmatchedRoutes lookahead; -1 when unused
methodInt int // HTTP method INT equivalent
isAbandoned atomic.Bool // If true, ctx won't be pooled until ForceRelease is called
isMatched bool // Non use route matched
shouldSkipNonUseRoutes bool // Skip non-use routes while iterating middleware
isUserContextSet bool // User context was stored in fasthttp user values
pathNeedsNorm bool // pathOriginal holds an escape or a dot segment; set wherever pathOriginal is
}
// TLSHandler hosts the callback hooks Fiber invokes while negotiating TLS
// connections, including optional client certificate lookups.
//
// It records the ClientHelloInfo of every connection, keyed by the connection,
// and releases it when the server reports the connection closed.
type TLSHandler struct {
connless atomic.Pointer[tls.ClientHelloInfo]
clientHelloInfos sync.Map // underlying net.Conn -> *tls.ClientHelloInfo
serverConns sync.Map // server-visible net.Conn -> underlying net.Conn
}
// GetClientInfo Callback function to set ClientHelloInfo
// Must comply with the method structure of https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/crypto/tls/common.go;l=554-563
// Since we overlay the method of the TLS config in the listener method
func (t *TLSHandler) GetClientInfo(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
switch {
case info == nil:
case info.Conn != nil:
t.clientHelloInfos.Store(info.Conn, info)
default:
t.connless.Store(info)
}
return nil, nil //nolint:nilnil // Not returning anything useful here is probably fine
}
// clientHelloInfo returns the ClientHelloInfo recorded for a connection, or nil.
func (t *TLSHandler) clientHelloInfo(conn net.Conn) *tls.ClientHelloInfo {
if key := underlyingConn(conn); key != nil {
if v, ok := t.clientHelloInfos.Load(key); ok {
if info, ok := v.(*tls.ClientHelloInfo); ok {
return info
}
}
}
return t.connless.Load()
}
// track notes what a server-visible connection wraps while that connection is
// still open, so its record can be found again once it is not. A *tls.Conn
// needs no note: it answers for what it wraps at any time. The wrapper fasthttp
// installs for Server.MaxConnsPerIP does not — it is closed and returned to a
// pool before the close is reported, and clears the connection it embeds on the
// way, so asking it then dereferences a nil *tls.Conn.
func (t *TLSHandler) track(conn net.Conn) {
if conn == nil {
return
}
if _, ok := conn.(*tls.Conn); ok {
return
}
key := underlyingConn(conn)
if key == nil || key == conn {
return
}
// Reaching a wrapper that still holds a note means it was recycled before
// the close of the connection it carried was reported. That connection is
// closed — only Close returns a wrapper to the pool — so drop the record it
// would otherwise strand.
if prev, loaded := t.serverConns.Swap(conn, key); loaded {
if stale, ok := prev.(net.Conn); ok && stale != key {
t.clientHelloInfos.Delete(stale)
}
}
}
// forget drops the record kept for a closed connection, resolving it through
// the note track left when the connection could still be asked what it wrapped.
func (t *TLSHandler) forget(conn net.Conn) {
if conn == nil {
return
}
if noted, ok := t.serverConns.LoadAndDelete(conn); ok {
if key, ok := noted.(net.Conn); ok {
t.clientHelloInfos.Delete(key)
}
return
}
key := conn
if tc, ok := conn.(*tls.Conn); ok {
key = tc.NetConn()
}
if key != nil {
t.clientHelloInfos.Delete(key)
}
}
// underlyingConn returns the net.Conn a TLS handshake ran on; crypto/tls hands
// GetCertificate the raw connection, while the server sees the *tls.Conn — or a
// wrapper around one, as fasthttp installs for Server.MaxConnsPerIP accounting.
// Matching the method rather than the concrete type unwraps both, so the two
// sides key on the same connection. Only call this for a connection still in
// use; see track for why a closed one cannot be unwrapped this way.
func underlyingConn(conn net.Conn) net.Conn {
if tc, ok := conn.(interface{ NetConn() net.Conn }); ok {
return tc.NetConn()
}
return conn
}
// Views is the interface that wraps the Render function.
type Views interface {
Load() error
Render(out io.Writer, name string, binding any, layout ...string) error
}
// App returns the *App reference to the instance of the Fiber application
func (c *DefaultCtx) App() *App {
return c.app
}
// BaseURL returns (protocol + host + base path).
func (c *DefaultCtx) BaseURL() string {
// TODO: Could be improved: 53.8 ns/op 32 B/op 1 allocs/op
// Should work like https://codeigniter.com/user_guide/helpers/url_helper.html
if c.baseURI != "" {
return c.baseURI
}
scheme := c.Scheme()
host := c.Host()
buf := make([]byte, 0, len(scheme)+len("://")+len(host))
buf = append(buf, scheme...)
buf = append(buf, "://"...)
buf = append(buf, host...)
c.baseURI = c.app.toString(buf)
return c.baseURI
}
// RequestCtx returns *fasthttp.RequestCtx that carries a deadline
// a cancellation signal, and other values across API boundaries.
func (c *DefaultCtx) RequestCtx() *fasthttp.RequestCtx {
return c.fasthttp
}
// Context returns a context implementation that was set by
// user earlier or returns a non-nil, empty context, if it was not set earlier.
func (c *DefaultCtx) Context() context.Context {
if c.fasthttp == nil {
return context.Background()
}
if ctx, ok := c.fasthttp.UserValue(userContextKey).(context.Context); ok && ctx != nil {
return ctx
}
ctx := context.Background()
c.SetContext(ctx)
return ctx
}
// SetContext sets a context implementation by user.
func (c *DefaultCtx) SetContext(ctx context.Context) {
if c.fasthttp == nil {
return
}
c.fasthttp.SetUserValue(userContextKey, ctx)
c.isUserContextSet = true
}
// Deadline returns the time when work done on behalf of this context
// should be canceled. Ctx carries no deadline, so ok is always false.
//
// Ctx satisfies context.Context as a context that can never be canceled: it is
// pooled and reused, so it cannot honor the stability and concurrency the
// interface requires. Pass Context() to anything that is cancellation-aware or
// that outlives the handler.
func (*DefaultCtx) Deadline() (time.Time, bool) {
return time.Time{}, false
}
// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Ctx can never be canceled, so Done always
// returns nil, which context.Context explicitly permits. See Deadline.
func (*DefaultCtx) Done() <-chan struct{} {
return nil
}
// Err returns nil until the Done channel is closed. Done is always nil here,
// so Err always returns nil. See Deadline.
func (*DefaultCtx) Err() error {
return nil
}
// Request return the *fasthttp.Request object
// This allows you to use all fasthttp request methods
// https://godoc.org/github.com/valyala/fasthttp#Request
// Returns nil if the context has been released.
func (c *DefaultCtx) Request() *fasthttp.Request {
if c.fasthttp == nil {
return nil
}
return &c.fasthttp.Request
}
// Response return the *fasthttp.Response object
// This allows you to use all fasthttp response methods
// https://godoc.org/github.com/valyala/fasthttp#Response
// Returns nil if the context has been released.
func (c *DefaultCtx) Response() *fasthttp.Response {
if c.fasthttp == nil {
return nil
}
return &c.fasthttp.Response
}
// Body returns the request body, decompressing it when the request declares a
// Content-Encoding the app accepts. Req and Res both carry a Body, so on Ctx
// the request wins, as it does for Get; use Res().Body() for the response.
func (c *DefaultCtx) Body() []byte {
return c.DefaultReq.Body()
}
// ContentLength returns the value of the Content-Length request header. Req and
// Res both carry one, so on Ctx the request wins, as it does for Get; use
// Res().ContentLength() for the response.
func (c *DefaultCtx) ContentLength() int {
return c.DefaultReq.ContentLength()
}
// ContentType returns the Content-Type request header, parameters included. Req
// and Res both carry one, so on Ctx the request wins, as it does for Get; use
// Res().ContentType() for the response.
func (c *DefaultCtx) ContentType() string {
return c.DefaultReq.ContentType()
}
// Cookies returns the request cookie with the given key, or defaultValue. Only
// valid within the handler unless Immutable is set. For the cookies the response
// is set to send, use Res().GetCookies().
func (c *DefaultCtx) Cookies(key string, defaultValue ...string) string {
return c.DefaultReq.Cookies(key, defaultValue...)
}
// Get returns the HTTP request header specified by field.
// Field names are case-insensitive
// Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting instead.
func (c *DefaultCtx) Get(key string, defaultValue ...string) string {
return c.DefaultReq.Get(key, defaultValue...)
}
// GetHeaders returns the HTTP request headers.
// Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting instead.
func (c *DefaultCtx) GetHeaders() map[string][]string {
return c.DefaultReq.GetHeaders()
}
// GetReqHeaders returns the HTTP request headers.
// Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting instead.
func (c *DefaultCtx) GetReqHeaders() map[string][]string {
return c.DefaultReq.GetHeaders()
}
// GetRespHeader returns the HTTP response header specified by field.
// Field names are case-insensitive
// Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting instead.
func (c *DefaultCtx) GetRespHeader(key string, defaultValue ...string) string {
return c.DefaultRes.Get(key, defaultValue...)
}
// GetRespHeaders returns the HTTP response headers.
// Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting instead.
func (c *DefaultCtx) GetRespHeaders() map[string][]string {
return c.DefaultRes.GetHeaders()
}
// ClientHelloInfo returns the TLS ClientHelloInfo of the connection this request arrived on, or nil.
func (c *DefaultCtx) ClientHelloInfo() *tls.ClientHelloInfo {
if c.app.tlsHandler != nil && c.fasthttp != nil {
return c.app.tlsHandler.clientHelloInfo(c.fasthttp.Conn())
}
return nil
}
// Next executes the next method in the stack that matches the current route.
func (c *DefaultCtx) Next() error {
// Increment handler index
c.indexHandler++
// Did we execute all route handlers?
if c.indexHandler < len(c.route.Handlers) {
if c.handlerCtx != nil {
return c.route.Handlers[c.indexHandler](c.handlerCtx)
}
return c.route.Handlers[c.indexHandler](c)
}
if c.handlerCtx != nil {
_, err := c.app.nextCustom(c.handlerCtx)
return err
}
_, err := c.app.next(c)
return err
}
// RestartRouting instead of going to the next handler. This may be useful after
// changing the request path. Note that handlers might be executed again.
func (c *DefaultCtx) RestartRouting() error {
c.indexRoute = -1
// Path may have changed; invalidate the lookahead index
c.firstMatchIndex = -1
if c.handlerCtx != nil {
_, err := c.app.nextCustom(c.handlerCtx)
return err
}
_, err := c.app.next(c)
return err
}
// ctxForHandlers returns the context user code is handed: the custom context
// when the app uses one, otherwise the DefaultCtx itself, as Next does.
func (c *DefaultCtx) ctxForHandlers() Ctx {
if c.handlerCtx != nil {
return c.handlerCtx
}
return c
}
func (c *DefaultCtx) setHandlerCtx(ctx CustomCtx) {
if ctx == nil {
c.handlerCtx = nil
return
}
if defaultCtx, ok := ctx.(*DefaultCtx); ok && defaultCtx == c {
c.handlerCtx = nil
return
}
c.handlerCtx = ctx
}
// OriginalURL contains the original request URL.
// Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting to use the value outside the Handler.
func (c *DefaultCtx) OriginalURL() string {
return c.app.toString(c.fasthttp.Request.Header.RequestURI())
}
// RequestID returns the request identifier from the response header or request header.
func (c *DefaultCtx) RequestID() string {
if requestID := c.GetRespHeader(HeaderXRequestID); requestID != "" {
return requestID
}
return c.Get(HeaderXRequestID)
}
// Req returns a convenience type whose API is limited to operations
// on the incoming request.
func (c *DefaultCtx) Req() Req {
return &c.DefaultReq
}
// Res returns a convenience type whose API is limited to operations
// on the outgoing response.
func (c *DefaultCtx) Res() Res {
return &c.DefaultRes
}
// Redirect returns the Redirect reference.
// Use Redirect().Status() to set custom redirection status code.
// If status is not specified, status defaults to 303 See Other.
// You can use Redirect().To(), Redirect().Route() and Redirect().Back() for redirection.
func (c *DefaultCtx) Redirect() *Redirect {
if c.redirect == nil {
c.redirect = AcquireRedirect()
c.redirect.c = c
}
return c.redirect
}
// ViewBind Add vars to default view var map binding to template engine.
// Variables are read by the Render method and may be overwritten.
func (c *DefaultCtx) ViewBind(vars Map) error {
// init viewBindMap - lazy map
if c.viewBindMap == nil {
c.viewBindMap = make(Map, len(vars))
}
maps.Copy(c.viewBindMap, vars)
return nil
}
// Route returns the matched Route struct.
func (c *DefaultCtx) Route() *Route {
if c.route == nil {
// Cold path kept out of line so Route stays within the inlining budget.
return c.routeFallback()
}
return c.route
}
// routeFallback builds the synthetic route for the fasthttp error handler.
// Its Method field is resolved like c.Method() (including the raw-header
// fallback for unregistered methods) so Route and Method always agree.
// Never inlined: inlining it would push Route over the inlining budget.
//
//go:noinline
func (c *DefaultCtx) routeFallback() *Route {
return &Route{
path: c.pathOriginal,
Path: c.pathOriginal,
Method: currentMethod(c),
Handlers: emptyRouteHandlers[:],
Params: emptyRouteParams[:],
}
}
// Endpoint returns the route that will handle this request, without advancing the
// handler chain, so global middleware can read its Path or Name before calling
// Next. Returns nil when no endpoint will run: 404, 405, and while the error
// handler replays the chain for a request rejected at the protocol level.
//
// It looks ahead, where the neighboring accessors look back: Route reports the
// route currently executing, which inside middleware is the middleware itself,
// and Matched reports whether an endpoint has been selected yet.
//
// It scans the remaining routes in the request's tree bucket, so calling it from
// global middleware costs a second router scan per request.
func (c *DefaultCtx) Endpoint() *Route {
// Already on a non-middleware endpoint.
if c.route != nil && !c.route.use && !c.route.mount {
return c.route
}
if c.methodInt == -1 || c.app == nil {
return nil
}
// serverErrorHandler replays the chain with this set, and next() then lets no
// endpoint run, so naming one here would promise a handler that cannot run.
if c.shouldSkipNonUseRoutes {
return nil
}
tree, _ := c.app.treeIndex[c.methodInt].lookup(c.treePathHash)
detectionPath := utils.UnsafeString(c.detectionPath)
path := utils.UnsafeString(c.path)
head := pathHeadWord(detectionPath)
pathSlashes := c.pathSlashCount(c.app)
// SkipUnmatchedRoutes already resolved the endpoint, but the index only answers
// while routing has not walked past it, and a route registered mid-request can
// shift the bucket under it, so it still has to clear the prefix filter.
if c.firstMatchIndex > c.indexRoute && c.firstMatchIndex < len(tree) {
if route := tree[c.firstMatchIndex]; route != nil && !route.use && !route.mount &&
!route.prefixRejects(head) {
return route
}
}
// Use a scratch params buffer so look-ahead does not clobber c.values.
var scratch [maxParams]string
// Starting past indexRoute follows the chain, which is why the result needs no
// cache: every mutation that would invalidate one already moves a field read here.
for i := c.indexRoute + 1; i < len(tree); i++ {
route := tree[i]
if route.mount || route.use {
continue
}
if route.prefixRejects(head) {
continue
}
if route.match(detectionPath, path, &scratch, pathSlashes) {
return route
}
}
return nil
}
// FullPath returns the matched route path, including any group prefixes.
func (c *DefaultCtx) FullPath() string {
return c.Route().Path
}
// RouteName returns the name of the route currently executing, or "" when it is
// unnamed. Inside middleware that is the middleware's own route; use
// Endpoint().Name to look ahead to the one that will handle the request.
func (c *DefaultCtx) RouteName() string {
// Route() builds a synthetic Route when none matched, and that one never
// carries a Name — so the allocation could only ever produce "".
if c.route == nil {
return ""
}
return c.route.Name
}
// MountPath returns the prefix the sub-app owning the current route was mounted
// under, or "" for a top-level route. Path is not relative to it. One *App
// mounted twice reports its last prefix, as App.MountPath does.
func (c *DefaultCtx) MountPath() string {
if c.app == nil {
return ""
}
// A route with no recorded owner is this app's own, so it is not under a
// mount at all — reading the app's own prefix here would answer with a
// mount the request never went through.
owner := c.app.routeOwner(c.route)
if owner == nil {
return ""
}
return owner.MountPath()
}
// Matched returns true if the current request path was matched by the router.
func (c *DefaultCtx) Matched() bool {
return c.getMatched()
}
// IsMiddleware returns true if the current request handler was registered as middleware.
func (c *DefaultCtx) IsMiddleware() bool {
if c.route == nil {
return false
}
if c.route.use {
return true
}
// For route-level middleware, there will be a next handler in the chain
return c.indexHandler+1 < len(c.route.Handlers)
}
// IsFinal reports whether this is the last handler of a matched non-middleware
// route, so nothing further on that route runs. It describes the route, not the
// request: another route can still match, and a Use route is never final.
func (c *DefaultCtx) IsFinal() bool {
return c.route != nil && !c.IsMiddleware()
}
// OverrideParam overwrites a route parameter value by name.
// If the parameter name does not exist in the route, this method does nothing.
func (c *DefaultCtx) OverrideParam(name, value string) {
// If no route is matched, there are no parameters to update
if !c.Matched() {
return
}
// Normalize wildcard (*) and plus (+) tokens to their internal
// representations (*1, +1) used by the router.
if name == "*" || name == "+" {
name += "1"
}
if c.app.config.CaseSensitive {
for i, param := range c.route.Params {
if param == name {
c.values[i] = value
return
}
}
return
}
nameBytes := utils.UnsafeBytes(name)
for i, param := range c.route.Params {
if utils.EqualFold(utils.UnsafeBytes(param), nameBytes) {
c.values[i] = value
return
}
}
}
// SaveFile saves any multipart file to disk.
func (*DefaultCtx) SaveFile(fileheader *multipart.FileHeader, path string) error {
if fileheader == nil {
return ErrFileHeaderNil
}
return fasthttp.SaveMultipartFile(fileheader, path)
}
// SaveFileToStorage saves any multipart file to an external storage system.
func (c *DefaultCtx) SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error {
if fileheader == nil {
return ErrFileHeaderNil
}
file, err := fileheader.Open()
if err != nil {
return fmt.Errorf("%w: %q: %w", ErrFileOpen, fileheader.Filename, err)
}
defer file.Close() //nolint:errcheck // not needed
maxUploadSize := c.app.config.BodyLimit
if maxUploadSize <= 0 {
maxUploadSize = DefaultBodyLimit
}
if fileheader.Size > 0 && fileheader.Size > int64(maxUploadSize) {
return fmt.Errorf("%w: %q: %w", ErrFileRead, fileheader.Filename, fasthttp.ErrBodyTooLarge)
}
buf := bytebufferpool.Get()
defer bytebufferpool.Put(buf)
limitedReader := io.LimitReader(file, int64(maxUploadSize)+1)
if _, err = buf.ReadFrom(limitedReader); err != nil {
return fmt.Errorf("%w: %q: %w", ErrFileRead, fileheader.Filename, err)
}
if buf.Len() > maxUploadSize {
return fmt.Errorf("%w: %q: %w", ErrFileRead, fileheader.Filename, fasthttp.ErrBodyTooLarge)
}
data := append([]byte(nil), buf.Bytes()...)
if err := storage.SetWithContext(c.Context(), path, data, 0); err != nil {
return fmt.Errorf("%w: %q to %q: %w", ErrFileStore, fileheader.Filename, path, err)
}
return nil
}
// Error returns an *Error carrying the given status code, defaulting the message
// to the status text. Returning it hands it to the app's ErrorHandler, which
// writes the response; Error itself sets nothing.
func (*DefaultCtx) Error(status int, message ...string) error {
return NewError(status, message...)
}
// Status sets the HTTP status for the response.
// This method is chainable.
func (c *DefaultCtx) Status(status int) Ctx {
c.fasthttp.Response.SetStatusCode(status)
return c
}
// ID returns the connection-unique identifier fasthttp assigned to this request,
// unlike RequestID, which reads a header. It is not unique across processes or
// restarts, so use it to correlate log lines within one server run.
func (c *DefaultCtx) ID() uint64 {
return c.fasthttp.ID()
}
// StartTime returns the time the server began handling this request. It is the
// reference point Elapsed measures from.
func (c *DefaultCtx) StartTime() time.Time {
return c.fasthttp.Time()
}
// Elapsed returns how long this request has been handled so far, measured from
// StartTime. Called after the handler chain has run, it is the request latency.
func (c *DefaultCtx) Elapsed() time.Duration {
return time.Since(c.fasthttp.Time())
}
// LocalAddr returns the server-side address of the connection this request
// arrived on. IP returns the client address as a string; this is the full
// net.Addr, so the port, network, and unix socket path survive.
func (c *DefaultCtx) LocalAddr() net.Addr {
return c.fasthttp.LocalAddr()
}
// RemoteAddr returns the address of the immediate peer, which is the proxy
// rather than the client when the app sits behind one. Use IP or IPs for the
// client address a trusted proxy forwarded.
func (c *DefaultCtx) RemoteAddr() net.Addr {
return c.fasthttp.RemoteAddr()
}
// Hijack registers a handler that takes over the connection once the response is
// sent, for protocols Fiber does not speak. The connection then closes unless
// KeepHijackedConns is set, and the handler must not touch the pooled Ctx.
func (c *DefaultCtx) Hijack(handler fasthttp.HijackHandler) {
c.fasthttp.Hijack(handler)
}
// Hijacked returns true if Hijack has been called on this request, so a later
// handler can tell that the connection is already spoken for and leave the
// response alone.
func (c *DefaultCtx) Hijacked() bool {
return c.fasthttp.Hijacked()
}
// String returns unique string representation of the ctx.
//
// The returned value may be useful for logging.
func (c *DefaultCtx) String() string {
// Get buffer from pool
buf := bytebufferpool.Get()
// Start with the ID, converting it to a hex string without fmt.Sprintf
buf.WriteByte('#')
const hex = "0123456789abcdef"
var id [16]byte
ctxID := c.fasthttp.ID()
for i := len(id) - 1; i >= 0; i-- {
id[i] = hex[ctxID&0xf]
ctxID >>= 4
}
buf.Write(id[:])
buf.WriteString(" - ")
// Add local and remote addresses directly
buf.WriteString(c.fasthttp.LocalAddr().String())
buf.WriteString(" <-> ")
buf.WriteString(c.fasthttp.RemoteAddr().String())
buf.WriteString(" - ")
// Add method and URI
buf.Write(c.fasthttp.Request.Header.Method())
buf.WriteByte(' ')
buf.Write(c.fasthttp.URI().FullURI())
// Allocate string
str := buf.String()
// Reset buffer
buf.Reset()
bytebufferpool.Put(buf)
return str
}
// Value makes it possible to retrieve values (Locals) under keys scoped to the request
// and therefore available to all following routes that match the request. If the context
// has been released and c.fasthttp is nil (for example, after ReleaseCtx), Value returns nil.
func (c *DefaultCtx) Value(key any) any {
if c.fasthttp == nil {
return nil
}
return c.fasthttp.UserValue(key)
}
// configDependentPaths set paths for route recognition and prepared paths for the user,
// here the features for caseSensitive, decoded paths, strict paths are evaluated
func (c *DefaultCtx) configDependentPaths() {
// The path is normalized as RFC 3986 Section 6.2.2 describes before any
// route sees it (see normalizeRequestPath). The detection path is what a
// route is matched against: the case fold of the path unless CaseSensitive
// is set. Most requests need no normalization, and under the default
// configuration both are then written in a single pass over the original.
switch {
case c.pathNeedsNorm:
c.path = append(c.path[:0], c.pathOriginal...)
c.path = normalizeRequestPath(c.path, c.app.config.UnescapePath)
if c.app.config.CaseSensitive {
c.detectionPath = append(c.detectionPath[:0], c.path...)
} else {
c.detectionPath = appendLowerASCII(c.detectionPath[:0], c.path)
}
case !c.app.config.CaseSensitive:
c.path, c.detectionPath = appendCopyLowerASCII(c.path, c.detectionPath, c.pathOriginal)
default:
c.path = append(c.path[:0], c.pathOriginal...)
c.detectionPath = append(c.detectionPath[:0], c.path...)
}
// If StrictRouting is disabled, we strip all trailing slashes
if !c.app.config.StrictRouting && len(c.detectionPath) > 1 && c.detectionPath[len(c.detectionPath)-1] == '/' {
c.detectionPath = utils.TrimRight(c.detectionPath, '/')
}
// Define the path for dividing routes into areas for fast tree detection, so that fewer routes need to be traversed,
// since the first three characters area select a list of routes
c.treePathHash = 0
if len(c.detectionPath) >= maxDetectionPaths {
c.treePathHash = int(c.detectionPath[0])<<16 |
int(c.detectionPath[1])<<8 |
int(c.detectionPath[2])
}
// Invalidate the cached slash count of the detection path; pathSlashCount
// recomputes it lazily when route matching first needs it.
c.pathSlashes = 0
c.pathPrint = 0
}
// pathNeedsNormalization reports whether normalizeRequestPath could change
// path, given the length of the path fasthttp normalized when it parsed the
// request. Parsing decoded the escapes and removed the dot and empty segments,
// and each of those shortens the path, so one that kept its length holds no
// escape and no dot segment, apart from a trailing "/." that fasthttp leaves
// in place.
//
// It takes that length rather than the URI so that it stays inlinable in the
// request hot path. A caller that switched DisablePathNormalizing on has asked
// for the path to be treated as sent, so it does not trust the parsed copy and
// scans the original with needsPathNormalization instead.
func pathNeedsNormalization(normalizedLen int, path string) bool {
n := len(path)
return normalizedLen != n || (n >= 2 && path[n-2] == '/' && path[n-1] == '.')
}
// Reset is a method to reset context fields by given request when to use server handlers.
func (c *DefaultCtx) Reset(fctx *fasthttp.RequestCtx) {
// Reset route and handler index
c.indexRoute = -1
c.indexHandler = 0
// Reset matched flag
c.isMatched = false
c.shouldSkipNonUseRoutes = false
c.firstMatchIndex = -1
c.route = nil
// Set paths
uri := fctx.URI()
c.pathOriginal = c.app.toString(uri.PathOriginal())
if uri.DisablePathNormalizing {
c.pathNeedsNorm = needsPathNormalization(c.pathOriginal)
} else {
c.pathNeedsNorm = pathNeedsNormalization(len(uri.Path()), c.pathOriginal)
}
// Set method
c.methodInt = c.app.methodInt(utils.UnsafeString(fctx.Request.Header.Method()))
// Attach *fasthttp.RequestCtx to ctx
c.fasthttp = fctx
// reset base uri
c.baseURI = ""
// Prettify path
c.configDependentPaths()
c.DefaultReq.c = c
c.DefaultRes.c = c
}
// release is a method to reset context fields when to use ReleaseCtx()
func (c *DefaultCtx) release() {
if c.isUserContextSet {
if c.fasthttp != nil {
c.fasthttp.SetUserValue(userContextKey, nil)
}
c.isUserContextSet = false
}
c.route = nil
c.fasthttp = nil
if c.bind != nil {
ReleaseBind(c.bind)
c.bind = nil
}
// Zero the whole backing array before pooling: what lives here is the previous
// request's flash data, which for WithInput is its entire form.
// parseAndClearFlashMessages clears too, since UnmarshalMsg re-slices this.
clear(c.flashMessages[:cap(c.flashMessages)])
c.flashMessages = c.flashMessages[:0]
// Clear viewBindMap by deleting all keys (reuse underlying map if possible)
if c.viewBindMap != nil {
clear(c.viewBindMap)
}
if c.redirect != nil {
ReleaseRedirect(c.redirect)
c.redirect = nil
}
c.shouldSkipNonUseRoutes = false
// performance: no need for using c.isAbandoned.Store(false) here, as it is always set to false when it was true in ForceRelease
c.reclaim = nil
c.handlerCtx = nil
}
// reclaimLatch coordinates the safe, automatic reclamation of an abandoned
// context back into the pool. It is armed only via ScheduleReclaim (currently by
// the timeout middleware) and stays nil on the common request path, so requests
// that are not abandoned pay no additional cost.
type reclaimLatch struct {
releasedCh chan struct{} // closed once the request handler has released the ctx (event b)
once sync.Once // guards the close-exactly-once of releasedCh
}
// Abandon marks this context as abandoned. An abandoned context will not be
// returned to the pool when ReleaseCtx is called.
//
// This is used by the timeout and SSE middlewares to return immediately while a
// goroutine continues using the context safely.
//
// Only call ForceRelease after Abandon if you can guarantee no other goroutine
// (including Fiber's requestHandler and ErrorHandler) will touch the context.
// Callers that cannot make that guarantee themselves can instead call
// ScheduleReclaim, which arranges a race-free ForceRelease once the handler has
// finished and the request handler has released the context.
func (c *DefaultCtx) Abandon() {
c.isAbandoned.Store(true)
}
// IsAbandoned returns true if Abandon() was called on this context.
func (c *DefaultCtx) IsAbandoned() bool {
return c.isAbandoned.Load()
}
// ForceRelease releases an abandoned context back to the pool.
// This MUST only be called after all goroutines (including requestHandler and
// ErrorHandler) have completely finished using this context. Calling it while
// any goroutine is still running causes races.
func (c *DefaultCtx) ForceRelease() {
c.isAbandoned.Store(false)
c.app.ReleaseCtx(c)
}
// ScheduleReclaim arms automatic reclamation of an abandoned context, returning
// it to the pool once it is safe to do so.
//
// handlerDone must be closed once the goroutine that still uses this context
// (for the timeout middleware, the handler goroutine) has completely finished.
// cancel, if non-nil, is the CancelFunc of the context installed for that
// goroutine and is invoked as soon as it finishes.
//
// ForceRelease is performed only after BOTH handlerDone is closed AND the request
// handler has released the context (signaled from ReleaseCtx/releaseDefaultCtx),
// which makes the reclamation race-free. If handlerDone never closes — a handler
// that never returns — the context is intentionally never reclaimed, because the
// handler still owns it.
//
// This method calls Abandon internally, so callers do not need to call Abandon
// separately. Calling Abandon before ScheduleReclaim is still safe (idempotent).
func (c *DefaultCtx) ScheduleReclaim(handlerDone <-chan struct{}, cancel context.CancelFunc) {
c.Abandon()
latch := &reclaimLatch{releasedCh: make(chan struct{})}
c.reclaim = latch
go func() {
<-handlerDone
if cancel != nil {
cancel()