mirror of
https://github.com/zap-proto/fiber.git
synced 2026-10-30 15:06:23 +00:00
Co-authored-by: gaby <[email protected]> Agent-Logs-Url: https://github.com/gofiber/fiber/sessions/cebbf7c2-4fef-4eb0-b413-2d77a793c9f7
689 lines
24 KiB
Go
689 lines
24 KiB
Go
// ⚡️ 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),
|
|
}
|
|
}
|