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.
232 lines
7.1 KiB
232 lines
7.1 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Security.Authentication;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace Apewer.Web
|
|
{
|
|
|
|
/// <summary></summary>
|
|
public sealed class MiniServer
|
|
{
|
|
|
|
/// <summary></summary>
|
|
public MiniServer()
|
|
{
|
|
SynchronousIO = true;
|
|
MaxRequest = 1024 * 1024 * 1024;
|
|
}
|
|
|
|
#region configuration
|
|
|
|
/// <summary>上下文处理程序。</summary>
|
|
/// <remarks>默认值:未指定</remarks>
|
|
public Action<MiniContext> Handler { get; set; }
|
|
|
|
/// <summary>捕获处理请求的异常。</summary>
|
|
/// <remarks>默认值:未指定</remarks>
|
|
public Action<Exception> CatchException { get; set; }
|
|
|
|
/// <summary>获取已指定的本地终结点。</summary>
|
|
public IPEndPoint IPEndPoint { get; private set; }
|
|
|
|
/// <summary>获取已监听的本地端口。</summary>
|
|
public int Port { get => GetPort(); }
|
|
|
|
/// <summary>入站超时毫秒数。指定 0 或 -1 时为无限大。</summary>
|
|
/// <remarks>默认值:0</remarks>
|
|
public int InboundTimeout { get; set; }
|
|
|
|
/// <summary>出站超时毫秒数。指定 0 或 -1 时为无限大。</summary>
|
|
/// <remarks>默认值:0</remarks>
|
|
public int OutboundTimeout { get; set; }
|
|
|
|
/// <summary>SSL 证书。</summary>
|
|
internal X509Certificate SslCertificate { get; set; }
|
|
|
|
/// <summary>SSL 域名。</summary>
|
|
internal string SslDomain { get; set; }
|
|
|
|
/// <summary>SSL 协议。</summary>
|
|
internal SslProtocols SslProtocols { get; set; }
|
|
|
|
/// <summary>同步 IO 操作。</summary>
|
|
/// <remarks>默认值:True</remarks>
|
|
internal bool SynchronousIO { get; set; }
|
|
|
|
/// <summary>对响应体分块。</summary>
|
|
/// <remarks>默认值:False</remarks>
|
|
public bool Chunked { get; set; }
|
|
|
|
/// <summary>限制请求大小。</summary>
|
|
/// <remarks>默认值:1 GB。</remarks>
|
|
public int MaxRequest { get; set; }
|
|
|
|
/// <summary>允许压缩。</summary>
|
|
public bool Compression { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region launcher
|
|
|
|
Thread _thread = null;
|
|
|
|
/// <summary>启动服务器。</summary>
|
|
/// <exception cref="ArgumentNullException"></exception>
|
|
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
/// <exception cref="SocketException"></exception>
|
|
public void Run(int port = 0, bool await = true)
|
|
{
|
|
if (port < 0 || port > 65535) throw new ArgumentOutOfRangeException(nameof(port));
|
|
Run(new IPEndPoint(IPAddress.Any, port), await);
|
|
}
|
|
|
|
/// <summary>启动服务器。</summary>
|
|
/// <exception cref="ArgumentNullException"></exception>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
/// <exception cref="SocketException"></exception>
|
|
public void Run(IPEndPoint ipEndpoint, bool await = true)
|
|
{
|
|
if (_socket != null) throw new InvalidOperationException("存在已启动的 Socket。");
|
|
if (ipEndpoint == null) throw new ArgumentNullException(nameof(ipEndpoint));
|
|
IPEndPoint = ipEndpoint;
|
|
|
|
var ipep = ipEndpoint;
|
|
_socket = new Socket(ipep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
|
_socket.Bind(ipep);
|
|
_socket.Listen(500);
|
|
var inTimeout = InboundTimeout;
|
|
var outTimeout = OutboundTimeout;
|
|
if (inTimeout > 0) _socket.ReceiveTimeout = inTimeout;
|
|
if (outTimeout > 0) _socket.SendTimeout = outTimeout;
|
|
|
|
_thread = new Thread(Listen);
|
|
_thread.IsBackground = true;
|
|
_thread.Start();
|
|
|
|
if (await) while (_socket != null) Thread.Sleep(300);
|
|
}
|
|
|
|
/// <summary>关闭服务器。</summary>
|
|
public void Shutdown()
|
|
{
|
|
var socket = _socket;
|
|
if (socket != null)
|
|
{
|
|
try { socket.Shutdown(SocketShutdown.Both); } catch { }
|
|
try { socket.Close(1); } catch { }
|
|
_socket = null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region listener
|
|
|
|
Socket _socket = null;
|
|
|
|
int GetPort()
|
|
{
|
|
var socket = _socket;
|
|
if (socket != null) return 0;
|
|
try { if (socket.LocalEndPoint is IPEndPoint ipep) return ipep.Port; } catch { }
|
|
return 0;
|
|
}
|
|
|
|
void Listen()
|
|
{
|
|
if (SynchronousIO)
|
|
{
|
|
while (_socket != null)
|
|
{
|
|
var socket = null as Socket;
|
|
try
|
|
{
|
|
socket = _socket.Accept();
|
|
}
|
|
catch
|
|
{
|
|
if (socket != null)
|
|
{
|
|
try { socket.Close(); } catch { }
|
|
try { socket.Disconnect(false); } catch { }
|
|
#if !NET20
|
|
try { socket.Dispose(); } catch { }
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
if (socket != null) ThreadPool.QueueUserWorkItem(Process, socket);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var e = new SocketAsyncEventArgs();
|
|
e.UserToken = this;
|
|
e.Completed += (sender, arg) => Process(arg);
|
|
|
|
Socket dummy = null;
|
|
Accept(e, ref dummy);
|
|
}
|
|
}
|
|
|
|
void Accept(SocketAsyncEventArgs e, ref Socket socket)
|
|
{
|
|
e.AcceptSocket = null;
|
|
var async = false;
|
|
try
|
|
{
|
|
async = _socket.AcceptAsync(e);
|
|
}
|
|
catch
|
|
{
|
|
if (socket != null)
|
|
{
|
|
try { socket.Close(); } catch { }
|
|
socket = null;
|
|
}
|
|
return;
|
|
}
|
|
if (!async) Process(e);
|
|
}
|
|
|
|
void Process(SocketAsyncEventArgs e)
|
|
{
|
|
var socket = null as Socket;
|
|
if (e.SocketError == SocketError.Success) socket = e.AcceptSocket;
|
|
|
|
Accept(e, ref socket);
|
|
if (socket == null) return;
|
|
Process(socket);
|
|
}
|
|
|
|
void Process(object obj)
|
|
{
|
|
var socket = obj as Socket;
|
|
if (socket == null) return;
|
|
|
|
try
|
|
{
|
|
var conn = null as MiniConnection;
|
|
conn = new MiniConnection(this, socket);
|
|
conn.BeginRead();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Shutdown();
|
|
Logger.Web.Exception(this, ex);
|
|
CatchException?.Invoke(ex);
|
|
return;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|
|
|