using System; using System.Linq; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; using Vinno.FIS.Sonopost.Assets; using Vinno.FIS.Sonopost.Features.Config; using Vinno.FIS.Sonopost.Features.Web; using Vinno.FIS.Sonopost.WebApi; using Vinno.IUS.Common.Log; namespace Vinno.FIS.Sonopost { internal class WebHost : IDisposable { private bool _isDisposed; private bool _isHttpRunning; private bool _isWsRunning; private HttpListener _httpListener; private HttpListener _wsListener; private ViewEngine _viewEngine; private WebApiEngine _webapiHost; private WebPreviewHandler _webPreviewHandler; public WebHost() { _viewEngine = new ViewEngine(); _webapiHost = new WebApiEngine(); _webPreviewHandler = new WebPreviewHandler(); } ~WebHost() { DoDispose(); } /// /// Start Http Host /// internal void Start() { StartHttp(SonopostSystemSettings.Instance.WebSetting.WebPort, SonopostSystemSettings.Instance.WebSetting.WebPortStandby); StartWebScocket(SonopostSystemSettings.Instance.WebSetting.WebSocketPort); } private void StartHttp(int mainPort, int standbyPort = -1) { _httpListener = new HttpListener(); if (CheckPortInUse(mainPort)) { Logger.WriteLineWarn($"[{nameof(WebHost)}]{nameof(StartHttp)}:Port {mainPort} is occupied."); return; } _httpListener.Prefixes.Add($"http://*:{mainPort}/"); if (standbyPort > -1) { if (CheckPortInUse(standbyPort)) { Logger.WriteLineWarn($"[{nameof(WebHost)}]{nameof(StartHttp)}:Port {standbyPort} is occupied."); } else { _httpListener.Prefixes.Add($"http://*:{standbyPort}/"); } } try { _httpListener.Start(); _isHttpRunning = true; Thread thread = new Thread(new ThreadStart(async () => { while (_isHttpRunning) { try { HttpListenerContext context = await _httpListener.GetContextAsync(); if (context.Request.IsWebSocketRequest) { context.Response.StatusCode = HttpStatusCode.NotFound.GetHashCode(); context.Response.Close(); continue; } await HandleRequest(context); } catch (Exception ex) { Logger.WriteLineError($"[{nameof(WebHost)}]{nameof(StartHttp)} handle context error:{ex}"); } Thread.Sleep(1); } })); thread.Start(); } catch (Exception ex) { Logger.WriteLineError($"[{nameof(WebHost)}]{nameof(StartHttp)} error:{ex}"); try { _httpListener?.Stop(); } catch { } } } private void StartWebScocket(int port) { _wsListener = new HttpListener(); if (CheckPortInUse(port)) { Logger.WriteLineWarn($"[{nameof(WebHost)}]{nameof(StartWebScocket)}:Port {port} is occupied."); return; } _wsListener.Prefixes.Add($"http://*:{port}/"); try { _isWsRunning = true; _wsListener.Start(); Thread thread = new Thread(new ThreadStart(async () => { while (_isWsRunning) { try { HttpListenerContext context = await _wsListener.GetContextAsync(); if (context.Request.IsWebSocketRequest) { _webPreviewHandler.HandleRequest(context); } else { context.Response.StatusCode = HttpStatusCode.NotFound.GetHashCode(); context.Response.Close(); } } catch (Exception ex) { Logger.WriteLineError($"[{nameof(WebHost)}]{nameof(StartWebScocket)} handle context error:{ex}"); } Thread.Sleep(1); } })); thread.Start(); } catch (Exception ex) { Logger.WriteLineError($"[{nameof(WebHost)}]{nameof(StartWebScocket)} error:{ex}"); try { _wsListener?.Stop(); } catch { } } } /// /// Handle Http Request /// /// /// private async Task HandleRequest(HttpListenerContext context) { if (System.Net.Http.HttpMethod.Get.Method.Equals(context.Request.HttpMethod)) { await HandleGetRequest(context); } else if (System.Net.Http.HttpMethod.Post.Method.Equals(context.Request.HttpMethod)) { await HandlePostRequest(context); } context.Response.Close(); } /// /// 仅限静态资源 /// /// /// private async Task HandleGetRequest(HttpListenerContext context) { try { string url = context.Request.RawUrl.ToLowerInvariant(); if (url.StartsWith("/download/")) { _viewEngine.HandleDownload(context); } else { byte[] buffer = _viewEngine.FindSource(url); if (buffer != null) { await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length); context.Response.StatusCode = HttpStatusCode.OK.GetHashCode(); } else { context.Response.StatusCode = HttpStatusCode.NotFound.GetHashCode(); } } } catch (Exception ex) { Logger.WriteLineError($"[{nameof(WebHost)}]{nameof(HandleGetRequest)} error:{ex}"); context.Response.StatusCode = HttpStatusCode.InternalServerError.GetHashCode(); } finally { context.Response.OutputStream.Flush(); context.Response.OutputStream.Close(); } } /// /// 仅限WebApi调用 /// /// /// private async Task HandlePostRequest(HttpListenerContext context) { try { string url = context.Request.RawUrl.Split('?')[0].ToLowerInvariant(); Logger.WriteLineInfo($"WebHost HandlePostRequest Url:{url}"); if (url.StartsWith(WebApiEngine.ROUTE_PREFIX)) { url = url.Substring(WebApiEngine.ROUTE_PREFIX.Length, url.Length - WebApiEngine.ROUTE_PREFIX.Length); byte[] bodyBytes = new byte[context.Request.ContentLength64]; await context.Request.InputStream.ReadAsync(bodyBytes, 0, bodyBytes.Length); string requestJson = Encoding.UTF8.GetString(bodyBytes); string resultJson = _webapiHost.Handle(url, requestJson, context); if (string.IsNullOrWhiteSpace(resultJson)) { context.Response.StatusCode = HttpStatusCode.NotFound.GetHashCode(); } else { context.Response.ContentType = "application/json"; context.Response.ContentEncoding = Encoding.UTF8; byte[] buffer = Encoding.UTF8.GetBytes(resultJson); await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length); context.Response.StatusCode = HttpStatusCode.OK.GetHashCode(); } } else { context.Response.StatusCode = HttpStatusCode.NotFound.GetHashCode(); } } catch (Exception ex) { Logger.WriteLineError($"[{nameof(WebHost)}]{nameof(HandlePostRequest)} error:{ex}"); context.Response.StatusCode = HttpStatusCode.InternalServerError.GetHashCode(); } finally { context.Response.OutputStream.Flush(); context.Response.OutputStream.Close(); } } internal void Stop() { Logger.WriteLineError($"[{nameof(WebHost)}]Stop Invoke"); _isWsRunning = false; _isHttpRunning = false; _wsListener = null; _httpListener = null; } private void DoDispose() { if (!_isDisposed) { Stop(); _isDisposed = true; } } public void Dispose() { DoDispose(); GC.SuppressFinalize(this); } /// /// 检测端口是否被占用 /// /// 端口号 /// private bool CheckPortInUse(int port) { var properties = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties(); IPEndPoint[] points = properties.GetActiveTcpListeners(); return points?.Any(x => x.Port == port) ?? false; } } }