You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

202 lines
6.8 KiB

  1. #if NETCORE
  2. using System;
  3. using System.Collections.Generic;
  4. using Microsoft.AspNetCore.Http;
  5. using Microsoft.AspNetCore.Hosting;
  6. using Microsoft.Extensions.Hosting;
  7. using Microsoft.Extensions.Configuration;
  8. using Microsoft.Extensions.DependencyInjection;
  9. using Microsoft.AspNetCore.Builder;
  10. using System.Threading.Tasks;
  11. using System.Net;
  12. using System.Net.WebSockets;
  13. using System.Security.Cryptography.X509Certificates;
  14. namespace Apewer.Web
  15. {
  16. /// <summary>ASP.NET Core 服务器。</summary>
  17. public sealed class AspNetCore
  18. {
  19. #region servers
  20. static AspNetCore[] _servers = new AspNetCore[65536];
  21. internal static AspNetCore Get(int port) => _servers[port];
  22. static object _operation = new object();
  23. static string Add(AspNetCore server)
  24. {
  25. if (server == null) return null;
  26. var port = server.Port;
  27. lock (_operation)
  28. {
  29. if (_servers[port] != null) return "端口已配置。";
  30. _servers[port] = server;
  31. }
  32. return null;
  33. }
  34. static void Stop(int port)
  35. {
  36. var server = null as AspNetCore;
  37. lock (_operation)
  38. {
  39. server = _servers[port];
  40. }
  41. if (server != null)
  42. {
  43. var host = server._host;
  44. Stop(host);
  45. }
  46. }
  47. static void Stop(IDisposable disposable) => RuntimeUtility.Dispose(disposable);
  48. /// <summary>获取所有正在解析的端口。</summary>
  49. public static int[] Ports
  50. {
  51. get
  52. {
  53. var count = 0;
  54. var arr1 = new int[65536];
  55. for (var i = 0; i < _servers.Length; i++)
  56. {
  57. var server = _servers[i];
  58. if (server == null) continue;
  59. arr1[count] = i;
  60. count += 1;
  61. }
  62. var arr2 = new int[count];
  63. if (count > 0) Array.Copy(arr1, 0, arr2, 0, count);
  64. return arr2;
  65. }
  66. }
  67. #endregion
  68. #region base
  69. private IHostBuilder _builder = null;
  70. private IHost _host = null;
  71. private int _port = 0;
  72. private bool _locked = false;
  73. /// <summary>监听的端口。默认值:0,自动选择 80 端口或 443 端口。</summary>
  74. public int Port
  75. {
  76. get { return _port > 0 ? _port : (Certificate == null ? 80 : 443); }
  77. set { _port = value; }
  78. }
  79. /// <summary>获取或设置将要使用的证书。</summary>
  80. public X509Certificate2 Certificate { get; set; }
  81. /// <summary>获取或设置请求的最大 Body 大小,单位为字节。可接受的最小值为 1048576 字节(1 MB)。默认值:1073741824。</summary>
  82. public long MaxBody { get; set; } = 1073741824L;
  83. /// <summary>获取或设置 Context 处理程序。</summary>
  84. public Action<HttpContext> Context { get; set; }
  85. /// <summary>获取或设置 WebSocket 处理程序。</summary>
  86. public Action<HttpContext, System.Net.WebSockets.WebSocket> WebSocket { get; set; }
  87. private string Run<TStartup>(Action<IWebHostBuilder> configure, bool async = false) where TStartup : class
  88. {
  89. if (Context == null) return "未指定 Context 处理程序。";
  90. var port = Port;
  91. if (port < ushort.MinValue || port > ushort.MaxValue) return "端口无法使用。";
  92. if (NetworkUtility.ListActiveTcpPort().Contains(port)) return "端口无法使用。";
  93. try
  94. {
  95. _builder = Host.CreateDefaultBuilder().ConfigureWebHostDefaults((builder) =>
  96. {
  97. configure?.Invoke(builder);
  98. builder.UseStartup<TStartup>();
  99. });
  100. _host = _builder.Build();
  101. // 添加到 Servers。
  102. var add = Add(this);
  103. if (!string.IsNullOrEmpty(add))
  104. {
  105. return add;
  106. Stop(_host);
  107. }
  108. // 异步运行。
  109. if (async)
  110. {
  111. _host.RunAsync();
  112. return null;
  113. }
  114. // 同步运行。
  115. _host.Run();
  116. }
  117. catch (Exception ex)
  118. {
  119. return ex.Message;
  120. }
  121. if (!async) Stop(port);
  122. return null;
  123. }
  124. #endregion
  125. /// <summary>启动 Kestrel 服务器。</summary>
  126. public string RunKestrel(bool async = false) => Run<KestrelStartup>((builder) => builder.ConfigureKestrel((options) =>
  127. {
  128. // 配置端口和证书。
  129. options.Listen(IPAddress.Any, Port, (listen) =>
  130. {
  131. if (Certificate != null) listen.UseHttps(Certificate);
  132. });
  133. // 同步 IO。
  134. options.AllowSynchronousIO = ApiOptions.AllowSynchronousIO;
  135. // 限制请求大小,最小为 1MB,默认为 1GB。
  136. options.Limits.MaxRequestBodySize = (MaxBody < 1048576L) ? 1073741824L : MaxBody;
  137. }), async);
  138. /// <summary>启动基于 HTTP.sys 的服务器。</summary>
  139. /// <remarks>
  140. /// <para>HTTP.sys 是仅在 Windows 上运行的适用于 ASP.NET Core 的 Web 服务器。<br />HTTP.sys 是 Kestrel 服务器的替代选择,提供了一些 Kestrel 不提供的功能。</para>
  141. /// <para>HTTP.sys 支持以下功能:<br />Windows 身份验证;<br />端口共享;<br />具有 SNI 的 HTTPS;<br />基于 TLS 的 HTTP/2(Windows 10 或更高版本);<br />直接文件传输;<br />响应缓存;<br />WebSocket(Windows 8 或更高版本)。</para>
  142. /// </remarks>
  143. public string RunHttpSys(string ip, bool async = false)
  144. {
  145. // 检查 IP 地址格式。
  146. if (!NetworkUtility.IsIP(ip)) return "IP 地址格式无效。";
  147. return Run<HttpSysStartup>((builder) => builder.UseHttpSys((options) =>
  148. {
  149. // 配置端口和证书。
  150. var protocol = Certificate == null ? "http" : "https";
  151. var url = $"{protocol}://{ip}:{Port}";
  152. options.UrlPrefixes.Add(url);
  153. // 同步 IO。
  154. options.AllowSynchronousIO = ApiOptions.AllowSynchronousIO;
  155. // 限制请求大小,最小为 1MB,默认为 1GB。
  156. options.MaxRequestBodySize = (MaxBody < 1048576L) ? 1073741824L : MaxBody;
  157. // 认证。
  158. options.Authentication.Schemes = Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes.None;
  159. options.Authentication.AllowAnonymous = true;
  160. }), async);
  161. }
  162. }
  163. }
  164. #endif