Thomas Nørgaard
Thomas Nørgaard

Reputation: 135

ServiceStack Proxy Feature code optimization

I'm tasked with creating a proxy for an internal system. The proxy needs to add a Basic authentication header to each request as well as log it and the response.

I'm using ServiceStack's Proxy Feature plugin, but feel like the code is repetitive, as I do the following for each operation:

Plugins.Add(new ProxyFeature(matchingRequests: req => req.PathInfo.ToLower().StartsWith("/opretaftale"), resolveUrl: req => gensamOpretaftaleUrl)
{
    ProxyRequestFilter = (req, httpWebRequest) =>
    {
        try
        {
            httpWebRequest.AddBasicAuth(clientId, clientPassword);
        }
        catch (System.Exception ex)
        {
            Log.Error(ex, "...");
        }
    },

    TransformRequest = async (res, requestStream) =>
    {
        using (var reader = new StreamReader(requestStream, Encoding.UTF8))
        {
            var requestBody = await reader.ReadToEndAsync();
            Log.Information("request: " + requestBody);
            return MemoryStreamFactory.GetStream(requestBody.ToUtf8Bytes());
        }
    },

    TransformResponse = async (res, responseStream) =>
    {
        using (var reader = new StreamReader(responseStream, Encoding.UTF8))
        {
            var responseBody = await reader.ReadToEndAsync();
            Log.Information("response: " + responseBody);
            return MemoryStreamFactory.GetStream(responseBody.ToUtf8Bytes());
        }
    }
});

Is it possible to have the same actions for multiple requests and do I really need the TransformRequest/TransformResponse to log the request/response? I'm having difficulty getting the stream content from the ProxyRequestFilter/ProxyResponseFilter actions...

Thanks!

Upvotes: 3

Views: 183

Answers (1)

mythz
mythz

Reputation: 143359

You can refactor using normal C#, e.g. by extracting out the inline lambdas into generic methods and referencing them instead:

private Func<IHttpRequest, Stream, Task<Stream>> TransformRequest =>
    async (res, requestStream) => {
        using var reader = new StreamReader(requestStream, Encoding.UTF8);
        var requestBody = await reader.ReadToEndAsync();
        Log.Information("request: " + requestBody);
        return MemoryStreamFactory.GetStream(requestBody.ToUtf8Bytes());
    };

Where you can:

Plugins.Add(new ProxyFeature(...) {
    TransformRequest = TransformRequest,
});

Although in this case you may want to create a factory method that constructs the PluginFeature instead, e.g:

ProxyFeature CreateProxyRule(Func<IHttpRequest, bool> matchingRequests,
    Func<IHttpRequest, string> resolveUrl)
{
    return new ProxyFeature(matchingRequests, resolveUrl)
    {
        ProxyRequestFilter = (req, httpWebRequest) => {
            httpWebRequest.AddBasicAuth(clientId, clientPassword);
        },
        TransformRequest = async (res, requestStream) => {
            using var reader = new StreamReader(requestStream, Encoding.UTF8);
            var requestBody = await reader.ReadToEndAsync();
            Log.Information("request: " + requestBody);
            return MemoryStreamFactory.GetStream(requestBody.ToUtf8Bytes());
        },
        TransformResponse = async (res, responseStream) => {
            using var reader = new StreamReader(responseStream, Encoding.UTF8);
            var responseBody = await reader.ReadToEndAsync();
            Log.Information("response: " + responseBody);
            return MemoryStreamFactory.GetStream(responseBody.ToUtf8Bytes());
        }
    };
}

Then just pass in all the parts that are configurable between your different proxies, e.g:

Plugins.Add(CreateProxyRule(
    matchingRequests: req => req.PathInfo.ToLower().StartsWith("/opretaftale"), 
    resolveUrl: req => gensamOpretaftaleUrl));

As HTTP Requests are typically forward-only streams if you want to log the body you would need to buffer the Request/Response so for Proxy requests you would need to do this in TransformRequest/TransformResponse.

If you just wanted to log ServiceStack Requests you could register a Request Logger with EnableRequestBodyTracking enabled, e.g:

Plugins.Add(new RequestLogsFeature { EnableRequestBodyTracking = true });

Which will enable Request Buffering in order to be able to buffer the Request Body for logging & deserialization.

Upvotes: 2

Related Questions