问题描述
我正在努力使我的asp.net core 2应用程序使用URL重写规则像反向代理一样.
I'm struggling to make my asp.net core 2 app act like a reverse proxy using URL Rewrite rules.
startup.cs中包含以下内容:
I have the following in my startup.cs:
var rewriteRules = new RewriteOptions()
.AddRedirectToHttps();
.AddRewrite(@"^POC/(.*)", "http://192.168.7.73:3001/$1", true);
app.UseRewriter(rewriteRules);
重写规则与我的IIS设置(我正在尝试用此方法替换)中的规则完全一样.
The rewrite rule is exactly as it is in my IIS settings (which I'm trying to replace with this method) which works fine.
我假设它与转发标头有关吗?或者,如果您希望转发请求而不是仅相对于当前主机名进行重写,那么也许我只是不明白重写中间件应该如何工作.
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 :
首先,我们在启动类中添加IUrlRewriter服务和ProxyMiddleware.
First the startup class where we add a IUrlRewriter service and the 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.如果网址不应在中间件中重定向,则为null.
Next we will create a basic implementation of IUrlRewriter. The RewriteUri method must transform the HttpContext into an absolute Uri. Or null if the url should not be redirected in the middleware.
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
方法的参数.
And then the Middleware (stolen from an old verison of aspnet proxy repo) and customized. It get the IUrlRewrite service as parameter of Invoke
method.
管道是:
- 尝试重写网址
- 创建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包
Edit
I found a project to do same but with way more other feature https://github.com/damianh/ProxyKit as a nuget package
这篇关于ASP.NET Core 2充当反向代理用户重写中间件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!