Context for humans and coding agents working on this repository.
- Compile path:
core/prepare.gocallscompileRouterIndexincore/compile.goon startup and on config reload. Host and path patterns are compiled once (Goregexp); invalid patterns fail startup/reload. host_typeresolution: Ifhost_typeis omitted or set toauto, the effective type is chosen from thehoststring at compile time and written back torule.Rule.HostType. Downstream code (core/match.go,core/build.go, service name templates) relies on that final value, not only on the YAML omission.- Inference order (when auto):
- If
hostcontains regexp metacharacters( ) [ ] ^ $ | + ? \→ regex - Else if
hostcontains*→ wildcard - Else → exact
- If
- Regex is checked before
*so patterns like^.*\.example\.com$are not misclassified as wildcard. - Explicit
host_type: exact: Disables inference;hostis matched as a literal string even if it looks like a pattern (rare). - Upstream Host mode: Prefer
backend.service.mode(internal/external). Legacybackend.modeis an alias whenservice.modeis empty; both must not disagree.internal(default) keeps the clientHostunlessservice.request.host.rewriteis set.externalsetsHostto the upstream (service.Host()). Explicitrequest.host.rewritewins. Fallback useshost: @@fallback(core/host_rewrite.go). - Path strip prefix: On
paths[].backend.serviceonly,strip_prefix: trueexpands at load time torequest.path.rewritesusing that path’spaths[].path(seecore/strip_prefix.go). Cannot combine with explicitrequest.path.rewriteson the same backend. - Default upstream port:
backend.service.portmay be omitted (0).core/service/host.gothen uses 443 whenprotocolishttpsand 80 whenhttp(or default/empty). FirstHost()/Target()call may write the chosen port back onto the loadedServicevalue. service.protocolomission: Unset or empty defaults tohttp(protocol,default=httponcore/service.Serviceand the same default inHost()/Target()).
\win Go regex matches[0-9A-Za-z_], not-. Subdomains such ascustom-domain.inlets.example.comwill not match(\w+).inlets.example.com; use something like([a-zA-Z0-9-]+)or a wildcard host if appropriate.- Omitting
host_typeis not “always exact”: It is “auto”. Plain hostnames (letters, digits, dots, hyphens only, no*or regex metacharacters) still resolve to exact.
Ingress runs on github.com/go-zoox/zoox. Protocol features are implemented there; ingress maps YAML → zoox.Application.Config in core/build.go.
- HTTP/2 over TLS: When
https.portis set and TLS is available (files or SNI loader), zoox configures the HTTPS server with HTTP/2 (ALPNh2). No separate ingress flag. enable_h2c: Cleartext HTTP/2 on the plaintext HTTP listener (port). Unsafe on untrusted networks; use only behind a trusted LB or for local testing.- HTTP/3: Under
https:—enable_http3, optionalhttp3_port(default same ashttps.port), optionalhttp3_altsvc_max_age(Alt-Svcma=seconds;0uses framework default; negative disables the header). Requires TCP HTTPS and TLS; opens a UDP listener for QUIC.
Zoox may also honor env overrides when unset in config: ENABLE_H2C, ENABLE_HTTP3, HTTP3_PORT, HTTP3_ALTSVC_MAX_AGE (see zoox BuiltInEnv* in application.go / constants.go).
- Global redirect is configured under
https.redirect_from_httpincore/config.go. - Default behavior: when
https.portis set, forced HTTP -> HTTPS redirect is not active unlesshttps.redirect_from_http.enabled: true. permanent: truereturns 301;permanent: falsereturns 302, unlesswith_origin_method_and_body: trueuses 308/307 instead (302/301 when false).exclude_pathsuses exact path matching and skips forced redirect for matched paths.- Redirect is decided before route matching in
core/build.go(shouldRedirectFromHTTP), while route-level redirect (rules[].backend.redirect) still applies in normal route flow. - HTTPS detection checks both TLS and
X-Forwarded-Proto: httpsto avoid redirect loops behind trusted proxies/LBs.
- Ingress
loggingdecodes aszcfg.Loggerand is copied toapp.Config.Loggerinprepare()whenlevelortransportsis set. ZooxLogger()builds console + transports (components/application/logger). - Use
logging.transportswithtype: file,path,levels(same as zoox). No separate ingress-only logging types. - Relative
path/levels.*values resolve against the ingress config file directory (core.ResolveConfigPathsinrun/validate), not the process cwd.
- Access logs use
formatAccessLogincore/accesslog.go, emitted fromcore/build.go. - Format:
{client_ip} {host} -> {target} "{method} {path} {proto}" {status} {duration_ms} cache_hit=… waf_block=… real_ip=… referer=… ua=… xff=… tls_* upstream_*. - WAF blocks, redirects, handler, and upstream proxy share the same line shape.
- Where:
core/build.go— service after auth/DNS (cache hit short-circuits beforego-zoox/proxy); handler wrapsctx.WriterwithzooxHTTPCacheCaptureRWwhen storing; redirect tries cache beforeapplyRedirectand stores the finalLocationafter expansion (GET only). Validate acceptscache.enabledfor service, handler, and redirect (core/validate.go). - Defaults: off without
cache.enabled: true. Applied innormalizeHTTPCache(core/http_cache.go): TTL 300s, max body 2MiB, fingerprintmd5, methodsGET+HEAD, key headersAuthorization+Cookie+Accept-Encoding, client bypass tokensno-cache/no-store/max-age=0,Pragma: no-cachehonored by default, skip responses withSet-Cookieby default (skip_when_set_cookie).skip_varydefault false: non-empty originVary⇒ do not store; whenskip_vary,Varyis omitted from stored JSON and stripped on hits. - Upstream Host mode: Prefer
backend.service.mode(internal/external);backend.modeis a legacy alias. Both may not be set to different values. SeeeffectiveBackendMode/effectiveHostRewrite(core/host_rewrite.go). - Keys: prefix
httpcache:v1:(under the globalcache.prefixwhen using Redis). Canonical string treats HEAD method as GET for fingerprinting so both can share an entry. - Store: GET only for population: proxy upstream in
OnResponse; handler after executing handler; redirect after final URL is known. Handler uses an optional body capture buffer; other methods may still hit cache (e.g. HEAD shares GET key). - Logs: hits append
cache_hit=1to the access log line (service proxy, handler, and redirect).
Separate from matcher KV: top-level cache still configures the shared ctx.Cache() backend (core/prepare.go, core/match.go uses match.host:v2: keys).
- When: After a route match in
core/build.go, beforebackend.redirect, handler, or upstream proxy (waf.CheckRequest+*waf.Profile). - Package:
core/waf/—CompileIngress,CheckRequest,MergePatch/MergeRules,StarterRules,ApplyRulePatchesFromFile/ApplyRulePatchesFromYAML. - Config: Typed global
wafoncore.Config(rule.WAF— no nested pointers;go-zoox/configcannot decode them). Per-routerules[].wafmaps merge over the baseline viawaf.ApplyRulePatchesFromFile(called fromcmd/ingress/run.goandvalidate.goright afterconfig.Load). In-memorycfgusesrule.Rule.WAFPatch(config:"-"). - Semantics: IP deny list, optional allow gate, then regex/contains signatures (optional starters from
StarterRules(); disable viadisable_builtin). Global/per-rulelog_onlyaudits without blocking ([waf audit]vs[waf block]in logs). No HTTP body scanning in v1. - Tests / examples:
core/waf/compile_test.go,eval_test.go,patch_test.go,yaml_test.go;examples/waf/.
- Route redirect (
rules[].backend.redirectand path backends): evaluated before proxy/handler incore/build.go.backend.typeisservice,handler, orredirect(core/constants.go).inferBackendTypes/inferRuleBackends(core/backend_type.go) run duringprepareandingress validate, inferring the type whentypeis omitted and exactly one of service/handler/redirect blocks looks configured; otherwise validation demands an explicitbackend.type. Each typed backend permits only its matching block (core/validate.go).expandRedirectURL(core/match.go) applies${host.N}/${path.N}/$1-style templates in redirect URLs (aligned with service naming). Routeredirect.with_origin_method_and_bodymirrors global semantics (307/308 vs 302/301).
- Runnable YAML samples live under repo-root
examples/(topic subdirs);docs/examples/anddocs/zh/examples/embed them via VitePress 1 snippet lines<<< @/../examples/...(path only; optional{yaml}in braces—do not add a trailing space +yaml, it is parsed as part of the filename). - User-facing behavior:
docs/guide/routing.md(EN),docs/zh/guide/routing.md(ZH), WAF indocs/guide/waf.md/docs/zh/guide/waf.md, TLS and HTTP/2–3 indocs/guide/ssl-tls.md/docs/zh/guide/ssl-tls.md, routing/config snippets indocs/guide/configuration.md/docs/zh/guide/configuration.md, and access-log field notes in those same configuration docs. - Inference and compile behavior:
core/compile_test.go,core/compile.go(effectiveHostType,hostLooksLikeRegexp). - Config validation (
ingress validate):core/validate.go,core/validate_test.go. - Redirect and auth/header constants behavior:
core/build.go,core/constants.go,core/build_test.go. - Protocol wiring and logging:
core/build.go,core/build_test.go(TestBuild_HTTP2HTTP3ZooxConfig,TestBuild_AccessLogExtraFields_WithTLS,TestBuild_AccessLogExtraFields_WithoutTLS).
- Config:
admin:section iningress.yaml(admin.enabled,port,database,web). - Stack:
core/admin/web— React + TypeScript + Vite + pnpm;core/admin— go-zoox HTTP API, gormx + SQLite (audit_log,waf_event,config_revision). - Ingress integration: starts with
ingress runwhenadmin.enabled: true; reads/writes the same ingress config file;POST /api/v1/reloadvalidates then in-process reload. Route list / dry-run match usecore.ListRouteRowsandcore.PreviewMatch(core/admininspect.go). - Dev:
ingress run -c examples/admin-console/ingress.yaml+cd core/admin/web && pnpm dev(proxy/api). Build:cd core/admin && make build. Demo config:examples/admin-console/. - Logs API:
GET /api/v1/logssupportsoffset(byte tail),cache_hit,waf_blockfilters for live monitoring. - Cache / TLS API:
GET /api/v1/cache/overview,GET /api/v1/tls/certs(x509 metadata from cert files). - Static prototype (no backend):
prototypes/admin-console/.
- From repo root:
go test ./core/...(or narrow with-run). If the environment cannot reach the module proxy, tryGOPROXY=offwhen modules are already cached. - Admin:
make -C core/admin web && go build ./cmd/ingress(release/Docker run the web build beforego build).