remove some useless code form http low level; delay normal connection close, to let client do it instead

master
UbitUmarov 2020-04-21 00:13:02 +01:00
parent c097f148dd
commit 43fdbf87d4
7 changed files with 61 additions and 92 deletions

View File

@ -277,7 +277,7 @@ namespace OSHttpServer
if (context.TriggerKeepalive) if (context.TriggerKeepalive)
{ {
context.TriggerKeepalive = false; context.TriggerKeepalive = false;
context.MonitorKeepaliveStartMS = nowMS; context.MonitorKeepaliveStartMS = nowMS + 1;
return false; return false;
} }
@ -285,7 +285,10 @@ namespace OSHttpServer
{ {
if (EnvironmentTickCountAdd(context.TimeoutKeepAlive, context.MonitorKeepaliveStartMS) < nowMS) if (EnvironmentTickCountAdd(context.TimeoutKeepAlive, context.MonitorKeepaliveStartMS) < nowMS)
{ {
disconnectError = SocketError.TimedOut; if(context.IsClosing)
disconnectError = SocketError.Success;
else
disconnectError = SocketError.TimedOut;
context.MonitorKeepaliveStartMS = 0; context.MonitorKeepaliveStartMS = 0;
return true; return true;
} }

View File

@ -21,13 +21,13 @@ namespace OSHttpServer
public class HttpClientContext : IHttpClientContext, IDisposable public class HttpClientContext : IHttpClientContext, IDisposable
{ {
const int MAXREQUESTS = 20; const int MAXREQUESTS = 20;
const int MAXKEEPALIVE = 60000; const int MAXKEEPALIVE = 120000;
static private int basecontextID; static private int basecontextID;
private readonly byte[] m_ReceiveBuffer; private readonly byte[] m_ReceiveBuffer;
private int m_ReceiveBytesLeft; private int m_ReceiveBytesLeft;
private ILogWriter _log; private ILogWriter m_log;
private readonly IHttpRequestParser m_parser; private readonly IHttpRequestParser m_parser;
private HashSet<uint> requestsInServiceIDs; private HashSet<uint> requestsInServiceIDs;
private Socket m_sock; private Socket m_sock;
@ -42,8 +42,8 @@ namespace OSHttpServer
public int TimeoutRequestReceived = 30000; // 30 seconds public int TimeoutRequestReceived = 30000; // 30 seconds
// The difference between this and request received is on POST more time is needed before we get the full request. // The difference between this and request received is on POST more time is needed before we get the full request.
public int TimeoutMaxIdle = 600000; // 3 minutes public int TimeoutMaxIdle = 600000; // 10 minutes
public int m_TimeoutKeepAlive = MAXKEEPALIVE; // 400 seconds before keepalive timeout public int m_TimeoutKeepAlive = 60000;
public int m_maxRequests = MAXREQUESTS; public int m_maxRequests = MAXREQUESTS;
@ -51,6 +51,7 @@ namespace OSHttpServer
public bool FullRequestReceived; public bool FullRequestReceived;
private bool isSendingResponse = false; private bool isSendingResponse = false;
private bool m_isClosing = false;
private HttpRequest m_currentRequest; private HttpRequest m_currentRequest;
private HttpResponse m_currentResponse; private HttpResponse m_currentResponse;
@ -65,6 +66,11 @@ namespace OSHttpServer
} }
} }
public bool IsClosing
{
get { return m_isClosing;}
}
public int MaxRequests public int MaxRequests
{ {
get { return m_maxRequests; } get { return m_maxRequests; }
@ -102,14 +108,15 @@ namespace OSHttpServer
/// <exception cref="SocketException">If <see cref="Socket.BeginReceive(byte[],int,int,SocketFlags,AsyncCallback,object)"/> fails</exception> /// <exception cref="SocketException">If <see cref="Socket.BeginReceive(byte[],int,int,SocketFlags,AsyncCallback,object)"/> fails</exception>
/// <exception cref="ArgumentException">Stream must be writable and readable.</exception> /// <exception cref="ArgumentException">Stream must be writable and readable.</exception>
public HttpClientContext(bool secured, IPEndPoint remoteEndPoint, public HttpClientContext(bool secured, IPEndPoint remoteEndPoint,
Stream stream, IRequestParserFactory parserFactory, Socket sock) Stream stream, ILogWriter m_logWriter, Socket sock)
{ {
if (!stream.CanWrite || !stream.CanRead) if (!stream.CanWrite || !stream.CanRead)
throw new ArgumentException("Stream must be writable and readable."); throw new ArgumentException("Stream must be writable and readable.");
LocalIPEndPoint = remoteEndPoint; LocalIPEndPoint = remoteEndPoint;
_log = NullLogWriter.Instance; m_log = m_logWriter;
m_parser = parserFactory.CreateParser(_log); m_isClosing = false;
m_parser = new HttpRequestParser(m_log);
m_parser.RequestCompleted += OnRequestCompleted; m_parser.RequestCompleted += OnRequestCompleted;
m_parser.RequestLineReceived += OnRequestLine; m_parser.RequestLineReceived += OnRequestLine;
m_parser.HeaderReceived += OnHeaderReceived; m_parser.HeaderReceived += OnHeaderReceived;
@ -145,7 +152,7 @@ namespace OSHttpServer
public bool CanSend() public bool CanSend()
{ {
if (contextID < 0) if (contextID < 0 || m_isClosing)
return false; return false;
if (m_stream == null || m_sock == null || !m_sock.Connected) if (m_stream == null || m_sock == null || !m_sock.Connected)
@ -273,11 +280,11 @@ namespace OSHttpServer
/// </summary> /// </summary>
public ILogWriter LogWriter public ILogWriter LogWriter
{ {
get { return _log; } get { return m_log; }
set set
{ {
_log = value ?? NullLogWriter.Instance; m_log = value ?? NullLogWriter.Instance;
m_parser.LogWriter = _log; m_parser.LogWriter = m_log;
} }
} }
@ -309,9 +316,10 @@ namespace OSHttpServer
{ {
try try
{ {
m_stream.Flush(); // we should be on a work task so hold on it m_stream.Flush();
} }
catch { } catch { }
} }
m_stream.Close(); m_stream.Close();
m_stream = null; m_stream = null;
@ -346,6 +354,9 @@ namespace OSHttpServer
return; return;
} }
if(m_isClosing)
continue;
m_ReceiveBytesLeft += bytesRead; m_ReceiveBytesLeft += bytesRead;
if (m_ReceiveBytesLeft > m_ReceiveBuffer.Length) if (m_ReceiveBytesLeft > m_ReceiveBuffer.Length)
throw new BadRequestException("HTTP header Too large: " + m_ReceiveBytesLeft); throw new BadRequestException("HTTP header Too large: " + m_ReceiveBytesLeft);
@ -506,7 +517,7 @@ namespace OSHttpServer
ContextTimeoutManager.EnqueueSend(this, m_currentResponse.Priority, notThrottled); ContextTimeoutManager.EnqueueSend(this, m_currentResponse.Priority, notThrottled);
} }
public void EndSendResponse(uint requestID, ConnectionType ctype) public async Task EndSendResponse(uint requestID, ConnectionType ctype)
{ {
isSendingResponse = false; isSendingResponse = false;
m_currentResponse?.Clear(); m_currentResponse?.Clear();
@ -522,19 +533,28 @@ namespace OSHttpServer
} }
if (doclose) if (doclose)
Disconnect(SocketError.Success); {
m_isClosing = true;
lock (requestsInServiceIDs)
requestsInServiceIDs.Clear();
TriggerKeepalive = true;
return;
}
else else
{ {
LastActivityTimeMS = ContextTimeoutManager.EnvironmentTickCount(); LastActivityTimeMS = ContextTimeoutManager.EnvironmentTickCount();
if(Stream!=null && Stream.CanWrite) if(Stream!=null && Stream.CanWrite)
{ {
ContextTimeoutManager.ContextEnterActiveSend();
try try
{ {
Stream.Flush(); await Stream.FlushAsync().ConfigureAwait(false);
} }
catch catch
{ {
}; };
ContextTimeoutManager.ContextLeaveActiveSend();
} }
lock (requestsInServiceIDs) lock (requestsInServiceIDs)

View File

@ -16,7 +16,6 @@ namespace OSHttpServer
public class HttpContextFactory : IHttpContextFactory public class HttpContextFactory : IHttpContextFactory
{ {
private readonly ConcurrentDictionary<int, HttpClientContext> m_activeContexts = new ConcurrentDictionary<int, HttpClientContext>(); private readonly ConcurrentDictionary<int, HttpClientContext> m_activeContexts = new ConcurrentDictionary<int, HttpClientContext>();
private readonly IRequestParserFactory m_parserfactory;
private readonly ILogWriter m_logWriter; private readonly ILogWriter m_logWriter;
/// <summary> /// <summary>
@ -30,10 +29,9 @@ namespace OSHttpServer
/// <param name="writer">The writer.</param> /// <param name="writer">The writer.</param>
/// <param name="bufferSize">Amount of bytes to read from the incoming socket stream.</param> /// <param name="bufferSize">Amount of bytes to read from the incoming socket stream.</param>
/// <param name="factory">Used to create a request parser.</param> /// <param name="factory">Used to create a request parser.</param>
public HttpContextFactory(ILogWriter writer, IRequestParserFactory factory) public HttpContextFactory(ILogWriter writer)
{ {
m_logWriter = writer; m_logWriter = writer;
m_parserfactory = factory;
ContextTimeoutManager.Start(); ContextTimeoutManager.Start();
} }
@ -46,33 +44,16 @@ namespace OSHttpServer
/// <returns>A context.</returns> /// <returns>A context.</returns>
protected HttpClientContext CreateContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock) protected HttpClientContext CreateContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock)
{ {
HttpClientContext context; var context = new HttpClientContext(isSecured, endPoint, stream, m_logWriter, sock);
context = CreateNewContext(isSecured, endPoint, stream, sock);
context.Disconnected += OnFreeContext; context.Disconnected += OnFreeContext;
context.RequestReceived += OnRequestReceived; context.RequestReceived += OnRequestReceived;
context.Stream = stream;
context.IsSecured = isSecured;
context.LocalIPEndPoint = endPoint;
ContextTimeoutManager.StartMonitoringContext(context); ContextTimeoutManager.StartMonitoringContext(context);
m_activeContexts[context.contextID] = context; m_activeContexts[context.contextID] = context;
context.Start(); context.Start();
return context; return context;
} }
/// <summary>
/// Create a new context.
/// </summary>
/// <param name="isSecured">true if HTTPS is used.</param>
/// <param name="endPoint">Remote client</param>
/// <param name="stream">Network stream, <see cref="HttpClientContext"/></param>
/// <returns>A new context (always).</returns>
protected virtual HttpClientContext CreateNewContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock)
{
return new HttpClientContext(isSecured, endPoint, stream, m_parserfactory, sock);
}
private void OnRequestReceived(object sender, RequestEventArgs e) private void OnRequestReceived(object sender, RequestEventArgs e)
{ {
RequestReceived?.Invoke(sender, e); RequestReceived?.Invoke(sender, e);

View File

@ -34,11 +34,11 @@ namespace OSHttpServer
/// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param> /// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param>
/// <exception cref="ArgumentNullException"><c>address</c> is null.</exception> /// <exception cref="ArgumentNullException"><c>address</c> is null.</exception>
/// <exception cref="ArgumentException">Port must be a positive number.</exception> /// <exception cref="ArgumentException">Port must be a positive number.</exception>
protected OSHttpListener(IPAddress address, int port, IHttpContextFactory factory) protected OSHttpListener(IPAddress address, int port)
{ {
m_address = address; m_address = address;
m_port = port; m_port = port;
m_contextFactory = factory; m_contextFactory = new HttpContextFactory(m_logWriter);
m_contextFactory.RequestReceived += OnRequestReceived; m_contextFactory.RequestReceived += OnRequestReceived;
} }
@ -49,12 +49,10 @@ namespace OSHttpServer
/// <param name="port">TCP Port to listen on, default HTTPS port is 443</param> /// <param name="port">TCP Port to listen on, default HTTPS port is 443</param>
/// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param> /// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param>
/// <param name="certificate">Certificate to use</param> /// <param name="certificate">Certificate to use</param>
/// <param name="protocol">which HTTPS protocol to use, default is TLS.</param> protected OSHttpListener(IPAddress address, int port, X509Certificate certificate)
protected OSHttpListener(IPAddress address, int port, IHttpContextFactory factory, X509Certificate certificate, : this(address, port)
SslProtocols protocol)
: this(address, port, factory, certificate)
{ {
m_sslProtocol = protocol; m_certificate = certificate;
} }
/// <summary> /// <summary>
@ -64,31 +62,28 @@ namespace OSHttpServer
/// <param name="port">TCP Port to listen on, default HTTPS port is 443</param> /// <param name="port">TCP Port to listen on, default HTTPS port is 443</param>
/// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param> /// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param>
/// <param name="certificate">Certificate to use</param> /// <param name="certificate">Certificate to use</param>
protected OSHttpListener(IPAddress address, int port, IHttpContextFactory factory, X509Certificate certificate) /// <param name="protocol">which HTTPS protocol to use, default is TLS.</param>
: this(address, port, factory) protected OSHttpListener(IPAddress address, int port, X509Certificate certificate,
SslProtocols protocol)
: this(address, port)
{ {
m_certificate = certificate; m_certificate = certificate;
m_sslProtocol = protocol;
} }
public static OSHttpListener Create(IPAddress address, int port) public static OSHttpListener Create(IPAddress address, int port)
{ {
RequestParserFactory requestFactory = new RequestParserFactory(); return new OSHttpListener(address, port);
HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, requestFactory);
return new OSHttpListener(address, port, factory);
} }
public static OSHttpListener Create(IPAddress address, int port, X509Certificate certificate) public static OSHttpListener Create(IPAddress address, int port, X509Certificate certificate)
{ {
RequestParserFactory requestFactory = new RequestParserFactory(); return new OSHttpListener(address, port, certificate);
HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, requestFactory);
return new OSHttpListener(address, port, factory, certificate);
} }
public static OSHttpListener Create(IPAddress address, int port, X509Certificate certificate, SslProtocols protocol) public static OSHttpListener Create(IPAddress address, int port, X509Certificate certificate, SslProtocols protocol)
{ {
RequestParserFactory requestFactory = new RequestParserFactory(); return new OSHttpListener(address, port, certificate, protocol);
HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, requestFactory);
return new OSHttpListener(address, port, factory, certificate, protocol);
} }
private void OnRequestReceived(object sender, RequestEventArgs e) private void OnRequestReceived(object sender, RequestEventArgs e)
@ -96,7 +91,6 @@ namespace OSHttpServer
RequestReceived?.Invoke(sender, e); RequestReceived?.Invoke(sender, e);
} }
public RemoteCertificateValidationCallback CertificateValidationCallback public RemoteCertificateValidationCallback CertificateValidationCallback
{ {
set { m_clientCertValCallback = value; } set { m_clientCertValCallback = value; }

View File

@ -281,6 +281,9 @@ namespace OSHttpServer
public void Send() public void Send()
{ {
if(m_context.IsClosing)
return;
if (Sent) if (Sent)
throw new InvalidOperationException("Everything have already been sent."); throw new InvalidOperationException("Everything have already been sent.");
@ -446,7 +449,7 @@ namespace OSHttpServer
if (m_body != null) if (m_body != null)
m_body.Dispose(); m_body.Dispose();
Sent = true; Sent = true;
m_context.EndSendResponse(requestID, Connection); await m_context.EndSendResponse(requestID, Connection).ConfigureAwait(false);
} }
private int CheckBandwidth(int request, int bytesLimit) private int CheckBandwidth(int request, int bytesLimit)

View File

@ -29,6 +29,7 @@ namespace OSHttpServer
bool CanSend(); bool CanSend();
bool IsSending(); bool IsSending();
bool IsClosing {get ;}
/// <summary> /// <summary>
/// Disconnect from client /// Disconnect from client
@ -95,7 +96,7 @@ namespace OSHttpServer
void StartSendResponse(HttpResponse response); void StartSendResponse(HttpResponse response);
void ContinueSendResponse(bool notThrottled); void ContinueSendResponse(bool notThrottled);
void EndSendResponse(uint requestID, ConnectionType connection); Task EndSendResponse(uint requestID, ConnectionType connection);
bool TrySendResponse(int limit); bool TrySendResponse(int limit);
} }

View File

@ -1,33 +0,0 @@
using OSHttpServer.Parser;
namespace OSHttpServer
{
/// <summary>
/// Creates request parsers when needed.
/// </summary>
public class RequestParserFactory : IRequestParserFactory
{
/// <summary>
/// Create a new request parser.
/// </summary>
/// <param name="logWriter">Used when logging should be enabled.</param>
/// <returns>A new request parser.</returns>
public IHttpRequestParser CreateParser(ILogWriter logWriter)
{
return new HttpRequestParser(logWriter);
}
}
/// <summary>
/// Creates request parsers when needed.
/// </summary>
public interface IRequestParserFactory
{
/// <summary>
/// Create a new request parser.
/// </summary>
/// <param name="logWriter">Used when logging should be enabled.</param>
/// <returns>A new request parser.</returns>
IHttpRequestParser CreateParser(ILogWriter logWriter);
}
}