locked
Optimisation of Reverse Proxy RRS feed

  • Question

  • Hi guys,

    So I am currently working on implementing a new project I am using MVC .NET Core 3.1 to do all of my developments.

    I have a requirement to create a reverse proxy which all of my client side applications will connect to. The one I am having the biggest issue with is a connection to an on premise Analysis Services Instance, because the response content can get quite large it results in slow response times for my client.

    Here is the code that I have:

    using Microsoft.AspNetCore.Http;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net.Http;
    using System.Threading.Tasks;
    
    namespace SSASProxyTest2
    {
        public class ReverseProxyMiddleware
        {
            private static readonly HttpClient _httpClient = new HttpClient();
            private readonly RequestDelegate _nextMiddleware;
    
            public ReverseProxyMiddleware(RequestDelegate nextMiddleware)
            {
                _nextMiddleware = nextMiddleware;
            }
    
            public async Task Invoke(HttpContext context)
            {
                var targetUri = BuildTargetUri(context.Request);
    
                if (targetUri != null)
                {
                    var targetRequestMessage = CreateTargetMessage(context, targetUri);
    
    
                    using (var responseMessage = await _httpClient.SendAsync(targetRequestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
                    {
                        context.Response.StatusCode = (int)responseMessage.StatusCode;
                        CopyFromTargetResponseHeaders(context, responseMessage);
                        await responseMessage.Content.CopyToAsync(context.Response.Body);
                    }
                    return;
                }
                await _nextMiddleware(context);
            }
    
            private HttpRequestMessage CreateTargetMessage(HttpContext context, Uri targetUri)
            {
                var requestMessage = new HttpRequestMessage();
                CopyFromOriginalRequestContentAndHeaders(context, requestMessage);
                String username = "username";
                String password = "password";
                String encoded = Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
                requestMessage.Headers.Add("Authorization", "Basic " + encoded);
    
                requestMessage.RequestUri = targetUri;
                requestMessage.Headers.Host = targetUri.Host;
                requestMessage.Method = GetMethod(context.Request.Method);
    
                return requestMessage;
            }
    
            private void CopyFromOriginalRequestContentAndHeaders(HttpContext context, HttpRequestMessage requestMessage)
            {
                var requestMethod = context.Request.Method;
    
                if (!HttpMethods.IsGet(requestMethod) &&
                  !HttpMethods.IsHead(requestMethod) &&
                  !HttpMethods.IsDelete(requestMethod) &&
                  !HttpMethods.IsTrace(requestMethod))
                {
                    var streamContent = new StreamContent(context.Request.Body);
                    requestMessage.Content = streamContent;
                }
    
                foreach (var header in context.Request.Headers)
                {
                    requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
                }
            }
    
            private void CopyFromTargetResponseHeaders(HttpContext context, HttpResponseMessage responseMessage)
            {
                foreach (var header in responseMessage.Headers)
                {
                    context.Response.Headers[header.Key] = header.Value.ToArray();
                }
    
                foreach (var header in responseMessage.Content.Headers)
                {
                    context.Response.Headers[header.Key] = header.Value.ToArray();
                }
                context.Response.Headers.Remove("transfer-encoding");
            }
            private static HttpMethod GetMethod(string method)
            {
                if (HttpMethods.IsDelete(method)) return HttpMethod.Delete;
                if (HttpMethods.IsGet(method)) return HttpMethod.Get;
                if (HttpMethods.IsHead(method)) return HttpMethod.Head;
                if (HttpMethods.IsOptions(method)) return HttpMethod.Options;
                if (HttpMethods.IsPost(method)) return HttpMethod.Post;
                if (HttpMethods.IsPut(method)) return HttpMethod.Put;
                if (HttpMethods.IsTrace(method)) return HttpMethod.Trace;
                return new HttpMethod(method);
            }
    
            private Uri BuildTargetUri(HttpRequest request)
            {
                Uri targetUri = new Uri("https://Example.com/msmdpump.dll");
    
                return targetUri;
            }
        }
    }

    As you can see I am taking the original request changing the target, adding credentials then returning the response.

    This is where the block seems to be on large requests:

    await responseMessage.Content.CopyToAsync(context.Response.Body);

    This is going to be MiddelWare in my application. For completeness my startupclass has this:

     app.Map("/api/test", api =>
                {
                    api.UseMiddleware<ReverseProxyMiddleware>();
                });

    Can anyone tell me if this can be optimised further to deal with large responses?

    Really appreciate any help.

    Joe

    • Moved by CoolDadTx Thursday, April 23, 2020 1:27 PM ASP.NET related
    Wednesday, April 22, 2020 2:27 PM

All replies

  • ASP.NET Core can be discussed in the ASP.NET forums.

    http://forums.asp.net/

    Wednesday, April 22, 2020 11:42 PM
  • Thanks reposted
    Thursday, April 23, 2020 12:41 AM