【问题标题】:ASP.NET core 2 act as reverse proxy usering rewrite middlewareASP.NET core 2 充当反向代理用户重写中间件
【发布时间】:2019-02-22 22:06:36
【问题描述】:

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

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

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

重写规则与我的 IIS 设置(我试图用此方法替换)中的完全一样,效果很好。

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

【问题讨论】:

  • 究竟是什么不起作用?
  • 抱歉,该应用程序似乎使用重写规则来简单地路由到我的反向代理应用程序的根目录。因此,在示例中,假设我的反向代理应用程序在 localhost 上运行,然后 localhost/poc 路由到 localhost。如果我添加到 url,比如说 /poc/mycontroller/action 我得到 404(即使所述路由在我的反向代理应用程序上有效)
  • 重写中间件只会改变当前的HttpContext.Request.Path。它不会重定向或调用任何外部服务。 aspnet github中有一个poc代表here
  • @Dynde 我想你在这里没有找到答案,对吧? docs.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting 如果是这样,您能否说明您正在尝试做的事情的目的,以帮助我们更加了解您的需求,并为此提出替代解决方案?
  • 好吧,正如@Kalten 所说,重写中间件似乎不适合我的用例。我还没有找到一种方法来让 Kalten 所指的 Proxy 工作。我认为就我的情况而言,我将不得不依赖 IIS 来满足我的反向代理需求。我的主要问题是我正在尝试创建动态反向代理映射,因此我需要修改 IIS 应用程序的 web.config 文件。我很可能只需要这样做,并依靠应用程序回收(来自应用程序)来启用新的反向代理映射

标签: asp.net asp.net-mvc razor asp.net-core


【解决方案1】:

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

首先是我们添加 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>();
    }
}

接下来我们将创建 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);
    }
}

然后是中间件(从旧版本的 aspnet 代理 repo 窃取)并定制。它获取 IUrlRewrite 服务作为Invoke 方法的参数。

管道是:

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

等等

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

奖励:其他一些重写器

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

编辑

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

【讨论】:

  • 太美了!谢谢!工作一种享受!
  • 您应该添加一些日志记录并尝试捕获。
猜你喜欢
  • 2019-07-01
  • 1970-01-01
  • 2017-02-18
  • 2014-03-17
  • 1970-01-01
  • 1970-01-01
  • 2020-05-14
  • 2014-03-24
  • 2014-02-01
相关资源
最近更新 更多