domain.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. // ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
  2. // 🤖 GitHub Repository: https://github.com/gofiber/fiber
  3. // 📌 API Documentation: https://docs.gofiber.io
  4. package fiber
  5. import (
  6. "fmt"
  7. "reflect"
  8. "strings"
  9. "github.com/gofiber/utils/v2"
  10. utilsstrings "github.com/gofiber/utils/v2/strings"
  11. )
  12. // domainLocalsKeyType is an unexported type used as the Locals key for domain
  13. // parameters, preventing collisions with user or middleware keys.
  14. type domainLocalsKeyType struct{}
  15. // domainLocalsKey is the typed key used in c.Locals() to store domain parameter values.
  16. var domainLocalsKey = domainLocalsKeyType{}
  17. // domainParams stores domain parameter names and their values for a request.
  18. type domainParams struct {
  19. names []string
  20. values []string
  21. }
  22. // domainCheckResult caches a domain match result for a single request.
  23. // It stores the matched domain params (if any) alongside the match status
  24. // to avoid allocating a new domainParams struct for every handler invocation.
  25. type domainCheckResult struct {
  26. params *domainParams // pre-built params (nil if no params or no match)
  27. matched bool
  28. }
  29. // domainMatcher holds the parsed domain pattern for matching against request hostnames.
  30. type domainMatcher struct {
  31. parts []string // domain parts split by "."
  32. paramIdx []int // indices of parameter parts
  33. paramNames []string // parameter names (without ":")
  34. numParts int // total number of parts
  35. }
  36. // maxDomainParts defines the maximum number of domain labels allowed (e.g., sub.domain.example.com = 4 parts).
  37. // This prevents DoS attacks from patterns or hostnames with excessive label counts.
  38. // RFC 1035 suggests 127 labels max, but we use a more conservative limit to prevent memory exhaustion.
  39. const maxDomainParts = 16
  40. // parseDomainPattern parses a domain pattern like ":subdomain.example.com"
  41. // into a domainMatcher. Parameter parts start with ":".
  42. // Constant labels are lowercased per RFC 4343 (domain names are case-insensitive),
  43. // but parameter names are preserved as-is so that DomainParam lookups work with
  44. // the exact names the caller used (e.g., ":User" → param name "User").
  45. func parseDomainPattern(pattern string) domainMatcher {
  46. pattern = utils.TrimSpace(pattern)
  47. // Trim trailing dot of a fully-qualified domain name (RFC 3986),
  48. // consistent with Fiber's own host normalization in Subdomains().
  49. pattern = utils.TrimRight(pattern, '.')
  50. // Validate pattern is not empty after trimming
  51. if pattern == "" {
  52. panic("Domain pattern cannot be empty")
  53. }
  54. // Enforce RFC 1035 total length limit on patterns
  55. if len(pattern) > 253 {
  56. panic(fmt.Sprintf("Domain pattern '%s' exceeds RFC 1035 maximum of 253 characters (%d chars)",
  57. pattern, len(pattern)))
  58. }
  59. parts := strings.Split(pattern, ".")
  60. // Prevent DoS from patterns with excessive label counts
  61. if len(parts) > maxDomainParts {
  62. panic(fmt.Sprintf("Domain pattern '%s' has %d parts, which exceeds the maximum of %d",
  63. pattern, len(parts), maxDomainParts))
  64. }
  65. m := domainMatcher{
  66. parts: make([]string, len(parts)),
  67. numParts: len(parts),
  68. }
  69. for i, part := range parts {
  70. // Validate no empty labels (e.g., "example..com" is invalid)
  71. if part == "" {
  72. panic(fmt.Sprintf("Domain pattern '%s' contains empty label at position %d", pattern, i))
  73. }
  74. if part[0] == ':' {
  75. // Validate parameter name is not empty
  76. if len(part) == 1 {
  77. panic(fmt.Sprintf("Domain pattern '%s' contains empty parameter name at position %d", pattern, i))
  78. }
  79. paramName := part[1:]
  80. // Validate parameter name contains only ASCII-safe characters (a-z, A-Z, 0-9, underscore, hyphen).
  81. // Using explicit ASCII ranges rather than unicode.IsLetter/IsDigit to reject non-ASCII
  82. // characters that are invalid in DNS names.
  83. for _, ch := range paramName {
  84. if !isASCIIAlphanumeric(ch) && ch != '_' && ch != '-' {
  85. panic(fmt.Sprintf("Domain pattern '%s' contains invalid parameter name '%s' with character '%c'", pattern, paramName, ch))
  86. }
  87. }
  88. m.paramIdx = append(m.paramIdx, i)
  89. m.paramNames = append(m.paramNames, paramName) // preserve original case
  90. m.parts[i] = part // keep ":param" marker for matching
  91. } else {
  92. // Only lowercase constant labels (RFC 4343)
  93. // Enforce RFC 1035 per-label length limit (63 characters)
  94. if len(part) > 63 {
  95. panic(fmt.Sprintf("Domain pattern '%s' has label '%s' exceeding RFC 1035 limit of 63 characters (%d chars)",
  96. pattern, part, len(part)))
  97. }
  98. // Validate label contains only valid ASCII domain characters (a-z, 0-9, hyphen).
  99. normalized := utilsstrings.ToLower(part)
  100. for _, ch := range normalized {
  101. if !isASCIIAlphanumeric(ch) && ch != '-' {
  102. panic(fmt.Sprintf("Domain pattern '%s' contains invalid character '%c' in label '%s'", pattern, ch, part))
  103. }
  104. }
  105. m.parts[i] = normalized
  106. }
  107. }
  108. // Check if the domain pattern has too many parameters
  109. if len(m.paramNames) > maxParams {
  110. panic(fmt.Sprintf("Domain pattern '%s' has %d parameters, which exceeds the maximum of %d",
  111. pattern, len(m.paramNames), maxParams))
  112. }
  113. return m
  114. }
  115. // match checks if a hostname matches the domain pattern.
  116. // It returns true if matched and a slice of parameter values (parallel to paramNames).
  117. // Uses a stack-allocated buffer to avoid heap allocation for typical domain names.
  118. // Validates hostname to prevent DoS attacks from malicious input.
  119. func (m *domainMatcher) match(hostname string) (bool, []string) { //nolint:gocritic // unnamedResult: named returns conflict with nonamedreturns linter
  120. // Trim trailing dot of a fully-qualified domain name (RFC 3986),
  121. // consistent with Fiber's own host normalization in Subdomains().
  122. hostname = utils.TrimRight(hostname, '.')
  123. // Validate hostname is not empty and not excessively long (DoS protection)
  124. // RFC 1035 limits domain names to 253 characters
  125. if hostname == "" || len(hostname) > 253 {
  126. return false, nil
  127. }
  128. // Domain names are case-insensitive per RFC 4343; lowercase after cheap validation
  129. hostname = utilsstrings.ToLower(hostname)
  130. // Use stack-allocated array for typical domain names (up to 16 labels).
  131. // This avoids heap allocation for most common cases, consistent with
  132. // the Subdomains() implementation in req.go.
  133. // The buffer size matches maxDomainParts to prevent overflow.
  134. var partsBuf [maxDomainParts]string
  135. parts := partsBuf[:0]
  136. labelCount := 0
  137. for part := range strings.SplitSeq(hostname, ".") {
  138. labelCount++
  139. // DoS protection: reject hostnames with too many labels
  140. if labelCount > maxDomainParts {
  141. return false, nil
  142. }
  143. // DoS protection: reject empty labels or excessively long labels
  144. // RFC 1035 limits each label to 63 characters
  145. if part == "" || len(part) > 63 {
  146. return false, nil
  147. }
  148. // Validate label contains only safe ASCII domain characters (basic sanitization)
  149. // This prevents injection attacks via malicious hostnames
  150. for _, ch := range part {
  151. if ch != '-' && !isASCIIAlphanumeric(ch) {
  152. return false, nil
  153. }
  154. }
  155. parts = append(parts, part)
  156. }
  157. if len(parts) != m.numParts {
  158. return false, nil
  159. }
  160. // First pass: validate all constant labels without allocating paramValues.
  161. for i, patternPart := range m.parts {
  162. if patternPart != "" && patternPart[0] == ':' {
  163. // Parameter segment; skip in this pass.
  164. continue
  165. }
  166. if patternPart != parts[i] {
  167. return false, nil
  168. }
  169. }
  170. // No parameters to capture; avoid allocating an empty slice.
  171. if len(m.paramIdx) == 0 {
  172. return true, nil
  173. }
  174. // Second pass: now that constants are confirmed, allocate and fill paramValues.
  175. paramValues := make([]string, len(m.paramIdx))
  176. paramIter := 0
  177. for i, patternPart := range m.parts {
  178. if patternPart != "" && patternPart[0] == ':' {
  179. paramValues[paramIter] = parts[i]
  180. paramIter++
  181. }
  182. }
  183. return true, paramValues
  184. }
  185. // isASCIIAlphanumeric returns true if the rune is an ASCII letter (a-z, A-Z) or digit (0-9).
  186. // This is used instead of unicode.IsLetter/unicode.IsDigit to ensure only ASCII characters
  187. // are accepted in domain patterns and hostnames, as DNS names are ASCII-only.
  188. func isASCIIAlphanumeric(ch rune) bool {
  189. return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')
  190. }
  191. // DomainParam returns the value of a domain parameter from the context.
  192. // Domain parameters are set when a route registered via [App.Domain] or [Group.Domain]
  193. // matches the incoming request hostname.
  194. //
  195. // app.Domain("example.com").Get("/", func(c fiber.Ctx) error {
  196. // return c.SendString("Welcome!")
  197. // })
  198. //
  199. // app.Domain(":user.example.com").Get("/", func(c fiber.Ctx) error {
  200. // user := fiber.DomainParam(c, "user")
  201. // return c.SendString("Hello, " + user)
  202. // })
  203. func DomainParam(c Ctx, key string, defaultValue ...string) string {
  204. if params, ok := c.Locals(domainLocalsKey).(*domainParams); ok && params != nil {
  205. for i, name := range params.names {
  206. if name == key {
  207. return params.values[i]
  208. }
  209. }
  210. }
  211. if len(defaultValue) > 0 {
  212. return defaultValue[0]
  213. }
  214. return ""
  215. }
  216. // domainRouter implements [Router] for domain-filtered routing.
  217. // It wraps an underlying [App] or [Group] and checks the request hostname
  218. // against the domain pattern before executing handlers.
  219. //
  220. // Routes registered through a domainRouter have zero impact on routing
  221. // performance for requests that don't use domain-based routing.
  222. //
  223. // Known limitation: because domain filtering is applied at handler-execution
  224. // time (not at route-matching time), Fiber's 405 Method Not Allowed logic
  225. // may advertise methods for domain-scoped routes even when the requesting
  226. // host does not match the domain pattern. Fixing this would require core
  227. // router changes; for now callers should be aware that 405 responses may
  228. // include methods from domain-scoped routes whose host did not match.
  229. type domainRouter struct {
  230. app *App
  231. group *Group // non-nil when created from a Group
  232. matcher domainMatcher
  233. }
  234. // Verify domainRouter implements Router at compile time.
  235. var _ Router = (*domainRouter)(nil)
  236. // wrapHandlers wraps every handler in the slice with domain checking.
  237. // The hostname match is computed once per request per domain-router and cached
  238. // so that subsequent handlers in the same route avoid redundant parsing.
  239. // Each handler independently checks the cached result, ensuring that Fiber's
  240. // route-merging behavior (combining handlers from multiple registrations into
  241. // one route) cannot cause a non-domain handler to be skipped.
  242. // domainCheckResult objects are cached per-request in c.Locals() to avoid redundant hostname parsing.
  243. func (d *domainRouter) wrapHandlers(handlers []Handler) []Handler {
  244. if len(handlers) == 0 {
  245. return handlers
  246. }
  247. // Use the domainRouter pointer as cache key to avoid cross-matcher collisions.
  248. // Each domainRouter instance gets its own cache slot.
  249. cacheKey := d
  250. result := make([]Handler, len(handlers))
  251. for i, h := range handlers {
  252. origHandler := h
  253. result[i] = func(c Ctx) error {
  254. // Check if we already matched this domain on this request.
  255. var check *domainCheckResult
  256. if cached, ok := c.Locals(cacheKey).(*domainCheckResult); ok {
  257. check = cached
  258. } else {
  259. hostname := c.Hostname()
  260. matched, values := d.matcher.match(hostname)
  261. check = &domainCheckResult{matched: matched}
  262. if matched && len(values) > 0 {
  263. // Store values directly — match() returns a fresh slice each time.
  264. // Build domainParams once and cache it alongside the match result
  265. // so subsequent handlers reuse the same struct.
  266. check.params = &domainParams{
  267. names: d.matcher.paramNames,
  268. values: values,
  269. }
  270. }
  271. c.Locals(cacheKey, check)
  272. }
  273. if !check.matched {
  274. return c.Next()
  275. }
  276. // Reuse the cached domainParams (or nil to clear stale values)
  277. // instead of allocating a new struct per handler invocation.
  278. c.Locals(domainLocalsKey, check.params)
  279. return origHandler(c)
  280. }
  281. }
  282. return result
  283. }
  284. // registerPath returns the full path for registration, taking group prefix into account.
  285. func (d *domainRouter) registerPath(path string) string {
  286. if d.group != nil {
  287. return getGroupPath(d.group.Prefix, path)
  288. }
  289. return path
  290. }
  291. // registerGroup returns the group to associate with routes, if any.
  292. func (d *domainRouter) registerGroup() *Group {
  293. return d.group
  294. }
  295. // Use registers a middleware route that will match requests
  296. // with the provided prefix (which is optional and defaults to "/").
  297. //
  298. // The middleware only executes when the request hostname matches the domain pattern.
  299. //
  300. // api := app.Domain("api.example.com")
  301. // api.Use(func(c fiber.Ctx) error {
  302. // // Only runs for api.example.com requests
  303. // return c.Next()
  304. // })
  305. func (d *domainRouter) Use(args ...any) Router {
  306. var subApp *App
  307. var prefix string
  308. var prefixes []string
  309. var handlers []Handler
  310. for i := range args {
  311. switch arg := args[i].(type) {
  312. case string:
  313. prefix = arg
  314. case []string:
  315. prefixes = arg
  316. case *App:
  317. subApp = arg
  318. default:
  319. handler, ok := toFiberHandler(arg)
  320. if !ok {
  321. panic(fmt.Sprintf("use: invalid handler %v", reflect.TypeOf(arg)))
  322. }
  323. handlers = append(handlers, handler)
  324. }
  325. }
  326. if len(prefixes) == 0 {
  327. prefixes = append(prefixes, prefix)
  328. }
  329. for _, prefix := range prefixes {
  330. if subApp != nil {
  331. return d.mount(prefix, subApp)
  332. }
  333. wrapped := d.wrapHandlers(handlers)
  334. d.app.register([]string{methodUse}, d.registerPath(prefix), d.registerGroup(), wrapped...)
  335. }
  336. // Mark the underlying group so Name() can distinguish between
  337. // group-name-prefix calls (before routes) and route-name calls (after routes).
  338. if d.group != nil && !d.group.anyRouteDefined {
  339. d.group.anyRouteDefined = true
  340. }
  341. return d
  342. }
  343. // mount attaches a sub-app instance to the domain router at the specified prefix.
  344. // All routes from the sub-app will only be accessible when the request hostname
  345. // matches the domain pattern.
  346. //
  347. // The sub-app is not modified: routes are cloned into a dedicated wrapper app
  348. // with domain-filtered handlers, so the same sub-app can safely be mounted on
  349. // multiple domains without double-wrapping. Routes added to the sub-app after
  350. // mounting will not inherit domain filtering.
  351. func (d *domainRouter) mount(prefix string, subApp *App) Router {
  352. // Determine the full mount path by combining the domain router's path with the prefix
  353. var mountPath string
  354. if d.group != nil {
  355. mountPath = getGroupPath(d.group.Prefix, prefix)
  356. } else {
  357. mountPath = prefix
  358. }
  359. // Normalize the mount path
  360. mountPath = utils.TrimRight(mountPath, '/')
  361. if mountPath == "" {
  362. mountPath = "/"
  363. }
  364. // Create a wrapper app so that the original sub-app is not mutated.
  365. // This allows the same sub-app to be reused (e.g., mounted on multiple
  366. // domains) without double-wrapping handlers.
  367. wrapperApp := New(Config{
  368. CaseSensitive: subApp.config.CaseSensitive,
  369. StrictRouting: subApp.config.StrictRouting,
  370. })
  371. // Clone routes from the sub-app with domain-wrapped handlers.
  372. // Lock the sub-app while reading to prevent data races with concurrent
  373. // route registration.
  374. subApp.mutex.Lock()
  375. defer subApp.mutex.Unlock()
  376. for m := range subApp.stack {
  377. for _, route := range subApp.stack[m] {
  378. clonedRoute := subApp.copyRoute(route)
  379. if len(clonedRoute.Handlers) > 0 {
  380. clonedRoute.Handlers = d.wrapHandlers(clonedRoute.Handlers)
  381. }
  382. wrapperApp.stack[m] = append(wrapperApp.stack[m], clonedRoute)
  383. }
  384. }
  385. d.app.mutex.Lock()
  386. // Support for configs of mounted-apps and sub-mounted-apps
  387. for mountedPrefixes, subAppInstance := range subApp.mountFields.appList {
  388. path := getGroupPath(mountPath, mountedPrefixes)
  389. subAppInstance.mountFields.mountPath = path
  390. d.app.mountFields.appList[path] = subAppInstance
  391. }
  392. d.app.mutex.Unlock()
  393. // Create a mount group referencing the wrapper app (not the original).
  394. // During route expansion (processSubAppsRoutes), Fiber reads routes from
  395. // route.group.app.stack — so using the wrapper ensures expanded routes
  396. // carry domain-filtered handlers.
  397. mountGroup := &Group{Prefix: mountPath, app: wrapperApp}
  398. // Register the mount point - the routes will be expanded during startup
  399. d.app.register([]string{methodUse}, mountPath, mountGroup)
  400. // Execute onMount hooks
  401. if err := subApp.hooks.executeOnMountHooks(d.app); err != nil {
  402. panic(err)
  403. }
  404. // Mark the underlying group so Name() can distinguish between
  405. // group-name-prefix calls and route-name calls
  406. if d.group != nil && !d.group.anyRouteDefined {
  407. d.group.anyRouteDefined = true
  408. }
  409. return d
  410. }
  411. // Get registers a route for GET methods.
  412. // The handler only executes when the request hostname matches the domain pattern.
  413. func (d *domainRouter) Get(path string, handler any, handlers ...any) Router {
  414. return d.Add([]string{MethodGet}, path, handler, handlers...)
  415. }
  416. // Head registers a route for HEAD methods.
  417. // The handler only executes when the request hostname matches the domain pattern.
  418. func (d *domainRouter) Head(path string, handler any, handlers ...any) Router {
  419. return d.Add([]string{MethodHead}, path, handler, handlers...)
  420. }
  421. // Post registers a route for POST methods.
  422. // The handler only executes when the request hostname matches the domain pattern.
  423. func (d *domainRouter) Post(path string, handler any, handlers ...any) Router {
  424. return d.Add([]string{MethodPost}, path, handler, handlers...)
  425. }
  426. // Put registers a route for PUT methods.
  427. // The handler only executes when the request hostname matches the domain pattern.
  428. func (d *domainRouter) Put(path string, handler any, handlers ...any) Router {
  429. return d.Add([]string{MethodPut}, path, handler, handlers...)
  430. }
  431. // Delete registers a route for DELETE methods.
  432. // The handler only executes when the request hostname matches the domain pattern.
  433. func (d *domainRouter) Delete(path string, handler any, handlers ...any) Router {
  434. return d.Add([]string{MethodDelete}, path, handler, handlers...)
  435. }
  436. // Connect registers a route for CONNECT methods.
  437. // The handler only executes when the request hostname matches the domain pattern.
  438. func (d *domainRouter) Connect(path string, handler any, handlers ...any) Router {
  439. return d.Add([]string{MethodConnect}, path, handler, handlers...)
  440. }
  441. // Options registers a route for OPTIONS methods.
  442. // The handler only executes when the request hostname matches the domain pattern.
  443. func (d *domainRouter) Options(path string, handler any, handlers ...any) Router {
  444. return d.Add([]string{MethodOptions}, path, handler, handlers...)
  445. }
  446. // Trace registers a route for TRACE methods.
  447. // The handler only executes when the request hostname matches the domain pattern.
  448. func (d *domainRouter) Trace(path string, handler any, handlers ...any) Router {
  449. return d.Add([]string{MethodTrace}, path, handler, handlers...)
  450. }
  451. // Patch registers a route for PATCH methods.
  452. // The handler only executes when the request hostname matches the domain pattern.
  453. func (d *domainRouter) Patch(path string, handler any, handlers ...any) Router {
  454. return d.Add([]string{MethodPatch}, path, handler, handlers...)
  455. }
  456. // Add allows you to specify multiple HTTP methods to register a route.
  457. // The handler only executes when the request hostname matches the domain pattern.
  458. func (d *domainRouter) Add(methods []string, path string, handler any, handlers ...any) Router {
  459. converted := collectHandlers("domain", append([]any{handler}, handlers...)...)
  460. wrapped := d.wrapHandlers(converted)
  461. d.app.register(methods, d.registerPath(path), d.registerGroup(), wrapped...)
  462. // Mark the underlying group so Name() can distinguish between
  463. // group-name-prefix calls (before routes) and route-name calls (after routes).
  464. if d.group != nil && !d.group.anyRouteDefined {
  465. d.group.anyRouteDefined = true
  466. }
  467. return d
  468. }
  469. // All registers the handler on all HTTP methods.
  470. // The handler only executes when the request hostname matches the domain pattern.
  471. func (d *domainRouter) All(path string, handler any, handlers ...any) Router {
  472. return d.Add(d.app.config.RequestMethods, path, handler, handlers...)
  473. }
  474. // Group creates a new sub-router with a common prefix, scoped to the domain pattern.
  475. // Routes registered through the returned Router also inherit the domain filter.
  476. func (d *domainRouter) Group(prefix string, handlers ...any) Router {
  477. fullPrefix := d.registerPath(prefix)
  478. if len(handlers) > 0 {
  479. converted := collectHandlers("domain", handlers...)
  480. wrapped := d.wrapHandlers(converted)
  481. d.app.register([]string{methodUse}, fullPrefix, d.registerGroup(), wrapped...)
  482. }
  483. // Create a new group on the app
  484. newGrp := &Group{Prefix: fullPrefix, app: d.app, parentGroup: d.group}
  485. if err := d.app.hooks.executeOnGroupHooks(*newGrp); err != nil {
  486. panic(err)
  487. }
  488. return &domainRouter{
  489. app: d.app,
  490. group: newGrp,
  491. matcher: d.matcher,
  492. }
  493. }
  494. // RouteChain creates a Registering instance for the domain router.
  495. func (d *domainRouter) RouteChain(path string) Register {
  496. return &domainRegistering{
  497. domain: d,
  498. path: d.registerPath(path),
  499. }
  500. }
  501. // Route defines routes with a common prefix inside the supplied function,
  502. // scoped to the domain pattern.
  503. func (d *domainRouter) Route(prefix string, fn func(router Router), name ...string) Router {
  504. if fn == nil {
  505. panic("route handler 'fn' cannot be nil")
  506. }
  507. group := d.Group(prefix)
  508. if len(name) > 0 {
  509. group.Name(name[0])
  510. }
  511. fn(group)
  512. return group
  513. }
  514. // Name assigns a name to the most recently registered route.
  515. // When the domain router was created from a Group, this delegates to the
  516. // group's Name method so that group name prefixes are applied correctly.
  517. func (d *domainRouter) Name(name string) Router {
  518. if d.group != nil {
  519. d.group.Name(name)
  520. } else {
  521. d.app.Name(name)
  522. }
  523. return d
  524. }
  525. // Domain creates a new domain router that inherits this domain router's
  526. // group (if any) but uses a different hostname pattern.
  527. func (d *domainRouter) Domain(host string) Router {
  528. return &domainRouter{
  529. app: d.app,
  530. group: d.group,
  531. matcher: parseDomainPattern(host),
  532. }
  533. }
  534. // domainRegistering provides route registration helpers for a specific path
  535. // on a domain router, implementing the [Register] interface.
  536. type domainRegistering struct {
  537. domain *domainRouter
  538. path string
  539. }
  540. // Verify domainRegistering implements Register at compile time.
  541. var _ Register = (*domainRegistering)(nil)
  542. func (r *domainRegistering) All(handler any, handlers ...any) Register {
  543. converted := collectHandlers("domain", append([]any{handler}, handlers...)...)
  544. wrapped := r.domain.wrapHandlers(converted)
  545. r.domain.app.register([]string{methodUse}, r.path, r.domain.registerGroup(), wrapped...)
  546. return r
  547. }
  548. func (r *domainRegistering) Get(handler any, handlers ...any) Register {
  549. return r.Add([]string{MethodGet}, handler, handlers...)
  550. }
  551. func (r *domainRegistering) Head(handler any, handlers ...any) Register {
  552. return r.Add([]string{MethodHead}, handler, handlers...)
  553. }
  554. func (r *domainRegistering) Post(handler any, handlers ...any) Register {
  555. return r.Add([]string{MethodPost}, handler, handlers...)
  556. }
  557. func (r *domainRegistering) Put(handler any, handlers ...any) Register {
  558. return r.Add([]string{MethodPut}, handler, handlers...)
  559. }
  560. func (r *domainRegistering) Delete(handler any, handlers ...any) Register {
  561. return r.Add([]string{MethodDelete}, handler, handlers...)
  562. }
  563. func (r *domainRegistering) Connect(handler any, handlers ...any) Register {
  564. return r.Add([]string{MethodConnect}, handler, handlers...)
  565. }
  566. func (r *domainRegistering) Options(handler any, handlers ...any) Register {
  567. return r.Add([]string{MethodOptions}, handler, handlers...)
  568. }
  569. func (r *domainRegistering) Trace(handler any, handlers ...any) Register {
  570. return r.Add([]string{MethodTrace}, handler, handlers...)
  571. }
  572. func (r *domainRegistering) Patch(handler any, handlers ...any) Register {
  573. return r.Add([]string{MethodPatch}, handler, handlers...)
  574. }
  575. func (r *domainRegistering) Add(methods []string, handler any, handlers ...any) Register {
  576. converted := collectHandlers("domain", append([]any{handler}, handlers...)...)
  577. wrapped := r.domain.wrapHandlers(converted)
  578. r.domain.app.register(methods, r.path, r.domain.registerGroup(), wrapped...)
  579. return r
  580. }
  581. func (r *domainRegistering) RouteChain(path string) Register {
  582. return &domainRegistering{
  583. domain: r.domain,
  584. path: getGroupPath(r.path, path),
  585. }
  586. }