• 基于Yarp实现内网http穿透


    Yarp介绍

    YARP是微软开源的用来代理服务器的反向代理组件,可实现的功能类似于nginx。
    基于YARP,开发者可以非常快速的开发一个性能不错的小nginx,用于代理http(s)请求到上游的http(s)服务。

    http穿透原理

    同网现象

    在http反向代理里,代理服务器总是上游服务的http客户端,为了网络性能,实际上上游服务总是和代理服务处在同一个局域网。试问一个问题:在公网的小nginx,如何把请求代理到局域网的http服务器?你会发现,小nginx做不到,因为小nginx所在公网服务器,无法直接与局域网的http服务器进行通信。

    http穿透

    在tcp里,进行连接时总是由客户端发起,但当连接之后客户端与服务端是平等的,他们之间可以双向收发数据。只要公网小nginx与局域网的http服务器存在tcp连接,我们可以使用这个连接做为httpClient的连接层,然后小nginx使用这个httpClient请求到局域网http服务器,而从达到http穿透效果。

    完整流程

    image

    基于Yarp的http穿透

    main连接

    我们可以使用websocket协议,创建main连接,主要有以下好处:

    • 共享代理服务器监听的http(s)端口
    • 利用websocket的ping-pong实现连接检测
    • 利用websocket连接请求头进行身份认证

    接收局域网创建的连接

    我们可以为kestrel编写中间件,用获取获取局域网主动创建的tcp连接,这些连接与代理服务器与浏览器之间的连接共享同一个服务器端口,以下的listen.Use(transportService.OnConnectedAsync);是一个kestrel中间件。

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

    绑定连接到HttpClient

    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>
    /// 配置中间件
    /// </summary>
    /// <param name="app"></param>
    /// <param name="connectionService"></param>
    /// <param name="httpForwarderService"></param> 
    public void Configure(IApplicationBuilder app, IHostEnvironment hostEnvironment, ConnectionService connectionService, HttpForwarderService httpForwarderService)
    { 
        app.UseWebSockets();
        app.Use(connectionService.OnConnectedAsync);
        app.Use(httpForwarderService.SendAsync);
    }
    

    通过请求的域名,找到局域网要转发的最终服务器地址,做为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);
        } 
    }
    

    总结

    基于kestrel和SocketsHttpHandler高度可定制化的扩展能力,结合Yarp组件,我们可以很方便的开发一个支持内网http穿透的公网http反向代理服务器。如果把泛域名指向公网反向代理服务器,最终实现一个二级域名绑定流量到一个局域网http服务器的一对多功能。

  • 相关阅读:
    tensorflow入门
    【CentOS】yum安装教训
    【转载】Linux的五个查找命令
    【git】本地git bash连接远程库github
    【python学习】字符串相关
    【Linux】单计算机安装PBS系统(Torque)与运维
    xshell上windows和linux互传文件命令
    【Linux】Linux命令行下多任务前后台切换
    【python】windows更改jupyter notebook(ipython)的默认打开工作路径
    【Introduction】R语言入门关键小结
  • 原文地址:https://www.cnblogs.com/kewei/p/14985585.html
Copyright © 2020-2023  润新知