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