简体   繁体   English

ASP.NET核心2充当反向代理用户重写中间件

[英]ASP.NET core 2 act as reverse proxy usering rewrite middleware

I'm struggling to make my asp.net core 2 app act like a reverse proxy using URL Rewrite rules. 我正在努力使我的asp.net core 2应用程序像使用URL Rewrite规则的反向代理一样。

I have the following in my startup.cs: 我在startup.cs中有以下内容:

var rewriteRules = new RewriteOptions()
                .AddRedirectToHttps();
                .AddRewrite(@"^POC/(.*)", "http://192.168.7.73:3001/$1", true);
app.UseRewriter(rewriteRules);

The rewrite rule is exactly as it is in my IIS settings (which I'm trying to replace with this method) which works fine. 重写规则与我的IIS设置(我试图用这种方法替换)完全一样,工作正常。

I'm assuming it has something to do with forwarding the headers maybe? 我假设它与转发标题有关可能吗? Or maybe I just don't understand how the Rewrite Middleware is supposed to work, if you want the requests to be forwarded instead of just rewritten relative to current hostname. 或者我可能只是不明白重写中间件应该如何工作,如果你想要转发请求而不是只相对于当前主机名重写。

A reverse proxy can be emulated/implemeted within a middleware : 可以在中间件中模拟/实现反向代理:

First the startup class where we add a IUrlRewriter service and the ProxyMiddleware. 首先是我们添加IUrlRewriter服务和ProxyMiddleware的启动类。

public class Startup
{
    private readonly IConfiguration _configuration;

    public Startup(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IUrlRewriter>(new SingleRegexRewriter(@"^/POC/(.*)", "http://192.168.7.73:3001/$1"));
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseRewriter(new RewriteOptions().AddRedirectToHttps());
        app.UseMiddleware<ProxyMiddleware>();
    }
}

Next we will create a basic implementation of IUrlRewriter. 接下来,我们将创建IUrlRewriter的基本实现。 The RewriteUri method must transform the HttpContext into an absolute Uri. RewriteUri方法必须将HttpContext转换为绝对Uri。 Or null if the url should not be redirected in the middleware. 如果不应在中间件中重定向url,则返回null。

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

public class SingleRegexRewriter : IUrlRewriter
{
    private readonly string _pattern;
    private readonly string _replacement;
    private readonly RegexOptions _options;

    public SingleRegexRewriter(string pattern, string replacement)
        : this(pattern, replacement, RegexOptions.None) { }

    public SingleRegexRewriter(string pattern, string replacement, RegexOptions options)
    {
        _pattern = pattern ?? throw new ArgumentNullException(nameof(pattern));
        _replacement = replacement ?? throw new ArgumentNullException(nameof(pattern));
        _options = options;
    }

    public Task<Uri> RewriteUri(HttpContext context)
    {
        string url = context.Request.Path + context.Request.QueryString;
        var newUri = Regex.Replace(url, _pattern, _replacement);

        if (Uri.TryCreate(newUri, UriKind.Absolute, out var targetUri))
        {
            return Task.FromResult(targetUri);
        }

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

And then the Middleware (stolen from an old verison of aspnet proxy repo ) and customized. 然后中间件(从ASPNET代理的旧verison被盗回购和定制)。 It get the IUrlRewrite service as parameter of Invoke method. 它将IUrlRewrite服务作为Invoke方法的参数。

The pipeline is : 管道是:

  • Try rewrite url 尝试重写网址
  • Create a HttpRequestMessage 创建一个HttpRequestMessage
  • Copy Request Header and content 复制请求标题和内容
  • Send the request 发送请求
  • Copy response header 复制响应标头
  • Copy response content 复制回复内容
  • done DONE

Et voila 瞧瞧

public class ProxyMiddleware
{
    private static readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler()
    {
        AllowAutoRedirect = false,
        MaxConnectionsPerServer = int.MaxValue,
        UseCookies = false,
    });

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

    public async Task Invoke(HttpContext context, IUrlRewriter urlRewriter)
    {
        var targetUri = await urlRewriter.RewriteUri(context);

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

            return;
        }

        await _next(context);
    }

    private async Task SendAsync(HttpContext context, HttpRequestMessage requestMessage)
    {
        using (var responseMessage = await _httpClient.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);
    }
}

Bonus : some other Rewriter 额外奖励:其他一些重写者

public class PrefixRewriter : IUrlRewriter
{
    private readonly PathString _prefix;
    private readonly string _newHost;

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

    public Task<Uri> RewriteUri(HttpContext context)
    {
        if (context.Request.Path.StartsWithSegments(_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);
    }
}

public class MergeRewriter : IUrlRewriter
{
    private readonly List<IUrlRewriter> _rewriters = new List<IUrlRewriter>();
    public MergeRewriter()
    {
    }
    public MergeRewriter(IEnumerable<IUrlRewriter> rewriters)
    {
        if (rewriters == null) throw new ArgumentNullException(nameof(rewriters));

        _rewriters.AddRange(rewriters);
    }

    public MergeRewriter Add(IUrlRewriter rewriter)
    {
        if (rewriter == null) throw new ArgumentNullException(nameof(rewriter));

        _rewriters.Add(rewriter);

        return this;
    }

    public async Task<Uri> RewriteUri(HttpContext context)
    {
        foreach (var rewriter in _rewriters)
        {
            var targetUri = await rewriter.RewriteUri(context);
            if(targetUri != null)
            {
                return targetUri;
            }
        }

        return null;
    }
}

// In Statup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IUrlRewriter>(new MergeRewriter()
        .Add(new PrefixRewriter("/POC/API", "http://localhost:1234"))
        .Add(new SingleRegexRewriter(@"^/POC/(.*)", "http://192.168.7.73:3001/$1")));
}

Edit 编辑

I found a project to do same but with way more other feature https://github.com/damianh/ProxyKit as a nuget package 我发现了一个相同的项目,但更多的其他功能https://github.com/damianh/ProxyKit作为一个nuget包

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM