diff --git a/FastTunnel.Client/FastTunnel.Client.csproj b/FastTunnel.Client/FastTunnel.Client.csproj
index a9d0752..39204f2 100644
--- a/FastTunnel.Client/FastTunnel.Client.csproj
+++ b/FastTunnel.Client/FastTunnel.Client.csproj
@@ -5,10 +5,10 @@
-
-
-
-
+
+
+
+
diff --git a/FastTunnel.Core/Client/FastTunnelServer.cs b/FastTunnel.Core/Client/FastTunnelServer.cs
index 2ac3b29..1fe6c11 100644
--- a/FastTunnel.Core/Client/FastTunnelServer.cs
+++ b/FastTunnel.Core/Client/FastTunnelServer.cs
@@ -64,7 +64,7 @@ namespace FastTunnel.Core.Client
internal void ClientLogout(TunnelClient client)
{
Interlocked.Decrement(ref ConnectedClientCount);
- logger.LogInformation($"客户端关闭 {client.RemoteIpAddress} 当前在线数:{ConnectedClientCount},统计CLIENT连接数:{FastTunnelClientHandler.ConnectionCount}");
+ logger.LogInformation($"客户端关闭 {client.RemoteIpAddress} 当前在线数:{ConnectedClientCount},统计CLIENT连接数:{FastTunnelClientHandler.ConnectionCount - 1}");
Clients.Remove(client);
client.Logout();
}
diff --git a/FastTunnel.Server/Controllers/AccountController.cs b/FastTunnel.Server/Controllers/AccountController.cs
new file mode 100644
index 0000000..a86cdad
--- /dev/null
+++ b/FastTunnel.Server/Controllers/AccountController.cs
@@ -0,0 +1,90 @@
+// Licensed under the Apache License, Version 2.0 (the "License").
+// You may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE
+// Copyright (c) 2019 Gui.H
+
+using FastTunnel.Api.Models;
+using FastTunnel.Core.Config;
+using FastTunnel.Server.Models;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.IdentityModel.Tokens.Jwt;
+using System.Linq;
+using System.Security.Claims;
+
+namespace FastTunnel.Api.Controllers
+{
+ public class AccountController : BaseController
+ {
+ readonly IOptionsMonitor serverOptionsMonitor;
+
+ public AccountController(IOptionsMonitor optionsMonitor)
+ {
+ serverOptionsMonitor = optionsMonitor;
+ }
+
+ ///
+ /// 获取Token
+ ///
+ ///
+ ///
+ [AllowAnonymous]
+ [HttpPost]
+ public ApiResponse GetToken(GetTokenRequest request)
+ {
+ if ((serverOptionsMonitor.CurrentValue?.Api?.Accounts?.Length ?? 0) == 0)
+ {
+ ApiResponse.errorCode = ErrorCodeEnum.NoAccount;
+ ApiResponse.errorMessage = "账号或密码错误";
+ return ApiResponse;
+ }
+
+ var account = serverOptionsMonitor.CurrentValue.Api.Accounts.FirstOrDefault((x) =>
+ {
+ return x.Name.Equals(request.name) && x.Password.Equals(request.password);
+ });
+
+ if (account == null)
+ {
+ ApiResponse.errorCode = ErrorCodeEnum.NoAccount;
+ ApiResponse.errorMessage = "账号或密码错误";
+ return ApiResponse;
+ }
+
+ // 生成Token
+ var claims = new[] {
+ new Claim("Name", account.Name)
+ };
+
+ ApiResponse.data = GenerateToken(
+ claims,
+ serverOptionsMonitor.CurrentValue.Api.JWT.IssuerSigningKey,
+ serverOptionsMonitor.CurrentValue.Api.JWT.Expires,
+ serverOptionsMonitor.CurrentValue.Api.JWT.ValidIssuer,
+ serverOptionsMonitor.CurrentValue.Api.JWT.ValidAudience);
+
+ return ApiResponse;
+ }
+
+ public static string GenerateToken(
+ IEnumerable claims, string Secret, int expiresMinutes = 60, string issuer = null, string audience = null)
+ {
+ var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(Secret));
+ var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
+
+ var securityToken = new JwtSecurityToken(
+ issuer: issuer,
+ audience: audience,
+ claims: claims,
+ expires: DateTime.Now.AddMinutes(expiresMinutes),
+ signingCredentials: creds);
+
+ return new JwtSecurityTokenHandler().WriteToken(securityToken);
+ }
+ }
+}
diff --git a/FastTunnel.Server/Controllers/BaseController.cs b/FastTunnel.Server/Controllers/BaseController.cs
new file mode 100644
index 0000000..083af95
--- /dev/null
+++ b/FastTunnel.Server/Controllers/BaseController.cs
@@ -0,0 +1,22 @@
+// Licensed under the Apache License, Version 2.0 (the "License").
+// You may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE
+// Copyright (c) 2019 Gui.H
+
+using FastTunnel.Api.Filters;
+using FastTunnel.Server.Models;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace FastTunnel.Api.Controllers
+{
+ [Authorize]
+ [Route("api/[controller]/[action]")]
+ [ApiController]
+ [ServiceFilter(typeof(CustomExceptionFilterAttribute))]
+ public class BaseController : ControllerBase
+ {
+ protected ApiResponse ApiResponse = new ApiResponse();
+ }
+}
diff --git a/FastTunnel.Server/Controllers/SystemController.cs b/FastTunnel.Server/Controllers/SystemController.cs
new file mode 100644
index 0000000..0f6c9f5
--- /dev/null
+++ b/FastTunnel.Server/Controllers/SystemController.cs
@@ -0,0 +1,100 @@
+// Licensed under the Apache License, Version 2.0 (the "License").
+// You may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE
+// Copyright (c) 2019 Gui.H
+
+using FastTunnel.Core.Client;
+using FastTunnel.Server.Models;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace FastTunnel.Api.Controllers;
+
+public class SystemController : BaseController
+{
+ readonly FastTunnelServer fastTunnelServer;
+
+ public SystemController(FastTunnelServer fastTunnelServer)
+ {
+ this.fastTunnelServer = fastTunnelServer;
+ }
+
+ ///
+ /// 获取当前等待响应的请求
+ ///
+ ///
+ [HttpGet]
+ public ApiResponse GetResponseTempList()
+ {
+ ApiResponse.data = new
+ {
+ Count = fastTunnelServer.ResponseTasks.Count,
+ Rows = fastTunnelServer.ResponseTasks.Select(x => new
+ {
+ x.Key
+ })
+ };
+
+ return ApiResponse;
+ }
+
+ ///
+ /// 获取当前映射的所有站点信息
+ ///
+ ///
+ [HttpGet]
+ public ApiResponse GetAllWebList()
+ {
+ ApiResponse.data = new
+ {
+ Count = fastTunnelServer.WebList.Count,
+ Rows = fastTunnelServer.WebList.Select(x => new { x.Key, x.Value.WebConfig.LocalIp, x.Value.WebConfig.LocalPort })
+ };
+
+ return ApiResponse;
+ }
+
+ ///
+ /// 获取服务端配置信息
+ ///
+ ///
+ [HttpGet]
+ public ApiResponse GetServerOption()
+ {
+ ApiResponse.data = fastTunnelServer.ServerOption;
+ return ApiResponse;
+ }
+
+ ///
+ /// 获取所有端口转发映射列表
+ ///
+ ///
+ [HttpGet]
+ public ApiResponse GetAllForwardList()
+ {
+ ApiResponse.data = new
+ {
+ Count = fastTunnelServer.ForwardList.Count,
+ Rows = fastTunnelServer.ForwardList.Select(x => new { x.Key, x.Value.SSHConfig.LocalIp, x.Value.SSHConfig.LocalPort, x.Value.SSHConfig.RemotePort })
+
+ };
+
+ return ApiResponse;
+ }
+
+ ///
+ /// 获取当前客户端在线数量
+ ///
+ ///
+ [HttpGet]
+ public ApiResponse GetOnlineClientCount()
+ {
+ ApiResponse.data = fastTunnelServer.ConnectedClientCount;
+ return ApiResponse;
+ }
+}
diff --git a/FastTunnel.Server/FastTunnel.Server.csproj b/FastTunnel.Server/FastTunnel.Server.csproj
index b5935ff..024800a 100644
--- a/FastTunnel.Server/FastTunnel.Server.csproj
+++ b/FastTunnel.Server/FastTunnel.Server.csproj
@@ -12,19 +12,18 @@
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
diff --git a/FastTunnel.Server/Filters/CustomExceptionFilterAttribute.cs b/FastTunnel.Server/Filters/CustomExceptionFilterAttribute.cs
new file mode 100644
index 0000000..14262d8
--- /dev/null
+++ b/FastTunnel.Server/Filters/CustomExceptionFilterAttribute.cs
@@ -0,0 +1,39 @@
+// Licensed under the Apache License, Version 2.0 (the "License").
+// You may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE
+// Copyright (c) 2019 Gui.H
+
+using FastTunnel.Server.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.Extensions.Logging;
+
+namespace FastTunnel.Api.Filters
+{
+ public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
+ {
+ readonly ILogger _logger;
+
+ public CustomExceptionFilterAttribute(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public override void OnException(ExceptionContext context)
+ {
+ _logger.LogError(context.Exception, "【全局异常捕获】");
+ var res = new ApiResponse()
+ {
+ errorCode = ErrorCodeEnum.Exception,
+ data = null,
+ errorMessage = context.Exception.Message,
+ };
+
+ var result = new JsonResult(res) { StatusCode = 200 };
+
+ context.Result = result;
+ context.ExceptionHandled = true;
+ }
+ }
+}
diff --git a/FastTunnel.Server/Models/ApiResponse.cs b/FastTunnel.Server/Models/ApiResponse.cs
new file mode 100644
index 0000000..8062309
--- /dev/null
+++ b/FastTunnel.Server/Models/ApiResponse.cs
@@ -0,0 +1,32 @@
+// Licensed under the Apache License, Version 2.0 (the "License").
+// You may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE
+// Copyright (c) 2019 Gui.H
+
+namespace FastTunnel.Server.Models
+{
+ public class ApiResponse
+ {
+ ///
+ /// 错误码
+ /// 0 成功,其他为失败
+ ///
+ public ErrorCodeEnum errorCode { get; set; }
+
+ public string errorMessage { get; set; }
+
+ public object data { get; set; }
+ }
+
+ public enum ErrorCodeEnum
+ {
+ NONE = 0,
+
+ AuthError = 1,
+
+ Exception = 2,
+
+ NoAccount = 3,
+ }
+}
diff --git a/FastTunnel.Server/Models/GetTokenRequest.cs b/FastTunnel.Server/Models/GetTokenRequest.cs
new file mode 100644
index 0000000..8617030
--- /dev/null
+++ b/FastTunnel.Server/Models/GetTokenRequest.cs
@@ -0,0 +1,19 @@
+// Licensed under the Apache License, Version 2.0 (the "License").
+// You may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE
+// Copyright (c) 2019 Gui.H
+
+using System.ComponentModel.DataAnnotations;
+
+namespace FastTunnel.Api.Models
+{
+ public class GetTokenRequest
+ {
+ [Required]
+ public string name { get; set; }
+
+ [Required]
+ public string password { get; set; }
+ }
+}
diff --git a/FastTunnel.Server/Program.cs b/FastTunnel.Server/Program.cs
index 1f9574e..e380476 100644
--- a/FastTunnel.Server/Program.cs
+++ b/FastTunnel.Server/Program.cs
@@ -6,8 +6,11 @@
using System.Net.Http;
using System.Net.Sockets;
+using FastTunnel.Core.Extensions;
+using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
@@ -27,8 +30,58 @@ public class Program
try
{
- CreateHostBuilder(args).Build().Run();
+ var builder = WebApplication.CreateBuilder(new WebApplicationOptions
+ {
+ Args = args
+ });
+ // Add services to the container.
+
+ builder.Services.AddControllers();
+ // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+ builder.Services.AddEndpointsApiExplorer();
+ builder.Services.AddSwaggerGen();
+
+ builder.Host.UseSerilog((context, services, configuration) => configuration
+ .ReadFrom.Configuration(context.Configuration)
+ .ReadFrom.Services(services)
+ .Enrich.FromLogContext()
+ .WriteTo.Console());
+
+ (builder.Configuration as IConfigurationBuilder).AddJsonFile("config/appsettings.json", optional: false, reloadOnChange: true);
+ (builder.Configuration as IConfigurationBuilder).AddJsonFile($"config/appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true); ;
+
+ // -------------------FastTunnel STEP1 OF 3------------------
+ builder.Services.AddFastTunnelServer(builder.Configuration.GetSection("FastTunnel"));
+ // -------------------FastTunnel STEP1 END-------------------
+
+ builder.Host.UseWindowsService();
+
+ var app = builder.Build();
+
+ // Configure the HTTP request pipeline.
+ if (app.Environment.IsDevelopment())
+ {
+ app.UseSwagger();
+ app.UseSwaggerUI();
+ }
+
+ app.UseHttpsRedirection();
+
+ app.UseStaticFiles();
+ app.UseAuthentication();
+
+ app.UseAuthorization();
+
+ app.MapControllers();
+
+ // -------------------FastTunnel STEP2 OF 3------------------
+ app.UseFastTunnelServer();
+ // -------------------FastTunnel STEP2 END-------------------
+
+ app.MapFastTunnelServer();
+
+ app.Run();
}
catch (System.Exception ex)
{
@@ -36,31 +89,4 @@ public class Program
throw;
}
}
-
- public static IHostBuilder CreateHostBuilder(string[] args)
- {
- return Host.CreateDefaultBuilder(args)
- .UseSerilog((context, services, configuration) => configuration
- .ReadFrom.Configuration(context.Configuration)
- .ReadFrom.Services(services)
- .Enrich.FromLogContext()
- .WriteTo.Console())
- .UseWindowsService()
- .ConfigureWebHost(webHostBuilder =>
- {
- // Use FastTunnelHostingStartup
- webHostBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "FastTunnel.Api");
-
- webHostBuilder.ConfigureAppConfiguration((hostingContext, config) =>
- {
- var env = hostingContext.HostingEnvironment;
- config.AddJsonFile("config/appsettings.json", optional: false, reloadOnChange: true)
- .AddJsonFile($"config/appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
- });
- })
- .ConfigureWebHostDefaults(webBuilder =>
- {
- webBuilder.UseStartup();
- });
- }
}
diff --git a/FastTunnel.Server/Startup.cs b/FastTunnel.Server/Startup.cs
deleted file mode 100644
index 0a8d789..0000000
--- a/FastTunnel.Server/Startup.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License").
-// You may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE
-// Copyright (c) 2019 Gui.H
-
-using FastTunnel.Core.Extensions;
-using Microsoft.AspNetCore.Authentication.JwtBearer;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Microsoft.IdentityModel.Tokens;
-using System;
-using FastTunnel.Core.Config;
-using System.Text;
-using FastTunnel.Api.Filters;
-
-#if DEBUG
-using Microsoft.OpenApi.Models;
-#endif
-
-namespace FastTunnel.Server;
-
-public class Startup
-{
- public Startup(IConfiguration configuration)
- {
- Configuration = configuration;
- }
-
- public IConfiguration Configuration { get; }
-
- // This method gets called by the runtime. Use this method to add services to the container.
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddAuthorization();
-
- services.AddControllers();
-
-#if DEBUG
- services.AddSwaggerGen(c =>
- {
- c.SwaggerDoc("v2", new OpenApiInfo { Title = "FastTunel.Api", Version = "v2" });
- });
-#endif
- // -------------------FastTunnel STEP1 OF 3------------------
- services.AddFastTunnelServer(Configuration.GetSection("FastTunnel"));
- // -------------------FastTunnel STEP1 END-------------------
- }
-
- // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
- {
- if (env.IsDevelopment())
- {
- app.UseDeveloperExceptionPage();
-#if DEBUG
- app.UseSwagger();
- app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v2/swagger.json", "FastTunel.WebApi v2"));
-#endif
- }
-
- app.UseRouting();
-
- // -------------------FastTunnel STEP2 OF 3------------------
- app.UseFastTunnelServer();
- // -------------------FastTunnel STEP2 END-------------------
-
- app.UseStaticFiles();
- app.UseAuthentication();
- app.UseAuthorization();
-
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapControllers();
- // -------------------FastTunnel STEP3 OF 3------------------
- endpoints.MapFastTunnelServer();
- // -------------------FastTunnel STEP3 END-------------------
- });
- }
-}
diff --git a/FastTunnel.sln b/FastTunnel.sln
index 75e9a92..77d2918 100644
--- a/FastTunnel.sln
+++ b/FastTunnel.sln
@@ -11,15 +11,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastTunnel.Server", "FastTu
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastTunnel.Api", "FastTunnel.Api\FastTunnel.Api.csproj", "{7D560A9A-E480-40F4-AAF7-398447438255}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{54494A47-33E6-488B-B7D4-DBE8FD34916A}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.github\workflows\dotnetcore.yml = .github\workflows\dotnetcore.yml
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastTunnel.Core.Client", "FastTunnel.Core.Client\FastTunnel.Core.Client.csproj", "{67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastTunnel.Core.Client", "FastTunnel.Core.Client\FastTunnel.Core.Client.csproj", "{67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -39,10 +37,6 @@ Global
{DEF2E322-9075-4C3F-9967-7EAF0EE28CEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEF2E322-9075-4C3F-9967-7EAF0EE28CEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEF2E322-9075-4C3F-9967-7EAF0EE28CEB}.Release|Any CPU.Build.0 = Release|Any CPU
- {7D560A9A-E480-40F4-AAF7-398447438255}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7D560A9A-E480-40F4-AAF7-398447438255}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7D560A9A-E480-40F4-AAF7-398447438255}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7D560A9A-E480-40F4-AAF7-398447438255}.Release|Any CPU.Build.0 = Release|Any CPU
{67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -53,7 +47,6 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C8ADFEB1-59DB-4CE3-8D04-5B547107BCCB} = {0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C}
- {7D560A9A-E480-40F4-AAF7-398447438255} = {0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C}
{67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B} = {0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution