class HTTPRoute
A single-rule HTTPRoute attached to the cluster's one traefik-gateway.
Every route in ns/ shares the same parentRefs (the traefik-gateway
in kube-system) and differs only by listener section, host(s), the
backing Service, the path matches, and an optional chain of traefik
middlewares. This collapses that boilerplate to a keyword call.
Deliberately models exactly ONE rule: one set of matches -> one backend (or a terminal redirect), with one filter chain. Routes that need distinct backends or filters per path (fanout) are expressed as several instances on the same host -- the gateway merges them and the more-specific match wins.
Standard::GatewayApi::HTTPRoute.new(
name: "sourcebot",
gateway: "example-com-https",
domains: ["sg.example.com"],
service: namespace: "sourcebot", name: "sourcebot", port: 3000 ,
middleware: "forwardauth-oauth2-proxy-example",
)
Many matches -> one backend (forgejo git smart-HTTP):
Standard::GatewayApi::HTTPRoute.new(
name: "forgejo-git", gateway: "example-net-https", domains: ["git.example.net"],
regex: ["/.+/info/refs", "/.+/git-upload-pack", "/.+/git-receive-pack"],
prefix: ["/api", "/v2"],
service: namespace: "default", name: "forgejo-http", port: 3000 ,
timeouts: request: "600s", backendRequest: "600s" ,
)
Terminal http->https redirect (the -http sibling):
Standard::GatewayApi::HTTPRoute.new( name: "sourcebot-http", gateway: "example-com-http", domains: ["sg.example.com"], prefix: ["/"], redirect: true, )
Definitions
MIDDLEWARE_GROUP = "traefik.io"
traefik middlewares are attached to a rule via an ExtensionRef filter.
def _matches(prefix:, exact:, regex:, headers:)
Build the rule's matches: one entry per path across the typed
arrays, all sharing the single backend. A path match is mandatory --
every route (including a redirect-only one) must state its path, so
this raises rather than silently assuming / when none is given.
Implementation
def _matches(prefix:, exact:, regex:, headers:)
entries = []
{ prefix: prefix, exact: exact, regex: regex }.each do |kind, values|
Array(values).each do |value|
entries << { path: { type: PATH_TYPES.fetch(kind), value: value } }
end
end
if entries.empty?
raise ArgumentError,
"HTTPRoute requires at least one path match: pass prefix:, exact:, or regex:"
end
hdrs = _headers(headers)
hdrs.empty? ? entries : entries.map { |m| m.merge(headers: hdrs) }
end
def _headers(headers)
Accepts either a "Header" => "value" hash (Exact match) or a
ready-made array of Gateway API header-match hashes.
Implementation
def _headers(headers)
return [] if headers.nil? || headers.empty?
return headers if headers.is_a?(Array)
headers.map { |name, value| { type: "Exact", name: name.to_s, value: value } }
end
def _filters(middleware:, redirect:, header_modifier:)
Filter chain. Order: header rewrite, then middleware(s), then a terminal redirect (which needs no backend).
Implementation
def _filters(middleware:, redirect:, header_modifier:)
filters = []
if header_modifier
filters << { type: "RequestHeaderModifier", requestHeaderModifier: header_modifier }
end
Array(middleware).each do |name|
filters << {
type: "ExtensionRef",
extensionRef: { group: MIDDLEWARE_GROUP, kind: MIDDLEWARE_KIND, name: name },
}
end
if redirect
filters << { type: "RequestRedirect", requestRedirect: _redirect(redirect) }
end
filters
end
def _redirect(redirect)
redirect: true is the common http->https 301. A hash customises
scheme / status / path (ReplaceFullPath) / hostname.
Implementation
def _redirect(redirect)
opts = redirect == true ? {} : redirect
rr = {
scheme: opts.fetch(:scheme, "https"),
statusCode: opts[:status] || opts[:statusCode] || 301,
}
rr[:path] = { type: "ReplaceFullPath", replaceFullPath: opts[:path] } if opts[:path]
rr[:hostname] = opts[:hostname] if opts[:hostname]
rr
end
def _backend(service)
backendRef in the convention's field order. The Service namespace
is emitted only when the route lives in a different namespace than
the backend (the cross-namespace model, which needs a ReferenceGrant
the caller supplies). Omitting it yields the co-located short form.
Implementation
def _backend(service)
ref = {}
# Cross-namespace backends carry the explicit group/kind/namespace
# (and need a ReferenceGrant); a co-located Service uses the bare
# short form. Emitting group/kind only in the former keeps both
# shapes byte-identical to the hand-written routes they replace.
if service[:namespace]
ref[:group] = ""
ref[:kind] = "Service"
ref[:name] = service.fetch(:name)
ref[:namespace] = service[:namespace]
else
ref[:name] = service.fetch(:name)
end
ref[:port] = service.fetch(:port)
ref[:weight] = service.fetch(:weight, 1)
ref
end