kube_clusterSourceKubeClusterStandardGatewayApiHTTPRoute

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