https://blog.csdn.net/sD7O95O/article/details/118616751
YARP is Level 7 (HTTP), not Level 4 (TCP), so only HTTP traffic would be relevant.
Setting up YARP behind a router is fine, but you'd have to do a more traditional port forwarding in the router to access it. From there, yes, it could route requests to internal HTTP services.
The proxy server receives an authorized request.
需要开发一个客户端
负责接收服务端发来的请求及向内网网站应用发起请求,获取返回结果并将数据发回服务端,再由服务端返回给请求方。
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Options;
using System.Net.Security;
using System.Net;
using System.Net.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace HttpMouse.Server
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
public static IWebHostBuilder UseKestrelTransportChannel(this IWebHostBuilder hostBuilder)
{
return hostBuilder.UseKestrel(kestrel =>
{
var transportService = kestrel.ApplicationServices.GetRequiredService<TransportChannelService>();
var options = kestrel.ApplicationServices.GetRequiredService<IOptions<HttpMouseOptions>>().Value;
var http = options.Listen.Http;
if (http != null)
{
kestrel.Listen(http.IPAddress, http.Port, listen =>
{
listen.Use(transportService.OnConnectedAsync);
});
}
var https = options.Listen.Https;
if (https != null && File.Exists(https.Certificate.Path))
{
kestrel.Listen(https.IPAddress, https.Port, listen =>
{
listen.Protocols = HttpProtocols.Http1AndHttp2;
listen.UseHttps(https.Certificate.Path, https.Certificate.Password);
listen.Use(transportService.OnConnectedAsync);
});
}
});
}
}
}
// Yarp进行代理时,需要指定HttpMessageInvoker,HttpMessageInvoker实际是SocketsHttpHandler的包装。而SocketsHttpHandler可以设置ConnectCallback属性,用于指定连接。
private static HttpMessageInvoker CreateHttpClient(TransportChannelService transportChannelService)
{
return new HttpMessageInvoker(new SocketsHttpHandler()
{
UseProxy = false,
UseCookies = false,
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.None,
ConnectCallback = transportChannelService.CreateChannelAsync,
SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = delegate { return true; }
}
});
}
//通过请求的域名,找到局域网要转发的最终服务器地址,做为yarp的请求地址。
/// <summary>
/// 发送http数据
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public async Task SendAsync(HttpContext httpContext, Func<Task> next)
{
var clientDomain = httpContext.Request.Host.Host;
if (this.connectionService.TryGetClientUpStream(clientDomain, out var clientUpstream))
{
var destPrefix = clientUpstream.ToString();
if (this.options.CurrentValue.HttpRequest.TryGetValue(clientDomain, out var requestConfig) == false)
{
requestConfig = this.defaultRequestConfig;
}
await this.httpForwarder.SendAsync(httpContext, destPrefix, httpClient, requestConfig, this.transformer);
}
}
blog:
https://www.iaspnetcore.com/blogpost-65e4fbac4cc16d0284ca9a06-httpmouse
https://blog.csdn.net/went361346846/article/details/86621179
2.
var handler = new SocketsHttpHandler()
{
ConnectTimeout = TimeSpan.FromSeconds(10),
};
var client = new HttpClient(handler);
ConnectCallback 属性实现
handler.ConnectCallback = async (context, cancellationToken) =>
{
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
try
{
socket.NoDelay = true;
// 这里可以自己偷偷改掉域名哦,也就是将原本请求的域名修改为一个奇怪的域名。这里偷偷改了,团队的其他伙伴可是很难调试出来的哦
await socket.ConnectAsync(context.DnsEndPoint, cancellationToken)
// 配置异步等待后不需要回到原来的线程
.ConfigureAwait(false);
// 发送的超时时间,相当于请求的超时
socket.SendTimeout = (int) TimeSpan.FromSeconds(10).TotalMilliseconds;
// 接收的超时时间,相当于响应的超时
socket.ReceiveTimeout = (int) TimeSpan.FromSeconds(5).TotalMilliseconds;
}
catch
{
socket.Dispose();
throw;
}
// 在 NetworkStream 里,设置 ownsSocket 参数为 true 将会在 NetworkStream 被释放的时候,自动释放 Socket 资源
return new NetworkStream(socket, ownsSocket: true);
};
3.
static async Task Main()
{
using SocketsHttpHandler handler = new SocketsHttpHandler();
handler.ConnectCallback = async (ctx, ct) =>
{
var s = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
try
{
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
s.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 5);
s.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 5);
s.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 5);
await s.ConnectAsync(ctx.DnsEndPoint, ct);
return new NetworkStream(s, ownsSocket: true);
}
catch
{
s.Dispose();
throw;
}
};
// Create an HttpClient object
using HttpClient client = new HttpClient(handler);
// Call asynchronous network methods in a try/catch block to handle exceptions
try
{
HttpResponseMessage response = await client.GetAsync("https://docs.microsoft.com/");
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Read {responseBody.Length} characters");
}
catch (HttpRequestException e)
{
Console.WriteLine("\nException Caught!");
Console.WriteLine($"Message: {e.Message} ");
}
}