mirror of
https://github.com/hanzoai/gateway.git
synced 2026-11-02 23:52:23 +00:00
Every import in this module now resolves to github.com/hanzoai/gateway/v2.
The 34 github.com/krakend/* + github.com/luraproject/lura modules that
supplied the engine and its components are vendored under internal/ and
their 91 packages rewritten onto our own path.
Why fold rather than mirror into 34 hanzoai/* repos:
- It removes the exposure outright. 34 rows leave 34 rows of go.sum to
reason about; zero modules leave nothing. `go list -m all` and go.sum
now contain no krakend/lura entry at all.
- Cross-references cannot go wrong. 28 of the 34 import lura (97 files
inside lura self-reference it). Mirroring means rewriting every one of
those to our lura, and a single miss silently links a SECOND copy of
lura into the binary. With one module that is structurally impossible.
- It creates no duplicates. These are lura-coupled gateway glue with no
consumer outside this binary; publishing 34 repos would be 34 new
things to maintain for zero second caller. internal/ makes that
guarantee mechanical — Go forbids importing it from outside this
module, so none of it can become a competing public package. Verified
no repo outside gateway imports any hanzoai/gateway/v2 subpackage.
Layout mirrors provenance so an upstream re-sync stays a diff:
luraproject/lura/v2 -> internal/lura (the engine, 20 pkgs)
krakend/krakend-<x> -> internal/plugin/<x> (28 components)
krakend/<x> -> internal/pkg/<x> (6 standalone libs)
This commit is PURELY MECHANICAL: 196 of the 197 vendored files are
byte-identical to their upstream original once the import rewrite is
applied. The single exception is bloomfilter's subpackage, which upstream
names `krakend` — the one package name that could not survive as-is; it
becomes internal/pkg/bloomfilter/register. Hand edits land separately in
the next commit so they are readable on their own.
Per `go mod vendor` semantics, dependencies' _test.go files are not
vendored. They test upstream code at a pinned version that we do not
modify, several cannot run at all from a module distribution (they need
TLS certs and compiled .so plugins upstream generates but never ships),
and one is a 100k-request stress test that exhausts the ephemeral port
range and fails every package running beside it. Fold integrity is proven
by the byte-diff above instead, which is the stronger check.
Preserved deliberately — each of these is behaviour, not branding:
- the `github.com/devopsfaith/krakend*` extra_config namespace strings
(35 of them). These are config-surface keys, not import paths;
renaming one silently stops that component finding its config. The
jose validator namespace backs `auth/validator` on 212 endpoints.
- core.KrakendVersion / KrakendUserAgent / the X-KRAKEND consts, whose
VALUES rebrand.go overwrites — that seam is unchanged.
- the KRAKEND_ koanf env prefix (a hardcoded upstream const).
- every vendored module's LICENSE (34 files, Apache-2.0/MIT).
The Makefile/CI -X paths move with the engine. An -X naming a symbol that
does not exist is silently ignored by the linker, so a stale path here
would have shipped an "undefined" version in the backend User-Agent and
X-Api-Version with a perfectly green build. Verified live:
`gateway version` reports 2.13.0, not undefined.
Verification (GOWORK=off CGO_ENABLED=0, both tag modes):
go build ./... OK go build -tags legacy ./... OK
go vet ./... clean go vet -tags legacy ./... clean
go test ./... 6/6 ok go test -tags legacy 5/5 ok
tests/ integration harness 47 fixtures pass
gateway check -c configs/{hanzo,lux}/gateway.json -> Syntax OK!
go list -m all | grep -iE 'krakend|lura' -> empty
go mod verify -> all modules verified
Co-authored-by: Hanzo Dev <[email protected]>
276 lines
7.7 KiB
Go
276 lines
7.7 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
|
|
"github.com/hanzoai/gateway/v2/internal/lura/config"
|
|
"github.com/hanzoai/gateway/v2/internal/lura/logging"
|
|
"github.com/hanzoai/gateway/v2/internal/lura/proxy/plugin"
|
|
)
|
|
|
|
// NewPluginMiddleware returns an endpoint middleware wrapped (if required) with the plugin middleware.
|
|
// The plugin middleware will try to load all the required plugins from the register and execute them in order.
|
|
// RequestModifiers are executed before passing the request to the next middlware. ResponseModifiers are executed
|
|
// once the response is returned from the next middleware.
|
|
func NewPluginMiddleware(logger logging.Logger, endpoint *config.EndpointConfig) Middleware {
|
|
cfg, ok := endpoint.ExtraConfig[plugin.Namespace].(map[string]interface{})
|
|
|
|
if !ok {
|
|
return emptyMiddlewareFallback(logger)
|
|
}
|
|
|
|
return newPluginMiddleware(logger, "ENDPOINT", endpoint.Endpoint, cfg)
|
|
}
|
|
|
|
// NewBackendPluginMiddleware returns a backend middleware wrapped (if required) with the plugin middleware.
|
|
// The plugin middleware will try to load all the required plugins from the register and execute them in order.
|
|
// RequestModifiers are executed before passing the request to the next middlware. ResponseModifiers are executed
|
|
// once the response is returned from the next middleware.
|
|
func NewBackendPluginMiddleware(logger logging.Logger, remote *config.Backend) Middleware {
|
|
cfg, ok := remote.ExtraConfig[plugin.Namespace].(map[string]interface{})
|
|
|
|
if !ok {
|
|
return emptyMiddlewareFallback(logger)
|
|
}
|
|
|
|
return newPluginMiddleware(logger, "BACKEND",
|
|
fmt.Sprintf("%s %s -> %s", remote.ParentEndpointMethod, remote.ParentEndpoint, remote.URLPattern), cfg)
|
|
}
|
|
|
|
func newPluginMiddleware(logger logging.Logger, tag, pattern string, cfg map[string]interface{}) Middleware {
|
|
plugins, ok := cfg["name"].([]interface{})
|
|
if !ok {
|
|
return emptyMiddlewareFallback(logger)
|
|
}
|
|
|
|
var reqModifiers []func(interface{}) (interface{}, error)
|
|
|
|
var respModifiers []func(interface{}) (interface{}, error)
|
|
|
|
for _, p := range plugins {
|
|
name, ok := p.(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if mf, ok := plugin.GetRequestModifier(name); ok {
|
|
if fn := mf(cfg); fn != nil {
|
|
reqModifiers = append(reqModifiers, fn)
|
|
}
|
|
continue
|
|
}
|
|
|
|
if mf, ok := plugin.GetResponseModifier(name); ok {
|
|
if fn := mf(cfg); fn != nil {
|
|
respModifiers = append(respModifiers, fn)
|
|
}
|
|
}
|
|
}
|
|
|
|
totReqModifiers, totRespModifiers := len(reqModifiers), len(respModifiers)
|
|
if totReqModifiers == totRespModifiers && totRespModifiers == 0 {
|
|
return emptyMiddlewareFallback(logger)
|
|
}
|
|
|
|
logger.Debug(
|
|
fmt.Sprintf(
|
|
"[%s: %s][Modifier Plugins] Adding %d request and %d response modifiers",
|
|
tag,
|
|
pattern,
|
|
totReqModifiers,
|
|
totRespModifiers,
|
|
),
|
|
)
|
|
|
|
return func(next ...Proxy) Proxy {
|
|
if len(next) > 1 {
|
|
logger.Fatal("too many proxies for this proxy middleware: newPluginMiddleware only accepts 1 proxy, got %d tag: %s, pattern: %s",
|
|
len(next), tag, pattern)
|
|
return nil
|
|
}
|
|
|
|
if totReqModifiers == 0 {
|
|
return func(ctx context.Context, r *Request) (*Response, error) {
|
|
resp, err := next[0](ctx, r)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
|
|
return executeResponseModifiers(ctx, respModifiers, resp, newRequestWrapper(ctx, r))
|
|
}
|
|
}
|
|
|
|
if totRespModifiers == 0 {
|
|
return func(ctx context.Context, r *Request) (*Response, error) {
|
|
var err error
|
|
r, err = executeRequestModifiers(ctx, reqModifiers, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return next[0](ctx, r)
|
|
}
|
|
}
|
|
|
|
return func(ctx context.Context, r *Request) (*Response, error) {
|
|
var err error
|
|
r, err = executeRequestModifiers(ctx, reqModifiers, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := next[0](ctx, r)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
|
|
return executeResponseModifiers(ctx, respModifiers, resp, newRequestWrapper(ctx, r))
|
|
}
|
|
}
|
|
}
|
|
|
|
func executeRequestModifiers(ctx context.Context, reqModifiers []func(interface{}) (interface{}, error), r *Request) (*Request, error) {
|
|
var tmp RequestWrapper
|
|
tmp = newRequestWrapper(ctx, r)
|
|
|
|
for _, f := range reqModifiers {
|
|
res, err := f(tmp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
t, ok := res.(RequestWrapper)
|
|
if !ok {
|
|
continue
|
|
}
|
|
tmp = t
|
|
}
|
|
|
|
r.Method = tmp.Method()
|
|
r.URL = tmp.URL()
|
|
r.Query = tmp.Query()
|
|
r.Path = tmp.Path()
|
|
r.Body = tmp.Body()
|
|
r.Params = tmp.Params()
|
|
r.Headers = tmp.Headers()
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func executeResponseModifiers(ctx context.Context, respModifiers []func(interface{}) (interface{}, error), r *Response, req RequestWrapper) (*Response, error) {
|
|
var tmp ResponseWrapper
|
|
tmp = responseWrapper{
|
|
ctx: ctx,
|
|
request: req,
|
|
data: r.Data,
|
|
isComplete: r.IsComplete,
|
|
metadata: metadataWrapper{
|
|
headers: r.Metadata.Headers,
|
|
statusCode: r.Metadata.StatusCode,
|
|
},
|
|
io: r.Io,
|
|
}
|
|
|
|
for _, f := range respModifiers {
|
|
res, err := f(tmp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
t, ok := res.(ResponseWrapper)
|
|
if !ok {
|
|
continue
|
|
}
|
|
tmp = t
|
|
}
|
|
|
|
r.Data = tmp.Data()
|
|
r.IsComplete = tmp.IsComplete()
|
|
r.Io = tmp.Io()
|
|
r.Metadata = Metadata{}
|
|
r.Metadata.Headers = tmp.Headers()
|
|
r.Metadata.StatusCode = tmp.StatusCode()
|
|
return r, nil
|
|
}
|
|
|
|
// RequestWrapper is an interface for passing proxy request between the lura pipe and the loaded plugins
|
|
type RequestWrapper interface {
|
|
Params() map[string]string
|
|
Headers() map[string][]string
|
|
Body() io.ReadCloser
|
|
Method() string
|
|
URL() *url.URL
|
|
Query() url.Values
|
|
Path() string
|
|
}
|
|
|
|
// ResponseWrapper is an interface for passing proxy response between the lura pipe and the loaded plugins
|
|
type ResponseWrapper interface {
|
|
Data() map[string]interface{}
|
|
Io() io.Reader
|
|
IsComplete() bool
|
|
Headers() map[string][]string
|
|
StatusCode() int
|
|
}
|
|
|
|
func newRequestWrapper(ctx context.Context, r *Request) *requestWrapper {
|
|
return &requestWrapper{
|
|
ctx: ctx,
|
|
method: r.Method,
|
|
url: r.URL,
|
|
query: r.Query,
|
|
path: r.Path,
|
|
body: r.Body,
|
|
params: r.Params,
|
|
headers: r.Headers,
|
|
}
|
|
}
|
|
|
|
type requestWrapper struct {
|
|
ctx context.Context
|
|
method string
|
|
url *url.URL
|
|
query url.Values
|
|
path string
|
|
body io.ReadCloser
|
|
params map[string]string
|
|
headers map[string][]string
|
|
}
|
|
|
|
func (r *requestWrapper) Context() context.Context { return r.ctx }
|
|
func (r *requestWrapper) Method() string { return r.method }
|
|
func (r *requestWrapper) URL() *url.URL { return r.url }
|
|
func (r *requestWrapper) Query() url.Values { return r.query }
|
|
func (r *requestWrapper) Path() string { return r.path }
|
|
func (r *requestWrapper) Body() io.ReadCloser { return r.body }
|
|
func (r *requestWrapper) Params() map[string]string { return r.params }
|
|
func (r *requestWrapper) Headers() map[string][]string { return r.headers }
|
|
|
|
type metadataWrapper struct {
|
|
headers map[string][]string
|
|
statusCode int
|
|
}
|
|
|
|
func (m metadataWrapper) Headers() map[string][]string { return m.headers }
|
|
func (m metadataWrapper) StatusCode() int { return m.statusCode }
|
|
|
|
type responseWrapper struct {
|
|
ctx context.Context
|
|
request interface{}
|
|
data map[string]interface{}
|
|
isComplete bool
|
|
metadata metadataWrapper
|
|
io io.Reader
|
|
}
|
|
|
|
func (r responseWrapper) Context() context.Context { return r.ctx }
|
|
func (r responseWrapper) Request() interface{} { return r.request }
|
|
func (r responseWrapper) Data() map[string]interface{} { return r.data }
|
|
func (r responseWrapper) IsComplete() bool { return r.isComplete }
|
|
func (r responseWrapper) Io() io.Reader { return r.io }
|
|
func (r responseWrapper) Headers() map[string][]string { return r.metadata.headers }
|
|
func (r responseWrapper) StatusCode() int { return r.metadata.statusCode }
|