Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

envoy-go-sdk

The Go half of Envoy's golang HTTP filter contrib, extracted into a standalone module so Apoxy EdgeFunction plugins can depend on a tagged artifact instead of pinning the whole Envoy source tree.

import (
    "github.com/apoxy-dev/envoy-go-sdk/api"
    "github.com/apoxy-dev/envoy-go-sdk/http"
)
package upstream
api/ contrib/golang/common/go/api (+ api.h)
api_impl/ contrib/golang/common/go/api_impl
utils/ contrib/golang/common/go/utils
http/ contrib/golang/filters/http/source/go/pkg/http

The tree is upstream verbatim apart from rewritten import paths, a collapsed cgo include path, and the diffs in patches/. UPSTREAM records the Envoy tag it currently tracks.

Versioning

A plugin is a .so that Envoy dlopen()s, and api/api.h is the C ABI between them. A struct layout or prototype that differs from the Envoy binary loading the plugin is not a compile error, it is a segfault or silent memory corruption at runtime.

So module tags carry the Envoy tag they were cut from:

v1.36.1-apoxy.1     # Envoy v1.36.1, first Apoxy revision
v1.36.1-apoxy.2     # same ABI, SDK-side fix
v1.38.2-apoxy.1     # Envoy v1.38.2 -- every plugin must be rebuilt

Pin the tag that matches the backplane's Envoy. Never mix.

Build tags: envoystub

The Envoy callbacks the http package calls (envoyGoFilterHttpContinue and 32 others) are supplied by the Envoy process at dlopen() time. A test binary has no Envoy, so without help it links and then dies on a null PC.

http/envoy_stub.go defines all of them as no-ops behind //go:build envoystub:

go build -buildmode=c-shared -o plugin.so .   # production: NO tag, ever
go test -race -tags envoystub ./...           # tests: opt in

The tag is opt-in on purpose. The obvious design — stubs on by default, excluded by a cshared tag in the Dockerfile — fails open: a Dockerfile that forgets the tag ships a plugin whose stubs shadow the real Envoy callbacks, so the filter loads cleanly and does nothing. Opting in means a production build passes no tag at all and cannot contain a stub.

make check-example asserts both directions with nm, which is what each plugin's Dockerfile used to do by hand.

One consequence worth knowing if you edit the stub: it lives in package http, whose cgo preamble includes api.h. cgo concatenates every file's preamble into _cgo_export.c, so a stub whose signature does not match the api.h prototype is a conflicting types compile error. That is deliberate — the C compiler checks the stubs against the ABI for us.

Metrics

Envoy stats reach Prometheus through Envoy's own /stats endpoint, with envoy_ prepended and . mapped to _. Stats carry no labels, so every dimension has to be baked into the name, and only from a closed vocabulary — never from request or config data, or the stats scope grows without bound.

Define metrics once, in Parse, and hold them on the config:

type config struct {
    requests api.CounterMetric
    latency  api.HistogramMetric
}

func (p *parser) Parse(_ *anypb.Any, callbacks api.ConfigCallbackHandler) (interface{}, error) {
    if callbacks == nil {
        return &config{}, nil // route-level config: no callbacks
    }
    return &config{
        requests: callbacks.DefineCounterMetric("example.requests_total"),
        latency:  callbacks.DefineHistogramMetric("example.decode_headers_duration_us"),
    }, nil
}

func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType {
    start := time.Now()
    f.config.requests.Increment(1)
    // ...
    f.config.latency.Record(uint64(time.Since(start).Microseconds()))
    return api.Continue
}

HistogramMetric is Record-only. Increment and Get exist on counters and gauges because the C++ side implements them for those types; for a histogram they would cross the ABI and do nothing.

Upstream has never wired the histogram up on the Go side (// TODO Histogram in ConfigCallbacks) even though FilterConfig::defineMetric and recordMetric have handled MetricType::Histogram for releases. That gap is patches/0001-histogram-metric.patch.

See example/ for a filter that compiles.

Syncing a new Envoy tag

scripts/sync-upstream.sh v1.38.2
go mod tidy
make test build-example check-example

The script re-copies the four packages from the module cache, rewrites import paths, restores the SDK-owned files, writes UPSTREAM, and replays patches/*.patch. Read the api/api.h diff before committing: any change there is an ABI break, and every plugin has to be rebuilt and re-pinned.

If a patch conflicts, resolve it by hand and regenerate:

git diff HEAD -- api/filter.go http/filter.go > patches/0001-histogram-metric.patch

Licence

Apache 2.0, inherited from Envoy. See LICENSE and NOTICE.

About

Envoy golang HTTP filter SDK, extracted from envoyproxy/envoy contrib and tagged per Envoy ABI

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages