| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688 |
- // ⚡️ 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),
- }
- }
|