-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathdomain.go
More file actions
688 lines (586 loc) · 23.7 KB
/
domain.go
File metadata and controls
688 lines (586 loc) · 23.7 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
// ⚡️ 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 (
"fmt"
"reflect"
"strings"
"github.com/gofiber/utils/v2"
utilsstrings "github.com/gofiber/utils/v2/strings"
)
// domainLocalsKeyType is an unexported type used as the Locals key for domain
// parameters, preventing collisions with user or middleware keys.
type domainLocalsKeyType struct{}
// domainLocalsKey is the typed key used in c.Locals() to store domain parameter values.
var domainLocalsKey = domainLocalsKeyType{}
// domainParams stores domain parameter names and their values for a request.
type domainParams struct {
names []string
values []string
}
// domainCheckResult caches a domain match result for a single request.
// It stores the matched domain params (if any) alongside the match status
// to avoid allocating a new domainParams struct for every handler invocation.
type domainCheckResult struct {
params *domainParams // pre-built params (nil if no params or no match)
matched bool
}
// domainMatcher holds the parsed domain pattern for matching against request hostnames.
type domainMatcher struct {
parts []string // domain parts split by "."
paramIdx []int // indices of parameter parts
paramNames []string // parameter names (without ":")
numParts int // total number of parts
}
// maxDomainParts defines the maximum number of domain labels allowed (e.g., sub.domain.example.com = 4 parts).
// This prevents DoS attacks from patterns or hostnames with excessive label counts.
// RFC 1035 suggests 127 labels max, but we use a more conservative limit to prevent memory exhaustion.
const maxDomainParts = 16
// parseDomainPattern parses a domain pattern like ":subdomain.example.com"
// into a domainMatcher. Parameter parts start with ":".
// Constant labels are lowercased per RFC 4343 (domain names are case-insensitive),
// but parameter names are preserved as-is so that DomainParam lookups work with
// the exact names the caller used (e.g., ":User" → param name "User").
func parseDomainPattern(pattern string) domainMatcher {
pattern = utils.TrimSpace(pattern)
// Trim trailing dot of a fully-qualified domain name (RFC 3986),
// consistent with Fiber's own host normalization in Subdomains().
pattern = utils.TrimRight(pattern, '.')
// Validate pattern is not empty after trimming
if pattern == "" {
panic("Domain pattern cannot be empty")
}
// Enforce RFC 1035 total length limit on patterns
if len(pattern) > 253 {
panic(fmt.Sprintf("Domain pattern '%s' exceeds RFC 1035 maximum of 253 characters (%d chars)",
pattern, len(pattern)))
}
parts := strings.Split(pattern, ".")
// Prevent DoS from patterns with excessive label counts
if len(parts) > maxDomainParts {
panic(fmt.Sprintf("Domain pattern '%s' has %d parts, which exceeds the maximum of %d",
pattern, len(parts), maxDomainParts))
}
m := domainMatcher{
parts: make([]string, len(parts)),
numParts: len(parts),
}
for i, part := range parts {
// Validate no empty labels (e.g., "example..com" is invalid)
if part == "" {
panic(fmt.Sprintf("Domain pattern '%s' contains empty label at position %d", pattern, i))
}
if part[0] == ':' {
// Validate parameter name is not empty
if len(part) == 1 {
panic(fmt.Sprintf("Domain pattern '%s' contains empty parameter name at position %d", pattern, i))
}
paramName := part[1:]
// Validate parameter name contains only ASCII-safe characters (a-z, A-Z, 0-9, underscore, hyphen).
// Using explicit ASCII ranges rather than unicode.IsLetter/IsDigit to reject non-ASCII
// characters that are invalid in DNS names.
for _, ch := range paramName {
if !isASCIIAlphanumeric(ch) && ch != '_' && ch != '-' {
panic(fmt.Sprintf("Domain pattern '%s' contains invalid parameter name '%s' with character '%c'", pattern, paramName, ch))
}
}
m.paramIdx = append(m.paramIdx, i)
m.paramNames = append(m.paramNames, paramName) // preserve original case
m.parts[i] = part // keep ":param" marker for matching
} else {
// Only lowercase constant labels (RFC 4343)
// Enforce RFC 1035 per-label length limit (63 characters)
if len(part) > 63 {
panic(fmt.Sprintf("Domain pattern '%s' has label '%s' exceeding RFC 1035 limit of 63 characters (%d chars)",
pattern, part, len(part)))
}
// Validate label contains only valid ASCII domain characters (a-z, 0-9, hyphen).
normalized := utilsstrings.ToLower(part)
for _, ch := range normalized {
if !isASCIIAlphanumeric(ch) && ch != '-' {
panic(fmt.Sprintf("Domain pattern '%s' contains invalid character '%c' in label '%s'", pattern, ch, part))
}
}
m.parts[i] = normalized
}
}
// Check if the domain pattern has too many parameters
if len(m.paramNames) > maxParams {
panic(fmt.Sprintf("Domain pattern '%s' has %d parameters, which exceeds the maximum of %d",
pattern, len(m.paramNames), maxParams))
}
return m
}
// match checks if a hostname matches the domain pattern.
// It returns true if matched and a slice of parameter values (parallel to paramNames).
// Uses a stack-allocated buffer to avoid heap allocation for typical domain names.
// Validates hostname to prevent DoS attacks from malicious input.
func (m *domainMatcher) match(hostname string) (bool, []string) { //nolint:gocritic // unnamedResult: named returns conflict with nonamedreturns linter
// Trim trailing dot of a fully-qualified domain name (RFC 3986),
// consistent with Fiber's own host normalization in Subdomains().
hostname = utils.TrimRight(hostname, '.')
// Validate hostname is not empty and not excessively long (DoS protection)
// RFC 1035 limits domain names to 253 characters
if hostname == "" || len(hostname) > 253 {
return false, nil
}
// Domain names are case-insensitive per RFC 4343; lowercase after cheap validation
hostname = utilsstrings.ToLower(hostname)
// Use stack-allocated array for typical domain names (up to 16 labels).
// This avoids heap allocation for most common cases, consistent with
// the Subdomains() implementation in req.go.
// The buffer size matches maxDomainParts to prevent overflow.
var partsBuf [maxDomainParts]string
parts := partsBuf[:0]
labelCount := 0
for part := range strings.SplitSeq(hostname, ".") {
labelCount++
// DoS protection: reject hostnames with too many labels
if labelCount > maxDomainParts {
return false, nil
}
// DoS protection: reject empty labels or excessively long labels
// RFC 1035 limits each label to 63 characters
if part == "" || len(part) > 63 {
return false, nil
}
// Validate label contains only safe ASCII domain characters (basic sanitization)
// This prevents injection attacks via malicious hostnames
for _, ch := range part {
if ch != '-' && !isASCIIAlphanumeric(ch) {
return false, nil
}
}
parts = append(parts, part)
}
if len(parts) != m.numParts {
return false, nil
}
// First pass: validate all constant labels without allocating paramValues.
for i, patternPart := range m.parts {
if patternPart != "" && patternPart[0] == ':' {
// Parameter segment; skip in this pass.
continue
}
if patternPart != parts[i] {
return false, nil
}
}
// No parameters to capture; avoid allocating an empty slice.
if len(m.paramIdx) == 0 {
return true, nil
}
// Second pass: now that constants are confirmed, allocate and fill paramValues.
paramValues := make([]string, len(m.paramIdx))
paramIter := 0
for i, patternPart := range m.parts {
if patternPart != "" && patternPart[0] == ':' {
paramValues[paramIter] = parts[i]
paramIter++
}
}
return true, paramValues
}
// isASCIIAlphanumeric returns true if the rune is an ASCII letter (a-z, A-Z) or digit (0-9).
// This is used instead of unicode.IsLetter/unicode.IsDigit to ensure only ASCII characters
// are accepted in domain patterns and hostnames, as DNS names are ASCII-only.
func isASCIIAlphanumeric(ch rune) bool {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')
}
// DomainParam returns the value of a domain parameter from the context.
// Domain parameters are set when a route registered via [App.Domain] or [Group.Domain]
// matches the incoming request hostname.
//
// app.Domain("example.com").Get("/", func(c fiber.Ctx) error {
// return c.SendString("Welcome!")
// })
//
// app.Domain(":user.example.com").Get("/", func(c fiber.Ctx) error {
// user := fiber.DomainParam(c, "user")
// return c.SendString("Hello, " + user)
// })
func DomainParam(c Ctx, key string, defaultValue ...string) string {
if params, ok := c.Locals(domainLocalsKey).(*domainParams); ok && params != nil {
for i, name := range params.names {
if name == key {
return params.values[i]
}
}
}
if len(defaultValue) > 0 {
return defaultValue[0]
}
return ""
}
// domainRouter implements [Router] for domain-filtered routing.
// It wraps an underlying [App] or [Group] and checks the request hostname
// against the domain pattern before executing handlers.
//
// Routes registered through a domainRouter have zero impact on routing
// performance for requests that don't use domain-based routing.
//
// Known limitation: because domain filtering is applied at handler-execution
// time (not at route-matching time), Fiber's 405 Method Not Allowed logic
// may advertise methods for domain-scoped routes even when the requesting
// host does not match the domain pattern. Fixing this would require core
// router changes; for now callers should be aware that 405 responses may
// include methods from domain-scoped routes whose host did not match.
type domainRouter struct {
app *App
group *Group // non-nil when created from a Group
matcher domainMatcher
}
// Verify domainRouter implements Router at compile time.
var _ Router = (*domainRouter)(nil)
// wrapHandlers wraps every handler in the slice with domain checking.
// The hostname match is computed once per request per domain-router and cached
// so that subsequent handlers in the same route avoid redundant parsing.
// Each handler independently checks the cached result, ensuring that Fiber's
// route-merging behavior (combining handlers from multiple registrations into
// one route) cannot cause a non-domain handler to be skipped.
// domainCheckResult objects are cached per-request in c.Locals() to avoid redundant hostname parsing.
func (d *domainRouter) wrapHandlers(handlers []Handler) []Handler {
if len(handlers) == 0 {
return handlers
}
// Use the domainRouter pointer as cache key to avoid cross-matcher collisions.
// Each domainRouter instance gets its own cache slot.
cacheKey := d
result := make([]Handler, len(handlers))
for i, h := range handlers {
origHandler := h
result[i] = func(c Ctx) error {
// Check if we already matched this domain on this request.
var check *domainCheckResult
if cached, ok := c.Locals(cacheKey).(*domainCheckResult); ok {
check = cached
} else {
hostname := c.Hostname()
matched, values := d.matcher.match(hostname)
check = &domainCheckResult{matched: matched}
if matched && len(values) > 0 {
// Store values directly — match() returns a fresh slice each time.
// Build domainParams once and cache it alongside the match result
// so subsequent handlers reuse the same struct.
check.params = &domainParams{
names: d.matcher.paramNames,
values: values,
}
}
c.Locals(cacheKey, check)
}
if !check.matched {
return c.Next()
}
// Reuse the cached domainParams (or nil to clear stale values)
// instead of allocating a new struct per handler invocation.
c.Locals(domainLocalsKey, check.params)
return origHandler(c)
}
}
return result
}
// registerPath returns the full path for registration, taking group prefix into account.
func (d *domainRouter) registerPath(path string) string {
if d.group != nil {
return getGroupPath(d.group.Prefix, path)
}
return path
}
// registerGroup returns the group to associate with routes, if any.
func (d *domainRouter) registerGroup() *Group {
return d.group
}
// Use registers a middleware route that will match requests
// with the provided prefix (which is optional and defaults to "/").
//
// The middleware only executes when the request hostname matches the domain pattern.
//
// api := app.Domain("api.example.com")
// api.Use(func(c fiber.Ctx) error {
// // Only runs for api.example.com requests
// return c.Next()
// })
func (d *domainRouter) Use(args ...any) Router {
var subApp *App
var prefix string
var prefixes []string
var handlers []Handler
for i := range args {
switch arg := args[i].(type) {
case string:
prefix = arg
case []string:
prefixes = arg
case *App:
subApp = arg
default:
handler, ok := toFiberHandler(arg)
if !ok {
panic(fmt.Sprintf("use: invalid handler %v", reflect.TypeOf(arg)))
}
handlers = append(handlers, handler)
}
}
if len(prefixes) == 0 {
prefixes = append(prefixes, prefix)
}
for _, prefix := range prefixes {
if subApp != nil {
return d.mount(prefix, subApp)
}
wrapped := d.wrapHandlers(handlers)
d.app.register([]string{methodUse}, d.registerPath(prefix), d.registerGroup(), wrapped...)
}
// Mark the underlying group so Name() can distinguish between
// group-name-prefix calls (before routes) and route-name calls (after routes).
if d.group != nil && !d.group.anyRouteDefined {
d.group.anyRouteDefined = true
}
return d
}
// mount attaches a sub-app instance to the domain router at the specified prefix.
// All routes from the sub-app will only be accessible when the request hostname
// matches the domain pattern.
//
// The sub-app is not modified: routes are cloned into a dedicated wrapper app
// with domain-filtered handlers, so the same sub-app can safely be mounted on
// multiple domains without double-wrapping. Routes added to the sub-app after
// mounting will not inherit domain filtering.
func (d *domainRouter) mount(prefix string, subApp *App) Router {
// Determine the full mount path by combining the domain router's path with the prefix
var mountPath string
if d.group != nil {
mountPath = getGroupPath(d.group.Prefix, prefix)
} else {
mountPath = prefix
}
// Normalize the mount path
mountPath = utils.TrimRight(mountPath, '/')
if mountPath == "" {
mountPath = "/"
}
// Create a wrapper app so that the original sub-app is not mutated.
// This allows the same sub-app to be reused (e.g., mounted on multiple
// domains) without double-wrapping handlers.
wrapperApp := New(Config{
CaseSensitive: subApp.config.CaseSensitive,
StrictRouting: subApp.config.StrictRouting,
})
// Clone routes from the sub-app with domain-wrapped handlers.
// Lock the sub-app while reading to prevent data races with concurrent
// route registration.
subApp.mutex.Lock()
defer subApp.mutex.Unlock()
for m := range subApp.stack {
for _, route := range subApp.stack[m] {
clonedRoute := subApp.copyRoute(route)
if len(clonedRoute.Handlers) > 0 {
clonedRoute.Handlers = d.wrapHandlers(clonedRoute.Handlers)
}
wrapperApp.stack[m] = append(wrapperApp.stack[m], clonedRoute)
}
}
d.app.mutex.Lock()
// Support for configs of mounted-apps and sub-mounted-apps
for mountedPrefixes, subAppInstance := range subApp.mountFields.appList {
path := getGroupPath(mountPath, mountedPrefixes)
subAppInstance.mountFields.mountPath = path
d.app.mountFields.appList[path] = subAppInstance
}
d.app.mutex.Unlock()
// Create a mount group referencing the wrapper app (not the original).
// During route expansion (processSubAppsRoutes), Fiber reads routes from
// route.group.app.stack — so using the wrapper ensures expanded routes
// carry domain-filtered handlers.
mountGroup := &Group{Prefix: mountPath, app: wrapperApp}
// Register the mount point - the routes will be expanded during startup
d.app.register([]string{methodUse}, mountPath, mountGroup)
// Execute onMount hooks
if err := subApp.hooks.executeOnMountHooks(d.app); err != nil {
panic(err)
}
// Mark the underlying group so Name() can distinguish between
// group-name-prefix calls and route-name calls
if d.group != nil && !d.group.anyRouteDefined {
d.group.anyRouteDefined = true
}
return d
}
// Get registers a route for GET methods.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) Get(path string, handler any, handlers ...any) Router {
return d.Add([]string{MethodGet}, path, handler, handlers...)
}
// Head registers a route for HEAD methods.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) Head(path string, handler any, handlers ...any) Router {
return d.Add([]string{MethodHead}, path, handler, handlers...)
}
// Post registers a route for POST methods.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) Post(path string, handler any, handlers ...any) Router {
return d.Add([]string{MethodPost}, path, handler, handlers...)
}
// Put registers a route for PUT methods.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) Put(path string, handler any, handlers ...any) Router {
return d.Add([]string{MethodPut}, path, handler, handlers...)
}
// Delete registers a route for DELETE methods.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) Delete(path string, handler any, handlers ...any) Router {
return d.Add([]string{MethodDelete}, path, handler, handlers...)
}
// Connect registers a route for CONNECT methods.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) Connect(path string, handler any, handlers ...any) Router {
return d.Add([]string{MethodConnect}, path, handler, handlers...)
}
// Options registers a route for OPTIONS methods.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) Options(path string, handler any, handlers ...any) Router {
return d.Add([]string{MethodOptions}, path, handler, handlers...)
}
// Trace registers a route for TRACE methods.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) Trace(path string, handler any, handlers ...any) Router {
return d.Add([]string{MethodTrace}, path, handler, handlers...)
}
// Patch registers a route for PATCH methods.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) Patch(path string, handler any, handlers ...any) Router {
return d.Add([]string{MethodPatch}, path, handler, handlers...)
}
// Add allows you to specify multiple HTTP methods to register a route.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) Add(methods []string, path string, handler any, handlers ...any) Router {
converted := collectHandlers("domain", append([]any{handler}, handlers...)...)
wrapped := d.wrapHandlers(converted)
d.app.register(methods, d.registerPath(path), d.registerGroup(), wrapped...)
// Mark the underlying group so Name() can distinguish between
// group-name-prefix calls (before routes) and route-name calls (after routes).
if d.group != nil && !d.group.anyRouteDefined {
d.group.anyRouteDefined = true
}
return d
}
// All registers the handler on all HTTP methods.
// The handler only executes when the request hostname matches the domain pattern.
func (d *domainRouter) All(path string, handler any, handlers ...any) Router {
return d.Add(d.app.config.RequestMethods, path, handler, handlers...)
}
// Group creates a new sub-router with a common prefix, scoped to the domain pattern.
// Routes registered through the returned Router also inherit the domain filter.
func (d *domainRouter) Group(prefix string, handlers ...any) Router {
fullPrefix := d.registerPath(prefix)
if len(handlers) > 0 {
converted := collectHandlers("domain", handlers...)
wrapped := d.wrapHandlers(converted)
d.app.register([]string{methodUse}, fullPrefix, d.registerGroup(), wrapped...)
}
// Create a new group on the app
newGrp := &Group{Prefix: fullPrefix, app: d.app, parentGroup: d.group}
if err := d.app.hooks.executeOnGroupHooks(*newGrp); err != nil {
panic(err)
}
return &domainRouter{
app: d.app,
group: newGrp,
matcher: d.matcher,
}
}
// RouteChain creates a Registering instance for the domain router.
func (d *domainRouter) RouteChain(path string) Register {
return &domainRegistering{
domain: d,
path: d.registerPath(path),
}
}
// Route defines routes with a common prefix inside the supplied function,
// scoped to the domain pattern.
func (d *domainRouter) Route(prefix string, fn func(router Router), name ...string) Router {
if fn == nil {
panic("route handler 'fn' cannot be nil")
}
group := d.Group(prefix)
if len(name) > 0 {
group.Name(name[0])
}
fn(group)
return group
}
// Name assigns a name to the most recently registered route.
// When the domain router was created from a Group, this delegates to the
// group's Name method so that group name prefixes are applied correctly.
func (d *domainRouter) Name(name string) Router {
if d.group != nil {
d.group.Name(name)
} else {
d.app.Name(name)
}
return d
}
// Domain creates a new domain router that inherits this domain router's
// group (if any) but uses a different hostname pattern.
func (d *domainRouter) Domain(host string) Router {
return &domainRouter{
app: d.app,
group: d.group,
matcher: parseDomainPattern(host),
}
}
// domainRegistering provides route registration helpers for a specific path
// on a domain router, implementing the [Register] interface.
type domainRegistering struct {
domain *domainRouter
path string
}
// Verify domainRegistering implements Register at compile time.
var _ Register = (*domainRegistering)(nil)
func (r *domainRegistering) All(handler any, handlers ...any) Register {
converted := collectHandlers("domain", append([]any{handler}, handlers...)...)
wrapped := r.domain.wrapHandlers(converted)
r.domain.app.register([]string{methodUse}, r.path, r.domain.registerGroup(), wrapped...)
return r
}
func (r *domainRegistering) Get(handler any, handlers ...any) Register {
return r.Add([]string{MethodGet}, handler, handlers...)
}
func (r *domainRegistering) Head(handler any, handlers ...any) Register {
return r.Add([]string{MethodHead}, handler, handlers...)
}
func (r *domainRegistering) Post(handler any, handlers ...any) Register {
return r.Add([]string{MethodPost}, handler, handlers...)
}
func (r *domainRegistering) Put(handler any, handlers ...any) Register {
return r.Add([]string{MethodPut}, handler, handlers...)
}
func (r *domainRegistering) Delete(handler any, handlers ...any) Register {
return r.Add([]string{MethodDelete}, handler, handlers...)
}
func (r *domainRegistering) Connect(handler any, handlers ...any) Register {
return r.Add([]string{MethodConnect}, handler, handlers...)
}
func (r *domainRegistering) Options(handler any, handlers ...any) Register {
return r.Add([]string{MethodOptions}, handler, handlers...)
}
func (r *domainRegistering) Trace(handler any, handlers ...any) Register {
return r.Add([]string{MethodTrace}, handler, handlers...)
}
func (r *domainRegistering) Patch(handler any, handlers ...any) Register {
return r.Add([]string{MethodPatch}, handler, handlers...)
}
func (r *domainRegistering) Add(methods []string, handler any, handlers ...any) Register {
converted := collectHandlers("domain", append([]any{handler}, handlers...)...)
wrapped := r.domain.wrapHandlers(converted)
r.domain.app.register(methods, r.path, r.domain.registerGroup(), wrapped...)
return r
}
func (r *domainRegistering) RouteChain(path string) Register {
return &domainRegistering{
domain: r.domain,
path: getGroupPath(r.path, path),
}
}