Proxy Feature

The ProxyFeature plugin is an application-level proxy that can be used to transparently proxy HTTP Requests through to downstream servers whose behavior can be customized with custom C# hooks to control how requests are proxied.

ProxyFeature registers an async/non-blocking RawHttpHandler which bypasses ServiceStack's Request Pipeline that in ASP.NET is executed as an ASP.NET IHttpAsyncHandler so it should be flexible and performant enough to handle many demanding workloads.

Usage

The example configuration below registers multiple proxies which proxies all requests to /techstacks, /marketing or /finance endpoints to their configured downstream servers:

Plugins.Add(new ProxyFeature(
    matchingRequests: req => req.PathInfo.StartsWith("/techstacks"),
    resolveUrl:req => $"http://{resolve(req)}.techstacks.io" + req.RawUrl.Replace("/techstacks","/")))
 
Plugins.Add(new ProxyFeature(
    matchingRequests: req => req.PathInfo.StartsWith("/marketing"),
    resolveUrl:req => "http://marketing.domain.com" + req.RawUrl.Replace("/marketing", "/")))
 
Plugins.Add(new ProxyFeature(
    matchingRequests: req => req.PathInfo.StartsWith("/finance"),
    resolveUrl:req => "http://finance.domain.com" + req.RawUrl.Replace("/finance", "/")))

Just like a normal HTTP Proxy, ProxyFeature forwards all the HTTP Request Headers and returns all the HTTP Response Headers and body of the downstream server inc. HTTP Error Responses. This works especially well with ServiceStack's message-based design as the proxied endpoint e.g /techstacks can be treated as if it were the BaseUrl for the downstream server which allows external clients to treat it like they're communicating with the downstream server directly despite every request being transparently proxied behind a central external ServiceStack instance.

Use Cases

One potential use-case is to enable smart load balancing which lets you use C# to dynamically control which external downstream server requests are proxied to.

Thanks to ServiceStack's clean Service Gateway design you can use the clean POCO DTOs from any server instance, which you can get using the x dotnet tool or npm servicestack-cli utils from either the public url or proxy endpoint url, e.g:

x csharp https://techstacks.io
x csharp https://external.domain.com/techstacks

The resulting DTOs can be used with any .NET Service Client, configured with the proxy endpoint as the BaseUrl:

var client = new JsonServiceClient("https://external.domain.com/techstacks");
 
var request = new GetTechnology { Slug = "ServiceStack" };
var response = client.Get(request);
response.PrintDump();

Another potential use-case is to have the proxy act like a facade to access multiple internal microservices that can be made available behind a single external URL, e.g:

var authRequest = new Authenticate { ... };
 
var marketingClient = new JsonServiceClient("https://external.domain.com/marketing");
var authResponse = marketingClient.Post(authRequest);
 
var financeClient = new JsonHttpClient("https://external.domain.com/finance");
var authResponse = await financeClient.PostAsync(authRequest);

When needed, there's a number of customization options available which enables complete control in how the request is proxied and ultimately what response is returned to clients:

class ProxyFeature
{
    // Required filters to specify which requests to proxy and which url to use
    ProxyFeature(
        Func<IHttpRequest, bool> matchingRequests, // Which requests should be proxied
        Func<IHttpRequest, string> resolveUrl);    // Which downstream url to use 
 
    // Customize the HTTP Request Headers that are sent to downstream server
    Action<IHttpRequest, HttpWebRequest> ProxyRequestFilter
 
    // Customize the downstream HTTP Response Headers that are returned to client
    Action<IHttpResponse, HttpWebResponse> ProxyResponseFilter
 
    // Inspect or Transform the HTTP Request Body that's sent downstream
    Func<IHttpRequest, Stream, Task<Stream>> TransformRequest
 
    // Inspect or Transform the downstream HTTP Response Body that's returned
    Func<IHttpResponse, Stream, Task<Stream>> TransformResponse
}

So you could use the TransformResponse delegate for instance to rewrite any internal urls to use external urls with something like:

Plugins.Add(new ProxyFeature(
    matchingRequests: req => req.PathInfo.StartsWith("/techstacks"),
    resolveUrl: req => $"http://{resolve(req)}.techstacks.io" + req.RawUrl.Replace("/techstacks","/"))
    {
        TransformResponse = async (res, responseStream) => 
        {
            using var reader = new StreamReader(responseStream, Encoding.UTF8);
            var responseBody = await reader.ReadToEndAsync();
            var replacedBody = responseBody.Replace(
                "https://techstacks.io",
                "https://external.domain.com/techstacks");
            return MemoryStreamFactory.GetStream(replacedBody.ToUtf8Bytes());
        }
    });