受欢迎的博客标签

基于Yarp实现内网http穿透

Published

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

https://learn.microsoft.com/en-us/dotnet/api/system.net.http.socketshttphandler.connectcallback?view=net-8.0