A modern API Gateway library for .NET Core and beyond.
- Add more functional tests:
- "upstreamRequest" -> "httpVersion" add a functional test that tests this setting.
- "upstreamRequest" -> "sender". Not sure how to test all of these. Maybe check the HTTP Client to see if the values are set correctly?
- Add tests for all HTTP methods (POST, DELETE, etc)
- Fix HTTPS so it also works on Mac and Linux!
- HttpClientName
- Routing
- Failed HTTP requests with and without content/body
- Timeout (both at httpClient to upstream and inside of proxy)
- ProxyException and exception handling
- When no route is found, do not return 200
- Authentication
- Run nginx side by side and ensure all headers and other properties match.
- Write documentation
- Add a table of all config values with a description and links/anchors from the rest of the doc.
- Mention the options-schema.json and how it can be used.
- URL Pattern Matching
- How to run teh test server
- Add unit tests for trailing TrailingHeaderSetter
- Enable github build actions
- Publish NuGet
- Add multiple Sample Projects
- Basic/simple
- Multiple kestrel servers in a single project
- Advanced extensibility
- Use of options
- Named HttpClient Management from the outside
- Kestrel should only listen on relevant exact routes defined in Options
- Benchmark
- Increase code coverage
- Add support for modification of the Path attribute in the Set-Cookie header
- Build and test on Linux & MacOS
- Implement the proxy OPTIONS section: here
- Add host filtering to the proxy. Something like this but not a middleware: https://github.com/dotnet/aspnetcore/blob/6427d9cc718f8093c506b62b6fd12544411b477f/src/Middleware/HostFiltering/src/HostFilteringMiddleware.cs
- HTTP2 Client Push
- Wire up more of SocketsHttpHandler properties in SettingsProvider.
- Add tests for content headers both for requests and also responses. Right now, we are only using GET requests that don't really have content length.
- Default timeout for upstream requests is set to 100 seconds. Should these be changed?
- Add the new schema URI to this readme so folks can validate their options.
- Not sure how to implement the CONNECT HTTP method: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT
- Not sure how to test the TRACE HTTP method: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/TRACE
- Implement Gateway Timeout (504) based on this spec: https://datatracker.ietf.org/doc/html/rfc2616#section-10.5.5
- Add a test that Content-MD5 is kept unchanged and is proxied https://datatracker.ietf.org/doc/html/rfc2616#section-14.15
- Protocols
- Add support for web-sockets
- Test gRPC
- Test SignalR
- Features
- Request Aggregation
- Caching
- Retry policies / QoS
- Load Balancing
- Platforms
- Kubernetes
- Service Fabric
- Fixes
- Search for
TODO
s and remove the ones that are fixed.
- Search for
TODO
The default HTTP protocol version for upstream requests is HTTP/2
. This can be changed as shown below:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"httpVersion": "1.1"
-
The values of
Host
and:authority
headers are redefined as the name and port of the upstream server. This can be changed as shown below:"routes": { "/api/": { "proxy": { "to": "http://upstream/v1/", "upstreamRequest": { "headers": { "overrides": { "Host": [ "$host" ]
-
A
Forwarded
header is added/appended/redefined. See this for more information. -
The following headers are discarded:
- Headers with an empty value
- Headers with an underscore (
_
) in their name - GatewayCore headers:
x-gwcore-external-address
,x-gwcore-proxy-name
- HTTP/2 Pseudo Headers:
:method
,:authority
,scheme
, and:path
- Standard hop-by-hop headers:
Keep-Alive
,Transfer-Encoding
,TE
,Connection
,Trailer
,Upgrade
,Proxy-Authorization
, andProxy-Authenticate
- Non-standard hop-by-hop headers defined by the
Connection
header.
-
All other headers from the downstream are typically passed as they are.
- The following headers are discarded:
- Headers with an empty value
- Headers with an underscore (
_
) in their name - Standard headers:
Via
andServer
- GatewayCore headers:
x-gwcore-external-address
,x-gwcore-proxy-name
- HTTP/2 Pseudo Header:
:status
- Standard hop-by-hop headers:
Keep-Alive
,Transfer-Encoding
,TE
,Connection
,Trailer
,Upgrade
,Proxy-Authorization
, andProxy-Authenticate
- Non-standard hop-by-hop headers defined by the
Connection
header.
- All other headers from the upstream are typically passed as they are.
There are four types of route tracking: two that offer information on proxies, and two that carry client details.
A proxy typically uses the Via
header for tracking message forwards, avoiding request loops, and identifying the protocol capabilities of senders along the request/response chain.
According to RFC7230, a proxy must send an appropriate Via
header field in each message that it forwards. An HTTP-to-HTTP gateway must send an appropriate Via
header field in each inbound request message and may send a Via
header field in forwarded response messages.
This header is a comma-separated list of proxies along the message chain with the closest proxy to the sender being the left-most value.
The value added by GatewayCore includes the pseudonym of the proxy. This default name is gwcore
but can be customized with proxyName
:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"proxyName": "my-proxy-name"
GatewayCore appends one of the following values:
Sender's protocol | Value | Example |
---|---|---|
HTTP/1.0 |
1.0 \<proxy-name> |
1.0 gwcore |
HTTP/1.1 |
1.1 \<proxy-name> |
1.1 gwcore |
HTTP/2.0 |
2.0 \<proxy-name> |
2.0 gwcore |
HTTP/X.Y |
X.Y \<proxy-name> |
X.Y gwcore |
<protocol>\<version> |
<protocol>\<version> <proxy-name> |
As illustrated above, GatewayCore omits the protocol for HTTP requests and responses.
The Via
header is included by default in requests to proxied servers but not in forwarded responses. You can change this behavior using skipVia
and addVia
as shown below:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"headers": {
"skipVia": true,
}
}
},
"downstreamResponse": {
"headers": {
"addVia": true
The Forwarded
class of headers contains information from the client facing side of proxy servers that is altered or lost when a proxy is involved in the path of the request. This information is passed on using one of these techniques:
- The
Forwarded
header is what GatewayCore uses by default. This is the standardized version of the header. - The
X-Forwarded-For
,X-Forwarded-Host
, andX-Forwarded-Proto
headers which are considered the de-facto standard versions of theForwarded
header.
The information included in these headers typically consists of the IP address of the client, the IP address where the request came into the proxy server, the Host
request header field as received by the proxy, and the protocol used by the request (typically "http" or "https").
IP V6 addresses are quoted and enclosed in square brackets.
GatewayCore emits the standard Forwarded
header. If such a header is not present on the inbound request, but a X-Forwarded-For
header exists, it is converted to a Forwarded
header.
It is also possible to omit the Forwarded
header on outbound requests by using skipForwarded
:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"headers": {
"skipForwarded": true
According to RFC2616, the Max-Forwards
, gateway recipient of a HTTP TRACE
or OPTIONS
request containing a Max-Forwards
header field must check and update its value prior to forwarding the request. If the received value is zero, the recipient must not forward the request; instead, it must respond as the final recipient. If the received Max-Forwards
value is greater than zero, then the forwarded message must contain an updated Max-Forwards
field with a value decremented by one 1
.
This behavior can be reversed engineered by malicious actors to discover the existence of a gateway or proxy, and as such, GatewayCore does not follow the specification for Max-Forwards
.
The Server
HTTP response header describes the software used by the upstream server that handled the request and generated a response.
GatewayCore discards inbound Server
headers and does not include a Server
header on its outbound responses to clients. This default behavior can be changed to include a Server
header with gwcore
as its value:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"downstreamResponse": {
"headers": {
"addServer": true
Security through obscurity: A
Server
header can reveal information that might make it easier for attackers to exploit known security holes. It is recommended not to include this header. An upstream specifiedServer
header is always ignored.
GatewayCore can forward the IP address of an immediate downstream client. This IP address is sent using the custom x-gwcore-external-address
header and can be enabled with the addExternalAddress
option:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"headers": {
"addExternalAddress": true
The Set-Cookie
HTTP response header is used to send cookies from the server to the client so that the client can send them back to the server later. To send multiple cookies, multiple Set-Cookie
headers can be sent in the same response.
This header can include attributes such as:
Expires
: The maximum lifetime of the cookie as an HTTP-date timestamp.Max-Age
: Number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately.Max-Age
has precedence overExpires
.Domain
: Host to which the cookie will be sent on subsequent calls.Path
: A path that must exist in the requested URL, or the client won't send the cookie header on subsequent requests.Secure
: A secure cookie is only sent to the server when a request is made with thehttps:
scheme.HttpOnly
: Limits the scope of the cookie to HTTP requests. In particular, the attribute instructs the client to omit the cookie when providing access to cookies via "non-HTTP" APIs such as JavaScript'sDocument.cookie
API.SameSite
: Helps with potential cross-site security issues.- If set to
none
, cookies are sent with both cross-site requests and same-site requests. - If set to
strict
, cookies are sent only for same-site requests. - If set to
lax
, same-site cookies are withheld on cross-site subrequests, such as calls to load images or frames, but will be sent when a user navigates to the URL from an external site; for example, by following a link. From Chrome version 80 and Edge 86, the default islax
and notnone
.
- If set to
A reverse proxy such as GatewayCore often modifies the domain, path, and scheme (http/https) of proxied requests and responses. Therefore, it might be necessary to update the Domain
, Path
, SameSite
, Secure
, and HttpOnly
attributes as demonstrated below:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"downstreamResponse": {
"headers": {
"cookies": {
"sessionId": {
"secure": true,
"httpOnly": false,
"sameSite": "lax",
"domain": "example.com"
In the example above, GatewayCore ensures that the Set-Cookie
response header for a cookie named sessionId
is modified such that:
- the
Secure
attribute is set, - the
HttpOnly
attribute is removed if it was specified, - the value of
SameSite
is changed tolax
, and - the
Domain
attribute is updated toexample.com
Set
domain
to an empty text ("domain": ""
) if theDomain
attribute should be fully removed from theSet-Cookie
header. Thedomain
value can be text or an expression.
It is also possible to use the wildcard symbol "*"
to provide a rule that applies to all cookies as shown below:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"downstreamResponse": {
"headers": {
"cookies": {
"*": {
"secure": true,
"httpOnly": false,
"sameSite": "strict",
"domain": "example.com"
A match of a non-wildcard rule supersedes a wildcard match.
GatewayCore pools HttpMessageHandler
instances and can reuse them for outbound upstream requests. Thus, local cookie handling is disabled by default as unanticipated CookieContainer
object sharing often results in incorrect behavior. Although strongly discouraged, it is possible to change this behavior using UseCookie
, as shown below.
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"sender": {
"useCookies": true
Avoid enabling
UseCookies
unless you are confident that this is the behavior that your application needs.
GatewayCore passes forward the W3C ratified traceparent
and tracestate
headers with no modifications. It uses the activity model in .NET as described here.
In addition to the controls offered through explicit configuration options, GatewayCore makes it easy to append, add, update, or remove headers on both outbound requests to upstream systems, as well as responses to clients.
GatewayCore can append additional headers to requests sent to proxied servers, as well as responses forwarded to clients. In the example below, GatewayCore appends the x-append-header
header with value new-value
to proxied requests. A similar header is also added to responses with two values: value-1
and value-2
. If these headers already exist, these values are appended to the existing values:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"headers": {
"overrides": {
"x-append-header": [ "new-value" ]
}
}
},
"downstreamResponse": {
"headers": {
"overrides": {
"x-append-header": [ "value-1", "value-2" ]
A header value can be text or an expression.
GatewayCore can add additional headers to requests sent to proxied servers, as well as responses forwarded to clients. In the example below, GatewayCore adds the x-new-request-header
header with value new-value
to proxied requests. A similar header is also added to responses with two values: value-1
and value-2
:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"headers": {
"overrides": {
"x-new-request-header": [ "new-value" ]
}
}
},
"downstreamResponse": {
"headers": {
"overrides": {
"x-new-response-header": [ "value-1", "value-2" ]
A header value can be text or an expression.
GatewayCore can update headers that it proxies. In the example below, it changes the value of x-request-header
header to updated-value
. The values of a similar response header are also replaced with value-1
and value-2
:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"headers": {
"overrides": {
"x-request-header": [ "updated-value" ]
}
}
},
"downstreamResponse": {
"headers": {
"overrides": {
"x-response-header": [ "value-1", "value-2" ]
A header value can be text or an expression.
The value of inbound headers can be discarded from proxied requests, as well as responses. Use the discards
option to ignore the value of these headers:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"headers": {
"discards": [ "x-header-1", "x-header-2" ]
}
},
"downstreamResponse": {
"headers": {
"discards": [ "x-header-1", "x-header-2" ]
It is possible to discard the values of all inbound headers. Use discardInboundHeaders
to drop all inbound client request headers, as well as all outbound response headers:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"headers": {
"discardInboundHeaders": true
}
},
"downstreamResponse": {
"headers": {
"discardInboundHeaders": true
It is typically unexpected to receive headers that do not have a value, but it is perfectly valid to have headers such as HTTP2-Settings
with an empty value. You can set discardEmpty
to true
to discard headers with no value:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"headers": {
"discardEmpty": true,
}
},
"downstreamResponse": {
"headers": {
"discardEmpty": true
Some clients and servers do not expect an underscore character (_
) in header names. Use discardUnderscore
to remove these headers:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"headers": {
"discardUnderscore": true,
}
},
"downstreamResponse": {
"headers": {
"discardUnderscore": true
Some of the configurations support the use of variables. These variables are:
Variable | Description |
---|---|
$content_length |
The value of the Content-Length request header. |
$content_type |
The value of the Content-Type request header. |
$host |
The value of the Host request header. |
$request_method |
The HTTP method of the inbound downstream request. |
$request_scheme |
The scheme (HTTP or HTTPS ) used by the inbound downstream request. |
$request_path_base |
The unescaped path base value. |
$request_path |
The unescaped path value. |
$request_query_string |
The escaped query string with the leading '?' character. |
$request_encoded_url |
The original escaped request URL including the query string portion (scheme + host + path-base + path + query-string ). |
$remote_address |
The IP address of the remote client/caller. |
$remote_port |
The IP port number of the remote client/caller. |
$server_name |
The name of the server which accepted the request. |
$server_address |
The IP address of the server which accepted the request. |
$server_port |
The IP port number of the server which accepted the request. |
$server_protocol |
The protocol of the inbound downstream request, usually HTTP/1.0 , HTTP/1.1 , or HTTP/2.0 . |
For example, the following configuration adds x-my-custom-header
HTTP header to the proxied call to the upstream. The value of the header includes the protocol and the port number of the server:
"routes": {
"/api/": {
"proxy": {
"to": "http://upstream/v1/",
"upstreamRequest": {
"headers": {
"overrides": {
"x-my-custom-header": [ "$server_protocol : $server_port" ]
The header will like this:x-my-custom-header: HTTP/1.1 : 5099
When using the GatewayCore as a library within your .net core application, you have full control over most portions of the proxy pipeline and other gateway components.
TODO
TODO
- how to pass using DI
- A table of what they are
Key | Can be an expression | Default value | Description |
---|---|---|---|
system |
This is the section that contains all system wide configurations. | ||
system:routeCacheMaxCount |
100,000 cache entries |
This is the maximum number of mappings between "inbound downstream request path" and "outbound upstream request URL" that can be cached in memory. | |
routes |
This is the section in which proxy routes are defined. | ||
routes:<path> |
This is the url pattern that if matched, the request is proxied to the address defined by its to property. |
||
routes:<path>:proxy |
This is the proxy configuration section for this url pattern match. | ||
routes:<path>:proxy:to |
✔️ | This is an expression that defines the URL of the upstream server to which the downstream request is forwarded to. This is a required property. | |
routes:<path>:proxy:proxyName |
✔️ | gwcore |
This is an expression that defines the name of this proxy. This value is used in the Via HTTP header send on the outbound upstream request, and the outbound downstream response. If a value is specified, an x-gwcore-proxy-name header with this value is also added to the outbound upstream request. |