Architecture
Big picture
Every managed thing in Easegress is an object under a supervisor, and every object's configuration is stored in an embedded etcd cluster shared across all nodes. Objects fall into categories that start in priority order: system controllers first, then business controllers, then pipelines, then traffic gates (pkg/supervisor/registry.go:102). A traffic gate such as an HTTP server accepts a request, routes it to a pipeline, and the pipeline runs the request through an ordered list of filters. The last filter is usually a proxy that forwards to a backend.
Components
Supervisor and object model
pkg/supervisor owns the lifecycle of every object. The Object interface (pkg/supervisor/registry.go:30) requires Category, Kind, a default spec, status, and close. Objects that handle traffic also implement TrafficObject with Init(superSpec, muxMapper) (pkg/supervisor/registry.go:61). The category constants and their startup priority are defined together (pkg/supervisor/registry.go:102), which fixes the order in which the supervisor brings objects up and tears them down.
Traffic gates
A traffic gate is the data-plane entry point. HTTPServer (pkg/object/httpserver) is the listener for HTTP; GRPCServer and MQTTProxy cover the other protocols. A gate does not process the request itself past routing: it matches a route and looks up the handler (a pipeline) that the route names.
Pipelines and filters
Pipeline (pkg/object/pipeline) holds a map of filters and an ordered flow. Each filter lives under pkg/filters/*: proxy, validator, ratelimiter, corsadaptor, opafilter, waf, aigatewayproxy, and more. A filter's Handle(ctx) returns a result string, and the pipeline uses that string to decide the next node. This is where traffic orchestration happens.
Controllers
Controllers run in the background as the control plane. TrafficController (pkg/object/trafficcontroller) holds HTTP servers and pipelines per namespace and can resolve a handler by name (pkg/object/trafficcontroller/trafficcontroller.go:123). Others include AutoCertManager, MeshController for the service mesh, and the various service registries (Eureka, Consul, Nacos, Zookeeper, etcd).
Embedded etcd cluster
pkg/cluster embeds go.etcd.io/etcd/server/v3/embed into each node (pkg/cluster/cluster.go:31). The cluster struct holds an *embed.Etcd server (pkg/cluster/cluster.go:123) started with embed.StartEtcd (pkg/cluster/cluster.go:586). Raft and leader election provide high availability, and the Cluster interface exposes Get, Put, Watch, Syncer, and STM over the shared key space (pkg/cluster/cluster_interface.go:33). Configuration lives in etcd and is shared across every node.
How a request flows
Trace one HTTP request from the listener to the backend. All anchors are verified at commit 3bdb192.
mux.ServeHTTP(pkg/object/httpserver/mux.go:338) is the entry point. It special-cases ACME challenges under/.well-known/acme-challenge/(pkg/object/httpserver/mux.go:344), and otherwise forwards to the currentmuxInstance.serveHTTP(pkg/object/httpserver/mux.go:357).- Inside
serveHTTP, the request body is wrapped in a byte-count reader (pkg/object/httpserver/mux.go:452), a trace span is opened (pkg/object/httpserver/mux.go:457), an internal context is created withcontext.New(span)(pkg/object/httpserver/mux.go:459), and the request is set on the context withctx.SetRequest(pkg/object/httpserver/mux.go:467). - Routing:
routers.NewContext(req)thenmi.search(routeCtx)decides the route (pkg/object/httpserver/mux.go:472), andctx.SetRouterecords it (pkg/object/httpserver/mux.go:474). A missed route returns a failure response (pkg/object/httpserver/mux.go:533). - Backend resolution:
route.route.GetBackend()(pkg/object/httpserver/mux.go:539) names the handler, andmi.muxMapper.GetHandler(backend)fetches it (pkg/object/httpserver/mux.go:540). TheMuxMapperis theTrafficController'sNamespace.GetHandler(pkg/object/trafficcontroller/trafficcontroller.go:123), so the handler is a pipeline. - Rewrite and payload:
route.route.Rewrite(pkg/object/httpserver/mux.go:548) thenreq.FetchPayload(maxBodySize)(pkg/object/httpserver/mux.go:557), which returns 413 if the body exceeds the limit. - Execution: with no global filter the code calls
handler.Handle(ctx)(pkg/object/httpserver/mux.go:572); with one it callsglobalFilter.Handle(ctx, handler)(pkg/object/httpserver/mux.go:574). TheHandlerinterface is justHandle(ctx) string(pkg/context/context.go:35). Pipeline.Handle(pkg/object/pipeline/pipeline.go:357) callsdoHandle(pkg/object/pipeline/pipeline.go:371), which walks the flow and calls eachnode.filter.Handle(ctx)(pkg/object/pipeline/pipeline.go:390). The returned result string indexesJumpIfto pick the next node, or ends the flow (pkg/object/pipeline/pipeline.go:399).- A terminal filter is usually the proxy.
Proxy.Handle(pkg/filters/proxies/httpproxy/proxy.go:343) fires the mirror pool asynchronously (pkg/filters/proxies/httpproxy/proxy.go:346), selects a candidate pool by match (pkg/filters/proxies/httpproxy/proxy.go:351), and forwards through the server pool (pkg/filters/proxies/httpproxy/proxy.go:358). - The response is written back in a deferred
mi.sendResponse(pkg/object/httpserver/mux.go:367), which copiesctx.GetResponseto the writer and finishes the access log, metrics, and span.
The key idea: a pipeline is a directed-graph execution engine. An empty result string means continue to the next filter; a non-empty result with no matching JumpIf entry ends the flow (pkg/object/pipeline/pipeline.go:399).
Key design decisions
Resilience is injected into the proxy, not run as separate filters. The v2.0 rework moved circuit breaker, retry, and time limiter out of standalone filters and into the Proxy, which distributes them to its pools via InjectResiliencePolicy (pkg/filters/proxies/httpproxy/proxy.go:362). The maintainers called the earlier split a mistake that mixed control logic with business logic (MegaEase v2.0 announcement).
The handler abstraction is deliberately tiny. From the HTTP server's view a pipeline is a single method that returns a string (pkg/context/context.go:35). That minimalism is what lets one pipeline model serve HTTP, gRPC, and MQTT: the gate protocol differs, but the pipeline contract does not.
etcd is embedded rather than external. Each node bundles the etcd server as a library (pkg/cluster/cluster.go:31) instead of talking to a separate etcd process. The trade-off is a larger binary that contains etcd, in exchange for a self-contained HA cluster with nothing else to deploy.
Extension points
- Filters: implement the
Filterinterface (pkg/filters/filters.go:54) and register aKindin the packageinit()viafilters.Register(pkg/filters/registry.go:29). A filter declares the result strings it can return, which the pipeline validates against its jump table. - Objects: register a new
Objectkind with the supervisor to add a controller or traffic gate type. - WebAssembly: the
wasmhostfilter runs user code compiled to WebAssembly, so extensions need not be written in Go. - Service registries: pluggable backends for Eureka, Consul, Nacos, Zookeeper, and etcd feed service discovery into routing.