A simple configuration-driven reverse proxy written in Go.
It has zero dependencies outside of the Go standard library.
Define a slice of type Config
, with the minimum set of fields being: Path
and Upstream
.
Configuration is defined in the routing/configuration file.
Upstreams are defined in the upstreams file.
Below we explain the actual routing configuration committed into this repo...
- Proxy Request
- Proxy Request using Regular Expression
- Proxy Request with Modified Path
- Override with Modified Path
- Modified Path + Override with Modified Path
- Override to Different Upstream
- Query String Override
- Query String Override with Regular Expression
Config{
Path: "/anything/standard",
Upstream: upstreams.HTTPBin,
}
/anything/standard
The request will be proxied straight through to the specified upstream without any modifications.
Config{
Path: "/anything/(?:foo|bar)$",
Upstream: upstreams.HTTPBin,
}
/anything/foo
/anything/bar
Both requests will be proxied straight through to the specified upstream without any modifications.
Config{
Path: `/(?P<cap>foo\w{3})`,
Upstream: upstreams.HTTPBin,
ModifyPath: "/anything/${cap}",
}
/fooabc
/fooxyz
Both requests will be proxied through to the specified upstream but the path will be modified to include the captured information: /anything/abc
and /anything/xyz
.
Config{
Path: "/(?P<start>anything)/(?P<cap>foobar)$",
Upstream: upstreams.HTTPBin,
Override: Override{
Header: "X-BF-Testing",
Match: "integralist",
ModifyPath: "/anything/newthing${cap}",
},
}
/anything/foobar
/anything/foobar
(+ HTTP Request HeaderX-BF-Testing: integralist
)
The request will be proxied straight through to the specified upstream without any modifications.
If the relevant request header is specified, then the request will be proxied through to the specified upstream but the path will be modified to include the captured information: /anything/newthingfoobar
.
Config{
Path: "/(?P<cap>double-checks)$",
Upstream: upstreams.HTTPBin,
ModifyPath: "/anything/toplevel-modified-${cap}",
Override: Override{
Header: "X-BF-Testing",
Match: "integralist",
ModifyPath: "/anything/override-modified-${cap}",
},
}
/double-checks
/double-checks
(+ HTTP Request HeaderX-BF-Testing: integralist
)
The request will be proxied through to the specified upstream but the path will be modified to include the captured information: /anything/toplevel-modified-double-checks
.
If the relevant request header is specified, then the request will be proxied through to the specified upstream but the path will be modified to include the captured information: /anything/override-modified-double-checks
.
Config{
Path: "/anything/(?P<cap>integralist)",
Upstream: upstreams.HTTPBin,
Override: Override{
Header: "X-BF-Testing",
Match: "integralist",
ModifyPath: "/about",
Upstream: upstreams.Integralist,
},
}
/anything/integralist
/anything/integralist
(+ HTTP Request HeaderX-BF-Testing: integralist
)
The request will be proxied straight through to the specified upstream without any modifications.
If the relevant request header is specified, then the request will be proxied through to a different specified upstream and the path will also be modified.
Note: although we use a named capture group, we don't actually utilise it anywhere in the rest of the configuration, so it's effectively a no-op.
Config{
Path: "/about",
Upstream: upstreams.HTTPBin,
Override: Override{
Query: "s",
Match: "integralist",
Upstream: upstreams.Integralist,
},
}
/about
/about?s=integralist
The request will be proxied straight through to the specified upstream without any modifications.
If the relevant query parameter is specified, then the request will be proxied through to a different specified upstream.
Config{
Path: "/anything/querytest",
Upstream: upstreams.HTTPBin,
Override: Override{
Query: "s",
Match: `integralist(?P<cap>\d{1,3})$`,
MatchType: "regex",
ModifyPath: "/anything/newthing${cap}",
},
}
/anything/querytest
/anything/querytest?s=integralist123
/anything/querytest?s=integralist456
The first request will be proxied straight through to the specified upstream without any modifications.
If the relevant query parameter is specified, then the second and third requests will have their path modified to include the captured information: /anything/newthing123
and /anything/newthing456
.
We set the following response headers (not all will be set depending on the configuration):
X-Forwarded-Host
X-Origin-Host
X-Router-Upstream
X-Router-Upstream-OriginalHost
X-Router-Upstream-OriginalPath
X-Router-Upstream-OriginalPathModified
X-Router-Upstream-Override
X-Router-Upstream-OverrideHost
X-Router-Upstream-OverridePath
make run
Note: the application listens on port
9001
.
curl -v http://localhost:9001/some/path/you/configured
make test
We use vegeta
for load testing, so make sure you have that installed.
make stress
Example output:
Requests [total, rate] 1500, 50.03
Duration [total, attack, wait] 30.11237994s, 29.982166788s, 130.213152ms
Latencies [mean, 50, 95, 99, max] 154.522948ms, 96.76258ms, 358.770472ms, 1.076826656s, 2.954136535s
Bytes In [total, mean] 2039772, 1359.85
Bytes Out [total, mean] 0, 0.00
Success [ratio] 100.00%
Status Codes [code:count] 200:1500
Error Set:
- Look at implementing thread pool processing on a host or server basis.
- Verify if DNS caching (or request memoization) would affect latency results?
- Review 301 redirect behaviour to be sure we don't need to handle that differently.
- Flesh out some unit tests (not just integration testing)