remove some useless code form http low level; delay normal connection close, to let client do it instead
parent
c097f148dd
commit
43fdbf87d4
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue