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

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

我正在努力使我的 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 设置(我正在尝试用此方法替换)中的规则完全一样,它工作正常。

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

最佳答案

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

首先是我们添加 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 包

    关于ASP.NET core 2 充当反向代理用户重写中间件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52393880/

    相关文章:

    asp.net-mvc-3 - 使用 MVC Razor 实现电子商务分析

    c# - 读取子组件值的最佳实践

    c# - ASP.NET MVC DropDownList 在事件时不显示颜色

    c# - ASP.NET MVC 从虚拟路径获取文件

    asp.net - 我可以将 asp.net 放在 css 中吗?

    c# - 缺少 HttpClient StatusDescription

    asp.net-mvc - 带有文件扩展名的 ASP.NET MVC 路由不再在生产中工作

    javascript - 在 MVC 中有条件地更改字体颜色

    c# - 如何用javascript将文本框的值相乘

    asp.net-mvc - 如何在 SQL Server 中为 Asp.Net MVC 4 应用程序或 WebRole 应用程序存储 asp.net session 状态?