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

Dyn*_*nde 2 asp.net asp.net-mvc razor asp.net-core

我正在努力使我的asp.net core 2应用程序像使用URL Rewrite规则的反向代理一样.

我在startup.cs中有以下内容:

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

重写规则与我的IIS设置(我试图用这种方法替换)完全一样,工作正常.

我假设它与转发标题有关可能吗?或者我可能只是不明白重写中间件应该如何工作,如果你想要转发请求而不是只相对于当前主机名重写.

Kal*_*ten 9

可以在中间件中模拟/实现反向代理:

首先是我们添加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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来,我们将创建IUrlRewriter的基本实现.RewriteUri方法必须将HttpContext转换为绝对Uri.如果不应在中间件中重定向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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后中间件(从ASPNET代理的旧verison被盗回购和定制).它将IUrlRewrite服务作为Invoke方法的参数.

管道是:

  • 尝试重写网址
  • 创建一个HttpRequestMessage
  • 复制请求标题和内容
  • 发送请求
  • 复制响应标头
  • 复制回复内容
  • DONE

瞧瞧

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

额外奖励:其他一些重写者

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")));
}
Run Code Online (Sandbox Code Playgroud)

编辑

我发现了一个相同的项目,但有更多其他功能https://github.com/damianh/ProxyKit作为nuget包