├── .gitattributes ├── .gitignore ├── Directory.Build.props ├── HttpMouse.Client ├── HttpMouse.Client.csproj ├── HttpMouseClientOptions.cs ├── HttpMouseClientServiceCollectionExtensions.cs ├── IHttpMouseClient.cs ├── IHttpMouseClientFactory.cs └── Implementions │ ├── HttpMouseClient.cs │ └── HttpMouseClientFactory.cs ├── HttpMouse.png ├── HttpMouse.sln ├── HttpMouse ├── DependencyInjection │ ├── HttpMouseApplicationBuilderExtensions.cs │ └── HttpMouseServiceCollectionExtensions.cs ├── HttpMouse.csproj ├── HttpMouseOptions.cs ├── IHttpMouseClient.cs ├── IHttpMouseClientHandler.cs ├── IHttpMouseClientVerifier.cs ├── IHttpMouseClusterProvider.cs ├── IHttpMouseRouteProvider.cs ├── IReverseConnectionHandler.cs └── Implementions │ ├── DefaultHttpMouseClientVerifier.cs │ ├── DefaultHttpMouseClusterProvider.cs │ ├── DefaultHttpMouseRouteProvider.cs │ ├── HttpMouseClient.cs │ ├── HttpMouseClientHandler.cs │ ├── HttpMouseForwarderHttpClientFactory.cs │ ├── HttpMouseProxyConfigProvider.cs │ ├── ReverseConnection.cs │ └── ReverseConnectionHandler.cs ├── README.md └── samples ├── HttpMouse.ClientHost ├── HttpMouse.ClientHost.csproj ├── HttpMouseClientHostedService.cs ├── Program.cs └── appsettings.json └── HttpMouse.ServerHost ├── FallbackOptions.cs ├── HttpMouse.ServerHost.csproj ├── Program.cs ├── Properties └── launchSettings.json ├── Startup.cs ├── appsettings.json └── fallback.json /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.0-beta1 4 | enable 5 | net5.0 6 | http公网反向代理到内网的服务端与客户端库 7 | 8 | -------------------------------------------------------------------------------- /HttpMouse.Client/HttpMouse.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | true 5 | true 6 | MIT 7 | https://github.com/xljiulang/httpmouse 8 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /HttpMouse.Client/HttpMouseClientOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace HttpMouse.Client 6 | { 7 | /// 8 | /// 客户端选项 9 | /// 10 | public class HttpMouseClientOptions 11 | { 12 | /// 13 | /// 服务器地址 14 | /// 15 | [AllowNull] 16 | [Required] 17 | public Uri Server { get; set; } 18 | 19 | /// 20 | /// 服务器密钥 21 | /// 22 | public string? ServerKey { get; set; } 23 | 24 | /// 25 | /// 客户端上游地址 26 | /// 27 | [AllowNull] 28 | [Required] 29 | public Uri ClientUpstream { get; set; } 30 | 31 | /// 32 | /// 映射到客户端的服务器域名或ip 33 | /// 34 | [AllowNull] 35 | [Required] 36 | public string ClientDomain { get; set; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /HttpMouse.Client/HttpMouseClientServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using HttpMouse.Client.Implementions; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | 6 | namespace HttpMouse.Client 7 | { 8 | /// 9 | /// HttpMouseClient扩展 10 | /// 11 | public static class HttpMouseClientServiceCollectionExtensions 12 | { 13 | /// 14 | /// 添加HttpMouseClient工厂 15 | /// 16 | /// 17 | /// 18 | /// 19 | public static IServiceCollection AddHttpMouseClient(this IServiceCollection services, Action configureOptions) 20 | { 21 | return services.AddHttpMouseClient().Configure(configureOptions); 22 | } 23 | 24 | /// 25 | /// 添加HttpMouseClient工厂 26 | /// 27 | /// 28 | /// 29 | /// 30 | public static IServiceCollection AddHttpMouseClient(this IServiceCollection services, IConfiguration configuration) 31 | { 32 | return services.AddHttpMouseClient().Configure(configuration); 33 | } 34 | 35 | /// 36 | /// 添加HttpMouseClient工厂 37 | /// 38 | /// 39 | /// 40 | public static IServiceCollection AddHttpMouseClient(this IServiceCollection services) 41 | { 42 | services 43 | .AddOptions() 44 | .ValidateDataAnnotations(); 45 | 46 | return services 47 | .AddSingleton(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /HttpMouse.Client/IHttpMouseClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace HttpMouse.Client 6 | { 7 | /// 8 | /// 客户端接口 9 | /// 10 | public interface IHttpMouseClient : IDisposable 11 | { 12 | /// 13 | /// 获取是否连接 14 | /// 15 | bool IsConnected { get; } 16 | 17 | /// 18 | /// 传输数据 19 | /// 20 | /// 21 | /// 22 | Task TransportAsync(CancellationToken cancellationToken); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /HttpMouse.Client/IHttpMouseClientFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace HttpMouse.Client 5 | { 6 | /// 7 | /// HttpMouseClient工厂 8 | /// 9 | public interface IHttpMouseClientFactory 10 | { 11 | /// 12 | /// 创建客户端实例 13 | /// 14 | /// 15 | /// 16 | Task CreateAsync(CancellationToken cancellation); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /HttpMouse.Client/Implementions/HttpMouseClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.IO; 4 | using System.Net; 5 | using System.Net.Security; 6 | using System.Net.Sockets; 7 | using System.Net.WebSockets; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace HttpMouse.Client.Implementions 13 | { 14 | /// 15 | /// 客户端 16 | /// 17 | sealed class HttpMouseClient : IHttpMouseClient 18 | { 19 | private readonly ClientWebSocket webScoket; 20 | private readonly HttpMouseClientOptions options; 21 | private readonly CancellationTokenSource disposeCancellationTokenSource = new(); 22 | 23 | /// 24 | /// 获取是否连接 25 | /// 26 | public bool IsConnected => this.webScoket.State == WebSocketState.Open; 27 | 28 | /// 29 | /// 客户端 30 | /// 31 | /// 32 | /// 33 | public HttpMouseClient(ClientWebSocket webScoket, HttpMouseClientOptions options) 34 | { 35 | this.webScoket = webScoket; 36 | this.options = options; 37 | } 38 | 39 | /// 40 | /// 传输数据 41 | /// 42 | /// 43 | /// 44 | public async Task TransportAsync(CancellationToken cancellationToken) 45 | { 46 | using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, this.disposeCancellationTokenSource.Token); 47 | try 48 | { 49 | while (true) 50 | { 51 | var connectionId = await this.ReadConnectionIdAsync(cancellationTokenSource.Token); 52 | this.TransportAsync(connectionId, cancellationTokenSource.Token); 53 | } 54 | } 55 | catch (Exception) 56 | { 57 | cancellationTokenSource.Cancel(); 58 | throw; 59 | } 60 | } 61 | 62 | /// 63 | /// 读取要创建的反向连接的id 64 | /// 65 | /// 66 | /// 67 | private async Task ReadConnectionIdAsync(CancellationToken cancellationToken) 68 | { 69 | var buffer = ArrayPool.Shared.Rent(64); 70 | try 71 | { 72 | var result = await this.webScoket.ReceiveAsync(buffer, cancellationToken); 73 | if (result.MessageType == WebSocketMessageType.Close) 74 | { 75 | throw new WebSocketException(WebSocketError.ConnectionClosedPrematurely, result.CloseStatusDescription); 76 | } 77 | 78 | var guid = Encoding.UTF8.GetString(buffer.AsSpan(0, result.Count)); 79 | return Guid.Parse(guid); 80 | } 81 | finally 82 | { 83 | ArrayPool.Shared.Return(buffer); 84 | } 85 | } 86 | 87 | 88 | /// 89 | /// 绑定上下游的连接进行双向传输 90 | /// 91 | /// 92 | /// 93 | private async void TransportAsync(Guid connectionId, CancellationToken cancellationToken) 94 | { 95 | try 96 | { 97 | await Task.Yield(); 98 | 99 | using var upConnection = await this.CreateUpConnectionAsync(cancellationToken); 100 | using var downConnection = await this.CreateDownConnectionAsync(connectionId, cancellationToken); 101 | 102 | var taskX = upConnection.CopyToAsync(downConnection, cancellationToken); 103 | var taskY = downConnection.CopyToAsync(upConnection, cancellationToken); 104 | 105 | await Task.WhenAny(taskX, taskY); 106 | } 107 | catch (Exception) 108 | { 109 | } 110 | } 111 | 112 | /// 113 | /// 创建下游连接 114 | /// 115 | /// 116 | /// 117 | /// 118 | private async Task CreateDownConnectionAsync(Guid connectionId, CancellationToken cancellationToken) 119 | { 120 | var server = this.options.Server; 121 | var endpoint = new DnsEndPoint(server.Host, server.Port); 122 | var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); 123 | await socket.ConnectAsync(endpoint, cancellationToken); 124 | 125 | Stream connection = new NetworkStream(socket, ownsSocket: true); 126 | if (server.Scheme == Uri.UriSchemeHttps) 127 | { 128 | var sslConnection = new SslStream(connection, false, delegate { return true; }); 129 | await sslConnection.AuthenticateAsClientAsync(server.Host); 130 | connection = sslConnection; 131 | } 132 | 133 | var reverse = $"REVERSE /{connectionId} HTTP/1.1\r\nHost: {server.Host}\r\n\r\n"; 134 | var request = Encoding.ASCII.GetBytes(reverse); 135 | await connection.WriteAsync(request, cancellationToken); 136 | 137 | return connection; 138 | } 139 | 140 | /// 141 | /// 创建上游连接 142 | /// 143 | /// 144 | /// 145 | private async Task CreateUpConnectionAsync(CancellationToken cancellationToken) 146 | { 147 | var client = this.options.ClientUpstream; 148 | var endpoint = new DnsEndPoint(client.Host, client.Port); 149 | var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); 150 | await socket.ConnectAsync(endpoint, cancellationToken); 151 | 152 | return new NetworkStream(socket, ownsSocket: true); 153 | } 154 | 155 | /// 156 | /// 释放资源 157 | /// 158 | public void Dispose() 159 | { 160 | this.disposeCancellationTokenSource.Cancel(); 161 | this.disposeCancellationTokenSource.Dispose(); 162 | this.webScoket.Dispose(); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /HttpMouse.Client/Implementions/HttpMouseClientFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System; 3 | using System.Net.WebSockets; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace HttpMouse.Client.Implementions 8 | { 9 | /// 10 | /// 客户端工厂 11 | /// 12 | sealed class HttpMouseClientFactory : IHttpMouseClientFactory 13 | { 14 | private const string SERVER_KEY = "ServerKey"; 15 | private const string CLIENT_DOMAIN = "ClientDomain"; 16 | private const string CLIENT_UP_STREAM = "ClientUpstream"; 17 | 18 | private readonly IOptionsMonitor options; 19 | 20 | /// 21 | /// 客户端工厂 22 | /// 23 | /// 24 | public HttpMouseClientFactory(IOptionsMonitor options) 25 | { 26 | this.options = options; 27 | } 28 | 29 | /// 30 | /// 创建客户端实例 31 | /// 32 | /// 33 | /// 34 | public async Task CreateAsync(CancellationToken cancellationToken) 35 | { 36 | var opt = this.options.CurrentValue; 37 | var uriBuilder = new UriBuilder(opt.Server); 38 | uriBuilder.Scheme = uriBuilder.Scheme == Uri.UriSchemeHttp ? "ws" : "wss"; 39 | 40 | var webSocket = new ClientWebSocket(); 41 | webSocket.Options.RemoteCertificateValidationCallback = delegate { return true; }; 42 | webSocket.Options.SetRequestHeader(SERVER_KEY, opt.ServerKey); 43 | webSocket.Options.SetRequestHeader(CLIENT_DOMAIN, opt.ClientDomain); 44 | webSocket.Options.SetRequestHeader(CLIENT_UP_STREAM, opt.ClientUpstream.ToString()); 45 | 46 | await webSocket.ConnectAsync(uriBuilder.Uri, cancellationToken); 47 | return new HttpMouseClient(webSocket, opt); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /HttpMouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htlp/HttpMouse/27c72d4db501b268625da8c3bdff850fb973759a/HttpMouse.png -------------------------------------------------------------------------------- /HttpMouse.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31320.298 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpMouse", "HttpMouse\HttpMouse.csproj", "{181F98D7-07F5-4A15-A596-F30F6A40CFC7}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpMouse.Client", "HttpMouse.Client\HttpMouse.Client.csproj", "{CB280978-D61C-4833-B907-044CEF3A914D}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpMouse.ClientHost", "samples\HttpMouse.ClientHost\HttpMouse.ClientHost.csproj", "{DCA3F32A-7E3A-44C6-8FA3-35DB695BB9ED}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpMouse.ServerHost", "samples\HttpMouse.ServerHost\HttpMouse.ServerHost.csproj", "{C71A773E-64C2-479D-832A-F80F61F80C83}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{3E09F4C8-86A0-427D-BC27-710572D61400}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {181F98D7-07F5-4A15-A596-F30F6A40CFC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {181F98D7-07F5-4A15-A596-F30F6A40CFC7}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {181F98D7-07F5-4A15-A596-F30F6A40CFC7}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {181F98D7-07F5-4A15-A596-F30F6A40CFC7}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {CB280978-D61C-4833-B907-044CEF3A914D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {CB280978-D61C-4833-B907-044CEF3A914D}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {CB280978-D61C-4833-B907-044CEF3A914D}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {CB280978-D61C-4833-B907-044CEF3A914D}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {DCA3F32A-7E3A-44C6-8FA3-35DB695BB9ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {DCA3F32A-7E3A-44C6-8FA3-35DB695BB9ED}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {DCA3F32A-7E3A-44C6-8FA3-35DB695BB9ED}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {DCA3F32A-7E3A-44C6-8FA3-35DB695BB9ED}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {C71A773E-64C2-479D-832A-F80F61F80C83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {C71A773E-64C2-479D-832A-F80F61F80C83}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {C71A773E-64C2-479D-832A-F80F61F80C83}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {C71A773E-64C2-479D-832A-F80F61F80C83}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {DCA3F32A-7E3A-44C6-8FA3-35DB695BB9ED} = {3E09F4C8-86A0-427D-BC27-710572D61400} 44 | {C71A773E-64C2-479D-832A-F80F61F80C83} = {3E09F4C8-86A0-427D-BC27-710572D61400} 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {A7445E87-EBB8-45A9-BF16-D2C841D1B7A2} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /HttpMouse/DependencyInjection/HttpMouseApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using HttpMouse; 2 | using HttpMouse.Implementions; 3 | using Microsoft.AspNetCore.Builder; 4 | using System; 5 | using Yarp.ReverseProxy.Configuration; 6 | using Yarp.ReverseProxy.Forwarder; 7 | 8 | namespace Microsoft.Extensions.DependencyInjection 9 | { 10 | /// 11 | /// HttpMouse的中间件扩展 12 | /// 13 | public static class HttpMouseApplicationBuilderExtensions 14 | { 15 | /// 16 | /// 使用httpMouse中间件 17 | /// 18 | /// 19 | /// 20 | public static IApplicationBuilder UseHttpMouse(this IApplicationBuilder builder) 21 | { 22 | builder.EnsureImplementation(); 23 | builder.EnsureImplementation(); 24 | 25 | var httpMouseClientHandler = builder.ApplicationServices.GetRequiredService(); 26 | var reverseConnectionHandler = builder.ApplicationServices.GetRequiredService(); 27 | 28 | builder.UseWebSockets(); 29 | builder.Use(httpMouseClientHandler.HandleConnectionAsync); 30 | builder.Use(reverseConnectionHandler.HandleConnectionAsync); 31 | 32 | return builder; 33 | } 34 | 35 | /// 36 | /// 确保服务的实现类型为指定类型 37 | /// 38 | /// 39 | /// 40 | /// 41 | private static void EnsureImplementation(this IApplicationBuilder builder) where TService : notnull 42 | { 43 | if (builder.ApplicationServices.GetRequiredService() is not TImplementation) 44 | { 45 | throw new InvalidOperationException($"不允许替换{typeof(TService)}的实现类"); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /HttpMouse/DependencyInjection/HttpMouseServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using HttpMouse; 2 | using HttpMouse.Abstractions; 3 | using HttpMouse.Implementions; 4 | using Microsoft.Extensions.Configuration; 5 | using System; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | using Yarp.ReverseProxy.Configuration; 9 | using Yarp.ReverseProxy.Forwarder; 10 | using Yarp.ReverseProxy.Transforms; 11 | 12 | namespace Microsoft.Extensions.DependencyInjection 13 | { 14 | /// 15 | /// HttpMouse的服务注册扩展 16 | /// 17 | public static class HttpMouseServiceCollectionExtensions 18 | { 19 | /// 20 | /// 注册HttpMouse相关服务 21 | /// 22 | /// 23 | /// 24 | /// 25 | public static IReverseProxyBuilder AddHttpMouse(this IServiceCollection services, IConfiguration configuration) 26 | { 27 | return services.Configure(configuration).AddHttpMouse(); 28 | } 29 | 30 | /// 31 | /// 注册HttpMouse相关服务 32 | /// 33 | /// 34 | /// 35 | /// 36 | public static IReverseProxyBuilder AddHttpMouse(this IServiceCollection services, Action configureOptions) 37 | { 38 | return services.Configure(configureOptions).AddHttpMouse(); 39 | } 40 | 41 | /// 42 | /// 注册HttpMouse相关服务 43 | /// 44 | /// 45 | /// 46 | public static IReverseProxyBuilder AddHttpMouse(this IServiceCollection services) 47 | { 48 | var optionsKey = new HttpRequestOptionsKey("ClientDomain"); 49 | var builder = services 50 | .AddReverseProxy() 51 | .AddTransforms(ctx => ctx.AddRequestTransform(request => 52 | { 53 | var clientDomain = request.HttpContext.Request.Host.Host; 54 | request.ProxyRequest.Options.Set(optionsKey, clientDomain); 55 | return ValueTask.CompletedTask; 56 | })); 57 | 58 | services 59 | .AddSingleton() 60 | .AddSingleton() 61 | .AddSingleton() 62 | 63 | .AddSingleton() 64 | .AddSingleton() 65 | .AddSingleton() 66 | .AddSingleton(); 67 | 68 | return builder; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /HttpMouse/HttpMouse.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | true 5 | true 6 | MIT 7 | https://github.com/xljiulang/httpmouse 8 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /HttpMouse/HttpMouseOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Yarp.ReverseProxy.Configuration; 3 | using Yarp.ReverseProxy.Forwarder; 4 | 5 | namespace HttpMouse 6 | { 7 | /// 8 | /// HttpMouse选项 9 | /// 10 | public class HttpMouseOptions 11 | { 12 | /// 13 | /// 缺省的密钥 14 | /// 15 | public string? DefaultKey { get; set; } 16 | 17 | /// 18 | /// 缺省的路由设置 19 | /// 20 | public RouteSetting DefaultRoute { get; set; } = new(); 21 | 22 | /// 23 | /// 缺省的集群设备 24 | /// 25 | public ClusterSetting DefaultCluster { get; set; } = new(); 26 | 27 | 28 | /// 29 | /// 客户端域名的秘钥配置 30 | /// 31 | public Dictionary Keys { get; set; } = new(); 32 | 33 | /// 34 | /// 客户端域名的路由配置 35 | /// 36 | public Dictionary Routes { get; set; } = new(); 37 | 38 | /// 39 | /// 客户端域名的集群配置 40 | /// 41 | public Dictionary Clusters { get; set; } = new(); 42 | 43 | /// 44 | /// 路由设置 45 | /// 46 | public class RouteSetting 47 | { 48 | /// 49 | /// 跨域策略 50 | /// 51 | public string? CorsPolicy { get; set; } 52 | 53 | /// 54 | /// 认证策略 55 | /// 56 | public string? AuthorizationPolicy { get; set; } 57 | } 58 | 59 | /// 60 | /// 集群配置 61 | /// 62 | public class ClusterSetting 63 | { 64 | /// 65 | /// http客户端配置 66 | /// 67 | public HttpClientConfig? HttpClient { get; set; } 68 | 69 | /// 70 | /// 转发请求配置 71 | /// 72 | public ForwarderRequestConfig? HttpRequest { get; set; } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /HttpMouse/IHttpMouseClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace HttpMouse 6 | { 7 | /// 8 | /// 客户端 9 | /// 10 | public interface IHttpMouseClient 11 | { 12 | /// 13 | /// 获取绑定的域名 14 | /// 15 | string Domain { get; } 16 | 17 | /// 18 | /// 获取上游地址 19 | /// 20 | Uri Upstream { get; } 21 | 22 | /// 23 | /// 获取输入的秘钥 24 | /// 25 | string? Key { get; } 26 | 27 | /// 28 | /// 发送创建反向连接指令 29 | /// 30 | /// 31 | /// 32 | /// 33 | Task SendCreateConnectionAsync(Guid connectionId, CancellationToken cancellationToken); 34 | 35 | /// 36 | /// 由于异常而关闭 37 | /// 38 | /// 异常原因 39 | /// 40 | /// 41 | Task CloseAsync(string error, CancellationToken cancellationToken = default); 42 | 43 | /// 44 | /// 等待关闭 45 | /// 46 | /// 47 | /// 48 | Task WaitingCloseAsync(CancellationToken cancellationToken = default); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /HttpMouse/IHttpMouseClientHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Threading.Tasks; 5 | 6 | namespace HttpMouse 7 | { 8 | /// 9 | /// 客户端处理者 10 | /// 11 | interface IHttpMouseClientHandler 12 | { 13 | /// 14 | /// 客户端变化后事件 15 | /// 16 | event Action? ClientsChanged; 17 | 18 | /// 19 | /// 处理客户端连接 20 | /// 21 | /// 22 | /// 23 | /// 24 | Task HandleConnectionAsync(HttpContext context, Func next); 25 | 26 | /// 27 | /// 通过客户端绑定的域名尝试获取客户端 28 | /// 29 | /// 客户端绑定的域名 30 | /// 31 | /// 32 | bool TryGetValue(string clientDomain, [MaybeNullWhen(false)] out IHttpMouseClient value); 33 | } 34 | } -------------------------------------------------------------------------------- /HttpMouse/IHttpMouseClientVerifier.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace HttpMouse.Abstractions 4 | { 5 | /// 6 | /// 客户端认证者 7 | /// 8 | public interface IHttpMouseClientVerifier 9 | { 10 | /// 11 | /// 认证 12 | /// 13 | /// 客户端 14 | /// 15 | ValueTask VerifyAsync(IHttpMouseClient httpMouseClient); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /HttpMouse/IHttpMouseClusterProvider.cs: -------------------------------------------------------------------------------- 1 | using Yarp.ReverseProxy.Configuration; 2 | 3 | namespace HttpMouse 4 | { 5 | /// 6 | /// 集群配置提供者 7 | /// 8 | public interface IHttpMouseClusterProvider 9 | { 10 | /// 11 | /// 创建集群 12 | /// 13 | /// 14 | /// 15 | ClusterConfig Create(IHttpMouseClient httpMouseClient); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /HttpMouse/IHttpMouseRouteProvider.cs: -------------------------------------------------------------------------------- 1 | using Yarp.ReverseProxy.Configuration; 2 | 3 | namespace HttpMouse 4 | { 5 | /// 6 | /// 路由配置提供者 7 | /// 8 | public interface IHttpMouseRouteProvider 9 | { 10 | /// 11 | /// 创建路由 12 | /// 13 | /// 14 | /// 15 | RouteConfig Create(IHttpMouseClient httpMouseClient); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /HttpMouse/IReverseConnectionHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System; 3 | using System.IO; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace HttpMouse 8 | { 9 | /// 10 | /// 反向连接处理者 11 | /// 12 | interface IReverseConnectionHandler 13 | { 14 | /// 15 | /// 处理连接 16 | /// 17 | /// 18 | /// 19 | /// 20 | Task HandleConnectionAsync(HttpContext context, Func next); 21 | 22 | /// 23 | /// 创建一个反向连接 24 | /// 25 | /// 客户端域名 26 | /// 27 | /// 28 | ValueTask CreateAsync(string clientDomain, CancellationToken cancellationToken); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /HttpMouse/Implementions/DefaultHttpMouseClientVerifier.cs: -------------------------------------------------------------------------------- 1 | using HttpMouse.Abstractions; 2 | using Microsoft.Extensions.Options; 3 | using System.Threading.Tasks; 4 | 5 | namespace HttpMouse.Implementions 6 | { 7 | /// 8 | /// 默认的客户端认证者 9 | /// 10 | public class DefaultHttpMouseClientVerifier : IHttpMouseClientVerifier 11 | { 12 | private readonly IOptionsMonitor options; 13 | 14 | /// 15 | /// 客户端认证者 16 | /// 17 | /// 18 | public DefaultHttpMouseClientVerifier(IOptionsMonitor options) 19 | { 20 | this.options = options; 21 | } 22 | 23 | /// 24 | /// 认证 25 | /// 26 | /// 客户端 27 | /// 28 | public virtual ValueTask VerifyAsync(IHttpMouseClient httpMouseClient) 29 | { 30 | var opt = this.options.CurrentValue; 31 | if (opt.Keys.TryGetValue(httpMouseClient.Domain, out var serverKey) == false) 32 | { 33 | serverKey = opt.DefaultKey; 34 | } 35 | 36 | var result = serverKey == null || serverKey == httpMouseClient.Key; 37 | return ValueTask.FromResult(result); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /HttpMouse/Implementions/DefaultHttpMouseClusterProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System.Collections.Generic; 3 | using Yarp.ReverseProxy.Configuration; 4 | 5 | namespace HttpMouse.Implementions 6 | { 7 | /// 8 | /// 默认的集群配置提供者 9 | /// 10 | public class DefaultHttpMouseClusterProvider : IHttpMouseClusterProvider 11 | { 12 | private readonly IOptionsMonitor options; 13 | 14 | /// 15 | /// 集群配置提供者 16 | /// 17 | /// 18 | public DefaultHttpMouseClusterProvider(IOptionsMonitor options) 19 | { 20 | this.options = options; 21 | } 22 | 23 | /// 24 | /// 创建集群 25 | /// 26 | /// 27 | /// 28 | public virtual ClusterConfig Create(IHttpMouseClient httpMouseClient) 29 | { 30 | var domain = httpMouseClient.Domain; 31 | var address = httpMouseClient.Upstream.ToString(); 32 | 33 | var destinations = new Dictionary 34 | { 35 | [domain] = new DestinationConfig { Address = address } 36 | }; 37 | 38 | var opt = this.options.CurrentValue; 39 | if (opt.Clusters.TryGetValue(domain, out var setting) == false) 40 | { 41 | setting = opt.DefaultCluster; 42 | } 43 | 44 | return new ClusterConfig 45 | { 46 | ClusterId = domain, 47 | Destinations = destinations, 48 | HttpRequest = setting.HttpRequest, 49 | HttpClient = setting.HttpClient 50 | }; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /HttpMouse/Implementions/DefaultHttpMouseRouteProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System.Collections.Generic; 3 | using Yarp.ReverseProxy.Configuration; 4 | 5 | namespace HttpMouse.Implementions 6 | { 7 | /// 8 | /// 默认的路由配置提供者 9 | /// 10 | public class DefaultHttpMouseRouteProvider : IHttpMouseRouteProvider 11 | { 12 | private IOptionsMonitor options; 13 | 14 | /// 15 | /// 路由配置提供者 16 | /// 17 | /// 18 | public DefaultHttpMouseRouteProvider(IOptionsMonitor options) 19 | { 20 | this.options = options; 21 | } 22 | 23 | /// 24 | /// 创建路由 25 | /// 26 | /// 27 | /// 28 | public virtual RouteConfig Create(IHttpMouseClient httpMouseClient) 29 | { 30 | var domain = httpMouseClient.Domain; 31 | var opt = this.options.CurrentValue; 32 | if (opt.Routes.TryGetValue(domain, out var setting) == false) 33 | { 34 | setting = opt.DefaultRoute; 35 | } 36 | 37 | return new RouteConfig 38 | { 39 | RouteId = domain, 40 | ClusterId = domain, 41 | CorsPolicy = setting.CorsPolicy, 42 | AuthorizationPolicy = setting.AuthorizationPolicy, 43 | Match = new RouteMatch 44 | { 45 | Hosts = new List { domain } 46 | } 47 | }; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /HttpMouse/Implementions/HttpMouseClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Net.WebSockets; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace HttpMouse.Implementions 9 | { 10 | /// 11 | /// 客户端 12 | /// 13 | sealed class HttpMouseClient : IHttpMouseClient 14 | { 15 | private readonly WebSocket webSocket; 16 | 17 | /// 18 | /// 获取绑定的域名 19 | /// 20 | public string Domain { get; } 21 | 22 | /// 23 | /// 获取上游地址 24 | /// 25 | public Uri Upstream { get; } 26 | 27 | /// 28 | /// 获取输入的秘钥 29 | /// 30 | public string? Key { get; } 31 | 32 | /// 33 | /// 基于websocket的主连接 34 | /// 35 | /// 36 | /// 37 | /// 38 | /// 39 | public HttpMouseClient(string domain, Uri upstream, string? key, WebSocket webSocket) 40 | { 41 | this.Domain = domain; 42 | this.Upstream = upstream; 43 | this.Key = key; 44 | this.webSocket = webSocket; 45 | } 46 | 47 | /// 48 | /// 发送创建反向连接指令 49 | /// 50 | /// 51 | /// 52 | /// 53 | public Task SendCreateConnectionAsync(Guid connectionId, CancellationToken cancellationToken) 54 | { 55 | var buffer = Encoding.UTF8.GetBytes(connectionId.ToString()); 56 | return this.webSocket.SendAsync(buffer, WebSocketMessageType.Binary, true, cancellationToken); 57 | } 58 | 59 | /// 60 | /// 等待关闭 61 | /// 62 | /// 63 | /// 64 | public async Task WaitingCloseAsync(CancellationToken cancellationToken = default) 65 | { 66 | var buffer = ArrayPool.Shared.Rent(4); 67 | try 68 | { 69 | while (cancellationToken.IsCancellationRequested == false) 70 | { 71 | await this.webSocket.ReceiveAsync(buffer, cancellationToken); 72 | } 73 | } 74 | catch (Exception) 75 | { 76 | } 77 | finally 78 | { 79 | ArrayPool.Shared.Return(buffer); 80 | } 81 | } 82 | 83 | /// 84 | /// 由于异常而关闭 85 | /// 86 | /// 异常原因 87 | /// 88 | /// 89 | public async Task CloseAsync(string error, CancellationToken cancellationToken = default) 90 | { 91 | try 92 | { 93 | await this.webSocket.CloseAsync(WebSocketCloseStatus.ProtocolError, error, cancellationToken); 94 | } 95 | catch 96 | { 97 | } 98 | } 99 | 100 | public override string ToString() 101 | { 102 | return $"{this.Domain}->{this.Upstream}"; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /HttpMouse/Implementions/HttpMouseClientHandler.cs: -------------------------------------------------------------------------------- 1 | using HttpMouse.Abstractions; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Diagnostics.CodeAnalysis; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace HttpMouse.Implementions 11 | { 12 | /// 13 | /// 客户端处理者 14 | /// 15 | sealed class HttpMouseClientHandler : IHttpMouseClientHandler 16 | { 17 | private const string SERVER_KEY = "ServerKey"; 18 | private const string CLIENT_DOMAIN = "ClientDomain"; 19 | private const string CLIENT_UP_STREAM = "ClientUpstream"; 20 | 21 | private readonly IHttpMouseClientVerifier verifier; 22 | private readonly ILogger logger; 23 | private readonly ConcurrentDictionary clients = new(); 24 | 25 | 26 | /// 27 | /// 客户端变化后事件 28 | /// 29 | public event Action? ClientsChanged; 30 | 31 | /// 32 | /// 客户端处理者 33 | /// 34 | /// 35 | /// 36 | public HttpMouseClientHandler( 37 | IHttpMouseClientVerifier verifier, 38 | ILogger logger) 39 | { 40 | this.verifier = verifier; 41 | this.logger = logger; 42 | } 43 | 44 | /// 45 | /// 处理连接 46 | /// 47 | /// 48 | /// 49 | /// 50 | public async Task HandleConnectionAsync(HttpContext context, Func next) 51 | { 52 | if (context.WebSockets.IsWebSocketRequest == false || 53 | context.Request.Headers.TryGetValue(SERVER_KEY, out var keyValues) == false || 54 | context.Request.Headers.TryGetValue(CLIENT_DOMAIN, out var domainValues) == false || 55 | context.Request.Headers.TryGetValue(CLIENT_UP_STREAM, out var upSteramValues) == false || 56 | Uri.TryCreate(upSteramValues.ToString(), UriKind.Absolute, out var clientUpstream) == false) 57 | { 58 | await next(); 59 | return; 60 | } 61 | 62 | var clientDomain = domainValues.ToString(); 63 | var clientKey = keyValues.ToString(); 64 | using var webSocket = await context.WebSockets.AcceptWebSocketAsync(); 65 | var client = new HttpMouseClient(clientDomain, clientUpstream, clientKey, webSocket); 66 | 67 | // 验证客户端 68 | if (await this.verifier.VerifyAsync(client) == false) 69 | { 70 | await client.CloseAsync("Key不正确"); 71 | return; 72 | } 73 | 74 | // 验证连接唯一 75 | if (this.clients.TryAdd(clientDomain, client) == false) 76 | { 77 | await client.CloseAsync($"已在其它地方存在{clientDomain}的客户端实例"); 78 | return; 79 | } 80 | 81 | this.logger.LogInformation($"{client}连接过来"); 82 | this.ClientsChanged?.Invoke(this.clients.Values.ToArray()); 83 | 84 | await client.WaitingCloseAsync(); 85 | 86 | this.logger.LogInformation($"{client}断开连接"); 87 | this.clients.TryRemove(clientDomain, out _); 88 | this.ClientsChanged?.Invoke(this.clients.Values.ToArray()); 89 | } 90 | 91 | /// 92 | /// 尝试获取连接 93 | /// 94 | /// 95 | /// 96 | /// 97 | public bool TryGetValue(string clientDomain, [MaybeNullWhen(false)] out IHttpMouseClient value) 98 | { 99 | return this.clients.TryGetValue(clientDomain, out value); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /HttpMouse/Implementions/HttpMouseForwarderHttpClientFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.IO; 4 | using System.Net.Http; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Yarp.ReverseProxy.Forwarder; 8 | 9 | namespace HttpMouse.Implementions 10 | { 11 | /// 12 | /// 反向连接的HttpClient工厂 13 | /// 14 | sealed class HttpMouseForwarderHttpClientFactory : ForwarderHttpClientFactory 15 | { 16 | private readonly IReverseConnectionHandler reverseConnectionHandler; 17 | private readonly ILogger logger; 18 | private readonly HttpRequestOptionsKey clientDomainKey = new("ClientDomain"); 19 | 20 | /// 21 | /// 反向连接的HttpClient工厂 22 | /// 23 | /// 24 | /// 25 | public HttpMouseForwarderHttpClientFactory( 26 | IReverseConnectionHandler reverseConnectionHandler, 27 | ILogger logger) 28 | { 29 | this.reverseConnectionHandler = reverseConnectionHandler; 30 | this.logger = logger; 31 | } 32 | 33 | /// 34 | /// 配置handler 35 | /// 36 | /// 37 | /// 38 | protected override void ConfigureHandler(ForwarderHttpClientContext context, SocketsHttpHandler handler) 39 | { 40 | base.ConfigureHandler(context, handler); 41 | handler.ConnectCallback = this.ConnectCallback; 42 | } 43 | 44 | /// 45 | /// 连接回调 46 | /// 47 | /// 48 | /// 49 | /// 50 | private async ValueTask ConnectCallback(SocketsHttpConnectionContext context, CancellationToken cancellation) 51 | { 52 | if (context.InitialRequestMessage.Options.TryGetValue(clientDomainKey, out var clientDomain) == false) 53 | { 54 | throw new InvalidOperationException($"未设置{nameof(HttpRequestMessage)}的Options:{clientDomainKey.Key}"); 55 | } 56 | 57 | try 58 | { 59 | return await this.reverseConnectionHandler.CreateAsync(clientDomain, cancellation); 60 | } 61 | catch (Exception ex) 62 | { 63 | this.logger.LogWarning(ex.Message); 64 | throw; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /HttpMouse/Implementions/HttpMouseProxyConfigProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Primitives; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading; 6 | using Yarp.ReverseProxy.Configuration; 7 | 8 | namespace HttpMouse.Implementions 9 | { 10 | /// 11 | /// httpMouse代理配置提供者 12 | /// 13 | sealed class HttpMouseProxyConfigProvider : IProxyConfigProvider 14 | { 15 | private volatile HttpMouseProxyConfig config = new(); 16 | private readonly IHttpMouseRouteProvider routeConfigProvider; 17 | private readonly IHttpMouseClusterProvider clusterConfigProvider; 18 | 19 | /// 20 | /// 内存配置提供者 21 | /// 22 | /// 23 | /// 24 | /// 25 | public HttpMouseProxyConfigProvider( 26 | IHttpMouseClientHandler httpMouseClientHandler, 27 | IHttpMouseRouteProvider routeConfigProvider, 28 | IHttpMouseClusterProvider clusterConfigProvider) 29 | { 30 | httpMouseClientHandler.ClientsChanged += HttpMouseClientsChanged; 31 | this.routeConfigProvider = routeConfigProvider; 32 | this.clusterConfigProvider = clusterConfigProvider; 33 | } 34 | 35 | /// 36 | /// 客户端变化后 37 | /// 38 | /// 39 | private void HttpMouseClientsChanged(IHttpMouseClient[] clients) 40 | { 41 | var oldConfig = this.config; 42 | 43 | var routes = clients.Select(item => this.routeConfigProvider.Create(item)).ToArray(); 44 | var clusters = clients.Select(item => this.clusterConfigProvider.Create(item)).ToArray(); 45 | this.config = new HttpMouseProxyConfig(routes, clusters); 46 | 47 | oldConfig.SignalChange(); 48 | } 49 | 50 | /// 51 | /// 获取配置 52 | /// 53 | /// 54 | public IProxyConfig GetConfig() 55 | { 56 | return this.config; 57 | } 58 | 59 | 60 | /// 61 | /// HttpMouse代理配置 62 | /// 63 | private class HttpMouseProxyConfig : IProxyConfig 64 | { 65 | private readonly CancellationTokenSource cancellationToken = new(); 66 | 67 | /// 68 | /// 获取路由配置 69 | /// 70 | public IReadOnlyList Routes { get; } 71 | 72 | /// 73 | /// 获取集群配置 74 | /// 75 | public IReadOnlyList Clusters { get; } 76 | 77 | /// 78 | /// 获取变化通知令牌 79 | /// 80 | public IChangeToken ChangeToken { get; } 81 | 82 | /// 83 | /// 内存配置 84 | /// 85 | public HttpMouseProxyConfig() 86 | : this(Array.Empty(), Array.Empty()) 87 | { 88 | } 89 | 90 | /// 91 | /// 内存配置 92 | /// 93 | /// 94 | /// 95 | public HttpMouseProxyConfig(IReadOnlyList routes, IReadOnlyList clusters) 96 | { 97 | this.Routes = routes; 98 | this.Clusters = clusters; 99 | this.ChangeToken = new CancellationChangeToken(cancellationToken.Token); 100 | } 101 | 102 | /// 103 | /// 通知配置变化 104 | /// 105 | public void SignalChange() 106 | { 107 | this.cancellationToken.Cancel(); 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /HttpMouse/Implementions/ReverseConnection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Connections.Features; 2 | using System; 3 | using System.IO; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace HttpMouse.Implementions 8 | { 9 | /// 10 | /// 表示反向连接 11 | /// 12 | sealed class ReverseConnection : Stream 13 | { 14 | private readonly Stream readStream; 15 | private readonly Stream wirteStream; 16 | private readonly IConnectionLifetimeFeature lifetimeFeature; 17 | 18 | /// 19 | /// 反向连接 20 | /// 21 | /// 22 | /// 23 | public ReverseConnection(IConnectionLifetimeFeature lifetimeFeature, IConnectionTransportFeature transportFeature) 24 | { 25 | this.readStream = transportFeature.Transport.Input.AsStream(); 26 | this.wirteStream = transportFeature.Transport.Output.AsStream(); 27 | this.lifetimeFeature = lifetimeFeature; 28 | } 29 | 30 | public override bool CanRead => true; 31 | 32 | public override bool CanSeek => false; 33 | 34 | public override bool CanWrite => true; 35 | 36 | public override long Length => throw new NotSupportedException(); 37 | 38 | public override long Position 39 | { 40 | get => throw new NotSupportedException(); 41 | set => throw new NotSupportedException(); 42 | } 43 | 44 | public override void Flush() 45 | { 46 | this.wirteStream.Flush(); 47 | } 48 | 49 | public override Task FlushAsync(CancellationToken cancellationToken) 50 | { 51 | return this.wirteStream.FlushAsync(cancellationToken); 52 | } 53 | 54 | public override long Seek(long offset, SeekOrigin origin) 55 | { 56 | throw new NotSupportedException(); 57 | } 58 | 59 | public override void SetLength(long value) 60 | { 61 | throw new NotSupportedException(); 62 | } 63 | 64 | public override int Read(byte[] buffer, int offset, int count) 65 | { 66 | return this.readStream.Read(buffer, offset, count); 67 | } 68 | public override void Write(byte[] buffer, int offset, int count) 69 | { 70 | this.wirteStream.Write(buffer, offset, count); 71 | } 72 | public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) 73 | { 74 | return this.readStream.ReadAsync(buffer, cancellationToken); 75 | } 76 | 77 | public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 78 | { 79 | return this.readStream.ReadAsync(buffer, offset, count, cancellationToken); 80 | } 81 | 82 | public override void Write(ReadOnlySpan buffer) 83 | { 84 | this.wirteStream.Write(buffer); 85 | } 86 | 87 | public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 88 | { 89 | return this.wirteStream.WriteAsync(buffer, offset, count, cancellationToken); 90 | } 91 | 92 | public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) 93 | { 94 | await this.wirteStream.WriteAsync(buffer, cancellationToken); 95 | } 96 | 97 | protected override void Dispose(bool disposing) 98 | { 99 | this.lifetimeFeature.Abort(); 100 | } 101 | 102 | public override ValueTask DisposeAsync() 103 | { 104 | this.lifetimeFeature.Abort(); 105 | return ValueTask.CompletedTask; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /HttpMouse/Implementions/ReverseConnectionHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Connections.Features; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.IO; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace HttpMouse.Implementions 11 | { 12 | /// 13 | /// 表示反向连接处理者 14 | /// 15 | sealed class ReverseConnectionHandler : IReverseConnectionHandler 16 | { 17 | private readonly IHttpMouseClientHandler httpMouseClientHandler; 18 | private readonly ILogger logger; 19 | 20 | private readonly TimeSpan timeout = TimeSpan.FromSeconds(10d); 21 | private readonly ConcurrentDictionary> connectionAwaiterTable = new(); 22 | 23 | /// 24 | /// 反向连接提值者 25 | /// 26 | /// 27 | /// 28 | public ReverseConnectionHandler( 29 | IHttpMouseClientHandler httpMouseClientHandler, 30 | ILogger logger) 31 | { 32 | this.httpMouseClientHandler = httpMouseClientHandler; 33 | this.logger = logger; 34 | } 35 | 36 | /// 37 | /// 创建一个反向连接 38 | /// 39 | /// 客户端域名 40 | /// 41 | /// 42 | public async ValueTask CreateAsync(string clientDomain, CancellationToken cancellation) 43 | { 44 | if (this.httpMouseClientHandler.TryGetValue(clientDomain, out var httpMouseClient) == false) 45 | { 46 | throw new Exception($"无法创建反向连接:上游{clientDomain}未连接"); 47 | } 48 | 49 | var connectionId = Guid.NewGuid(); 50 | using var connectionAwaiter = AwaitableCompletionSource.Create(); 51 | connectionAwaiter.TrySetExceptionAfter(new TimeoutException($"创建反向连接{connectionId}超时"), this.timeout); 52 | this.connectionAwaiterTable.TryAdd(connectionId, connectionAwaiter); 53 | 54 | try 55 | { 56 | await httpMouseClient.SendCreateConnectionAsync(connectionId, cancellation); 57 | return await connectionAwaiter.Task; 58 | } 59 | catch (Exception ex) 60 | { 61 | this.logger.LogWarning(ex.Message); 62 | throw; 63 | } 64 | finally 65 | { 66 | this.connectionAwaiterTable.TryRemove(connectionId, out _); 67 | } 68 | } 69 | 70 | 71 | /// 72 | /// 处理连接 73 | /// 74 | /// 75 | /// 76 | /// 77 | public async Task HandleConnectionAsync(HttpContext context, Func next) 78 | { 79 | if (TryReadConnectionId(context, out var connectionId) == false || 80 | this.connectionAwaiterTable.TryRemove(connectionId, out var connectionAwaiter) == false) 81 | { 82 | await next(); 83 | return; 84 | } 85 | 86 | var lifetime = context.Features.Get(); 87 | var transport = context.Features.Get(); 88 | 89 | if (lifetime == null || transport == null) 90 | { 91 | await next(); 92 | return; 93 | } 94 | 95 | using var reverseConnection = new ReverseConnection(lifetime, transport); 96 | connectionAwaiter.TrySetResult(reverseConnection); 97 | 98 | using var closedAwaiter = AwaitableCompletionSource.Create(); 99 | lifetime.ConnectionClosed.Register(state => ((IAwaitableCompletionSource)state!).TrySetResult(null), closedAwaiter); 100 | await closedAwaiter.Task; 101 | } 102 | 103 | /// 104 | /// 读取反向连接的id 105 | /// 106 | /// 107 | /// 108 | /// 109 | private static bool TryReadConnectionId(HttpContext context, out Guid value) 110 | { 111 | const string method = "REVERSE"; 112 | if (context.Request.Method != method) 113 | { 114 | value = default; 115 | return default; 116 | } 117 | 118 | var path = context.Request.Path.Value.AsSpan(); 119 | return Guid.TryParse(path[1..], out value); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HttpMouse 2 | 基于yarp的http公网反向代理到内网的服务端与客户端库 3 | 4 | ### Nuget 5 | 6 | | 包名 | 描述 | Nuget | 7 | ---|---|--| 8 | | HttpMouse | 服务端用 | [![NuGet](https://buildstats.info/nuget/HttpMouse)](https://www.nuget.org/packages/HttpMouse) | 9 | | HttpMouse.Client | 客户端用 | [![NuGet](https://buildstats.info/nuget/HttpMouse.Client )](https://www.nuget.org/packages/HttpMouse.Client ) | 10 | 11 | ### 原理图 12 | ![image](https://raw.githubusercontent.com/xljiulang/HttpMouse/master/HttpMouse.png) 13 | 14 | ### 服务端开发 15 | #### 基础入门 16 | ``` 17 | /// 18 | /// 配置服务 19 | /// 20 | /// 21 | public void ConfigureServices(IServiceCollection services) 22 | { 23 | services.AddHttpMouse(options => 24 | { 25 | options.DefaultKey = "客户端连接秘钥"; 26 | }); 27 | } 28 | 29 | /// 30 | /// 配置中间件 31 | /// 32 | /// 33 | /// 34 | public void Configure(IApplicationBuilder app, IHostEnvironment hostEnvironment) 35 | { 36 | app.UseHttpMouse(); 37 | app.UseRouting(); 38 | 39 | // 其它中间件 40 | 41 | app.UseEndpoints(endpoints => 42 | { 43 | endpoints.MapReverseProxy(); 44 | }); 45 | } 46 | ``` 47 | 48 | #### 自定义客户端认证 49 | 50 | ``` 51 | /// 52 | /// 配置服务 53 | /// 54 | /// 55 | public void ConfigureServices(IServiceCollection services) 56 | { 57 | services.AddHttpMouse(options => 58 | { 59 | options.DefaultKey = "客户端连接秘钥"; 60 | }); 61 | 62 | services.AddSingleton(); 63 | } 64 | ``` 65 | 66 | ``` 67 | class ComstomClientVerifier : IHttpMouseClientVerifier 68 | { 69 | public ValueTask VerifyAsync(IHttpMouseClient httpMouseClient) 70 | { 71 | var key = httpMouseClient.Key; 72 | var domain = httpMouseClient.Domain; 73 | 74 | var result = false; 75 | if (domain == "b.xx.com") 76 | { 77 | result = key == "123456"; 78 | } 79 | 80 | return ValueTask.FromResult(result); 81 | } 82 | } 83 | ``` 84 | 85 | #### YARP功能 86 | services.AddHttpMouse()返回YARP的IReverseProxyBuilder对象,此对象还有其它比较重要的功能。有关YARP的完整功能介绍,可以阅读[YARP文档](https://microsoft.github.io/reverse-proxy/articles/getting-started.html) 87 | 88 | ##### AddConfigFilter 89 | AddConfigFilter()实际注册了一个IProxyConfigFilter服务,服务用于修改既有的路由与集群配置。 90 | 91 | #### AddTransforms 92 | AddTransforms()提供多个重载方法,最终注册ITransformProvider服务,服务用于变换http请求或响应内容。 93 | 94 | #### ConfigureHttpClient 95 | 用于配置SocketsHttpHandler的配置 96 | 97 | #### ~~LoadFromConfig~~ 98 | ~~LoadFromConfig()实际是注册了基于配置文件的IProxyConfigProvider,HttpMouse实现了基于内存的IProxyConfigProvider,功能冲突,不能调用此方法~~ 99 | 100 | -------------------------------------------------------------------------------- /samples/HttpMouse.ClientHost/HttpMouse.ClientHost.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | true 16 | PreserveNewest 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /samples/HttpMouse.ClientHost/HttpMouseClientHostedService.cs: -------------------------------------------------------------------------------- 1 | using HttpMouse.Client; 2 | using Microsoft.Extensions.Hosting; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace HttpMouse.ClientHost 9 | { 10 | sealed class HttpMouseClientHostedService : BackgroundService 11 | { 12 | private readonly IHttpMouseClientFactory httpMouseClientFactory; 13 | private readonly ILogger logger; 14 | 15 | public HttpMouseClientHostedService( 16 | IHttpMouseClientFactory httpMouseClientFactory, 17 | ILogger logger) 18 | { 19 | this.httpMouseClientFactory = httpMouseClientFactory; 20 | this.logger = logger; 21 | } 22 | 23 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 24 | { 25 | while (stoppingToken.IsCancellationRequested == false) 26 | { 27 | try 28 | { 29 | using var client = await this.httpMouseClientFactory.CreateAsync(stoppingToken); 30 | this.logger.LogInformation($"连接到服务器成功"); 31 | 32 | this.logger.LogInformation($"等待数据传输.."); 33 | await client.TransportAsync(stoppingToken); 34 | this.logger.LogInformation($"数据传输结束"); 35 | } 36 | catch (Exception ex) 37 | { 38 | this.logger.LogWarning(ex.Message); 39 | await Task.Delay(TimeSpan.FromSeconds(5d), stoppingToken); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /samples/HttpMouse.ClientHost/Program.cs: -------------------------------------------------------------------------------- 1 | using HttpMouse.Client; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | namespace HttpMouse.ClientHost 6 | { 7 | class Program 8 | { 9 | /// 10 | /// 程序入口 11 | /// 12 | /// 13 | public static void Main(string[] args) 14 | { 15 | CreateHostBuilder(args).Build().Run(); 16 | } 17 | 18 | /// 19 | /// 创建host 20 | /// 21 | /// 22 | /// 23 | public static IHostBuilder CreateHostBuilder(string[] args) 24 | { 25 | return Host 26 | .CreateDefaultBuilder(args) 27 | .ConfigureServices((ctx, services) => 28 | { 29 | services 30 | .AddHttpMouseClient(ctx.Configuration.GetSection("HttpMouse")) 31 | .AddHostedService(); 32 | }); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/HttpMouse.ClientHost/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "HttpMouse": { 3 | "Server": "http://localhost:2880/", 4 | "ServerKey": "httpmouse", 5 | "ClientDomain": "localhost", 6 | "ClientUpstream": "http://localhost:5000/" 7 | } 8 | } -------------------------------------------------------------------------------- /samples/HttpMouse.ServerHost/FallbackOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace HttpMouse.ServerHost 7 | { 8 | /// 9 | /// 错误回退选项 10 | /// 11 | public class FallbackOptions 12 | { 13 | /// 14 | /// 响应状态码 15 | /// 16 | public int StatusCode { get; set; } = 503; 17 | 18 | /// 19 | /// 响应内容类型 20 | /// 21 | public string ContentType { get; set; } = "application/problem+json"; 22 | 23 | /// 24 | /// 响应内容文件路径 25 | /// 26 | public string ContentFile { get; set; } = "fallback.json"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/HttpMouse.ServerHost/HttpMouse.ServerHost.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/HttpMouse.ServerHost/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Connections; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Hosting; 4 | using Serilog; 5 | 6 | namespace HttpMouse.ServerHost 7 | { 8 | class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | CreateHostBuilder(args).Build().Run(); 13 | } 14 | 15 | public static IHostBuilder CreateHostBuilder(string[] args) 16 | { 17 | return Host 18 | .CreateDefaultBuilder(args) 19 | .ConfigureWebHostDefaults(webBuilder => 20 | { 21 | webBuilder.UseStartup(); 22 | }) 23 | .UseSerilog((hosting, logger) => 24 | { 25 | const string template = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}]{NewLine}{SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}"; 26 | logger.ReadFrom.Configuration(hosting.Configuration) 27 | .Enrich.FromLogContext() 28 | .WriteTo.Console(outputTemplate: template) 29 | .WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day, outputTemplate: template); 30 | }); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/HttpMouse.ServerHost/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "HttpMouse.ServerHost": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "dotnetRunMessages": "true", 10 | "applicationUrl": "http://localhost:2880/" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /samples/HttpMouse.ServerHost/Startup.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htlp/HttpMouse/27c72d4db501b268625da8c3bdff850fb973759a/samples/HttpMouse.ServerHost/Startup.cs -------------------------------------------------------------------------------- /samples/HttpMouse.ServerHost/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Kestrel": { 3 | "EndPoints": { 4 | "Http": { 5 | "Url": "http://*:2880" 6 | } 7 | } 8 | }, 9 | "HttpMouse": { 10 | "DefaultKey": "httpmouse", 11 | "DefaultCluster": { 12 | "HttpClient": { 13 | "MaxConnectionsPerServer": 10, 14 | "DangerousAcceptAnyServerCertificate": true 15 | }, 16 | "HttpRequest": { 17 | "Timeout": "00:02:00" 18 | } 19 | } 20 | }, 21 | "Fallback": { 22 | "StatusCode": 503, 23 | "ContentType": "application/problem+json", 24 | "ContentFile": "fallback.json" 25 | }, 26 | "Serilog": { 27 | "MinimumLevel": { 28 | "Default": "Information", 29 | "Override": { 30 | "System": "Warning", 31 | "Microsoft": "Warning", 32 | "Yarp.ReverseProxy": "Warning", 33 | "Microsoft.Hosting.Lifetime": "Information" 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /samples/HttpMouse.ServerHost/fallback.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "http://www.restapitutorial.com/httpstatuscodes.html", 3 | "title": "服务不可用", 4 | "detail": "代理的上游服务当前不可用", 5 | "status": 503 6 | } --------------------------------------------------------------------------------