ASP.NET The implementation of proxy forwarding in core

Time:2021-2-14

preface

Let’s talk about the development background of this article first

Under the background of front and back end separation, our customers have requirements again~

To separate the front end from the back end ~ however, for various reasons, there is no way to use a pure front-end framework (in fact, the learning cost is high, and there is no money for front-end developers)

So a plan was finally decided

That is to use MVC (only deal with the front view layer, just for hosting on. Net core) + webapi to realize the separation of the front and back view layers

Then the problem comes

At present, the mainstream front-end frameworks are hosted on nodejs. They access the back-end API through Axios, and cross domain access can be achieved by configuring the proxy configuration (proxytable) of Axios

So our JS runs on MVC and is hosted on. Net core.. what should we do?… there is no ready-made forwarding wheel.. we have to build our own

So this is the background of this article~

text

Fortunately, ASP.NET Core provides us with powerful middleware pattern

We can completely implement the forwarding of proxy interface by defining a forwarding middleware. The process is as follows:

Let’s create our middleware

1、 Interface and implementation of creating detection contract URL

First, we define an interface iurlrewriter to detect whether our URL has a corresponding prefix. If so, a new URL address will be generated

Here we define the interface to facilitate better replacement of the injection class in the future


public interface IUrlRewriter
{
    Task<Uri> RewriteUri(HttpContext context);
}

This interface is implemented as follows (explanations are in the comments)

public class PrefixRewriter : IUrlRewriter
  {
    private readonly PathString _ Prefix; // prefix value
    private readonly string _ Newhost; // forwarding address

    public PrefixRewriter(PathString prefix, string newHost)
    {
      _prefix = prefix;
      _newHost = newHost;
    }

    public Task<Uri> RewriteUri(HttpContext context)
    {
      if ( context.Request.Path .StartsWithSegments(_ Prefix)) // determine whether the access contains a prefix
      {
        var newUri = context.Request.Path.Value.Remove(0, _prefix.Value.Length) + context.Request.QueryString;
        var targetUri = new Uri(_newHost + newUri);
        return Task.FromResult(targetUri);
      }

      return Task.FromResult((Uri)null);
    }
  }

2、 Create proxyhttpclient for proxy forwarding

The main purpose of creating an independent proxyhttpclient is to distinguish the httpclient forwarded by the proxy, so as to facilitate later log addition or other processing


public class ProxyHttpClient
  {
    public HttpClient Client { get; private set; }

    public ProxyHttpClient(HttpClient httpClient)
    {
      Client = httpClient;
    }
  }

3、 Creating middleware for proxy forwarding

The code is as follows. Middleware is mainly the invoke method. You can see the comments for the description

public class ProxyMiddleware
  {
    // private ProxyHttpClient _proxyHttpClient;
    private const string CDN_HEADER_NAME = "Cache-Control";
    private static readonly string[] NotForwardedHttpHeaders = new[] { "Connection", "Host" };
    private readonly RequestDelegate _next;
    private readonly ILogger<ProxyMiddleware> _logger;

    public ProxyMiddleware(
        RequestDelegate next,
        ILogger<ProxyMiddleware> logger
        
        )
    {
      _next = next;
      _logger = logger;
      //_proxyHttpClient = proxyHttpClient;
    }

    /// <summary>
    ///Through middleware, access is intercepted, prefix is detected and forwarded
    /// </summary>
    /// <param name="context"></param>
    /// <param name="urlRewriter"></param>
    /// <returns></returns>
    public async Task Invoke(HttpContext context, IUrlRewriter urlRewriter, ProxyHttpClient proxyHttpClient)
    {
      var targetUri = await urlRewriter.RewriteUri(context);

      if (targetUri != null)
      {
        var requestMessage = GenerateProxifiedRequest(context, targetUri);
        await SendAsync(context, requestMessage, proxyHttpClient);

        return;
      }

      await _next(context);
    }

    private async Task SendAsync(HttpContext context, HttpRequestMessage requestMessage, ProxyHttpClient proxyHttpClient)
    {
    

      using (var responseMessage = await proxyHttpClient.Client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
      {
        context.Response.StatusCode = (int)responseMessage.StatusCode;

        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");

        if (!context.Response.Headers.ContainsKey(CDN_HEADER_NAME))
        {
          context.Response.Headers.Add(CDN_HEADER_NAME, "no-cache, no-store");
        }

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

    private static HttpRequestMessage GenerateProxifiedRequest(HttpContext context, Uri targetUri)
    {
      var requestMessage = new HttpRequestMessage();
      CopyRequestContentAndHeaders(context, requestMessage);
      requestMessage.RequestUri = targetUri;
      requestMessage.Headers.Host = targetUri.Host;
      requestMessage.Method = GetMethod(context.Request.Method);
      return requestMessage;
    }

    private static void CopyRequestContentAndHeaders(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)
      {
        if (!NotForwardedHttpHeaders.Contains(header.Key))
        {
          if (header.Key != "User-Agent")
          {
            if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
            {
              requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
            }
          }
          else
          {
            string userAgent = header.Value.Count > 0 ? (header.Value[0] + " " + context.TraceIdentifier) : string.Empty;

            if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, userAgent) && requestMessage.Content != null)
            {
              requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, userAgent);
            }
          }

        }
      }
    }

    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);
    }

4、 Inject and enable our middleware and proxyhttpclient

We add the following code to the configuration services of startup to inject our httpclient and iurlrewriter, as follows:

services.AddHttpClient<ProxyHttpClient>()
      .ConfigurePrimaryHttpMessageHandler(x => new HttpClientHandler()
      {
        AllowAutoRedirect = false,
        MaxConnectionsPerServer = int.MaxValue,
        UseCookies = false,
      }); // inject our defined httpclient
 services.AddSingleton <IUrlRewriter>(new PrefixRewriter("/webapp", " http://localhost : 63445 "); // fill in the prefix and the address to forward here

Then, in the configuration of startup, start our middleware as follows:


app.UseMiddleware<ProxyMiddleware>();

5、 Test middleware effect

We write the front-end code as follows:


created: function () {
        this.mockTableData1();
        axios.get("/webapp/api/values/get", "123").then(res => { alert(res.data[0]) });
        axios.post("/webapp/api/values/post",{value: 'david'}).then(res => { alert(res.data.message) });

      }

In another webapi project, the interface is as follows:


[HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
      return new string[] { "value1", accstring.ToString() };
    }

    [HttpPost]
    public AjaxResult Post(dynamic value)
    {
      string aaa = JsonConvert.SerializeObject(value);
      return Success("OK");
    }

The effect is as follows. You can see that our view correctly gets the return value:

Write at the end

Here we implement the proxy forwarding of the interface in the form of middleware, and there will be some small problems in the specific use process, and here we only implement the forwarding of HTTP. WS does not

If you want to use it, there is an open source project abroadhttps://github.com/ProxyKitThere are more than 900 stars. It should be good

This is about ASP.NET This is the article about the implementation of proxy forwarding of core’s ingenious interface, and more related ASP.NET Core interface agent forwarding content, please search previous articles of developer or continue to browse the following related articles. I hope you can support developer more in the future!