update last changes to now unused httpserver

This commit is contained in:
UbitUmarov
2020-09-03 19:26:52 +01:00
parent 44d22a7e76
commit 33cd0fae8b
11 changed files with 917 additions and 731 deletions
@@ -27,11 +27,11 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace HttpServer
{
@@ -44,45 +44,73 @@ namespace HttpServer
/// Use a Thread or a Timer to monitor the ugly
/// </summary>
private static Thread m_internalThread = null;
// private static readonly LocklessQueue<HttpClientContext> m_contexts = new LocklessQueue<HttpClientContext>();
private static object m_threadLock = new object();
private static ConcurrentQueue<HttpClientContext> m_contexts = new ConcurrentQueue<HttpClientContext>();
private static ConcurrentQueue<HttpClientContext> m_highPrio = new ConcurrentQueue<HttpClientContext>();
private static ConcurrentQueue<HttpClientContext> m_midPrio = new ConcurrentQueue<HttpClientContext>();
private static ConcurrentQueue<HttpClientContext> m_lowPrio = new ConcurrentQueue<HttpClientContext>();
private static AutoResetEvent m_processWaitEven = new AutoResetEvent(false);
private static bool m_shuttingDown;
private static int m_monitorMS = 1000;
private static int m_ActiveSendingCount;
private static double m_lastTimeOutCheckTime = 0;
private static double m_lastSendCheckTime = 0;
const int m_maxBandWidth = 10485760; //80Mbps
const int m_maxConcurrenSend = 32;
static ContextTimeoutManager()
{
TimeStampClockPeriod = 1.0 / (double)Stopwatch.Frequency;
TimeStampClockPeriodMS = 1e3 / (double)Stopwatch.Frequency;
}
public static void StartMonitoring()
public static void Start()
{
if(m_internalThread != null)
return;
m_internalThread = new Thread(ThreadRunProcess);
m_internalThread.Priority = ThreadPriority.Normal;
m_internalThread.IsBackground = true;
m_internalThread.CurrentCulture = new CultureInfo("en-US", false);
m_internalThread.Name = "HttpServer Timeout Checker";
m_internalThread.Start();
lock (m_threadLock)
{
if (m_internalThread != null)
return;
m_lastTimeOutCheckTime = GetTimeStampMS();
m_internalThread = new Thread(ThreadRunProcess);
m_internalThread.Priority = ThreadPriority.Normal;
m_internalThread.IsBackground = true;
m_internalThread.CurrentCulture = new CultureInfo("en-US", false);
m_internalThread.Name = "HttpServerMain";
m_internalThread.Start();
}
}
public static void StopMonitoring()
public static void Stop()
{
m_shuttingDown = true;
m_internalThread.Join();
ProcessShutDown();
}
private static void TimerCallbackCheck(object o)
{
ProcessContextTimeouts();
}
private static void ThreadRunProcess(object o)
private static void ThreadRunProcess()
{
while (!m_shuttingDown)
{
ProcessContextTimeouts();
Thread.Sleep(m_monitorMS);
m_processWaitEven.WaitOne(100);
if(m_shuttingDown)
return;
double now = GetTimeStampMS();
if(m_contexts.Count > 0)
{
ProcessSendQueues(now);
if (now - m_lastTimeOutCheckTime > 1000)
{
ProcessContextTimeouts();
m_lastTimeOutCheckTime = now;
}
}
else
m_lastTimeOutCheckTime = now;
}
}
@@ -103,21 +131,90 @@ namespace HttpServer
catch { }
}
}
m_processWaitEven.Dispose();
m_processWaitEven = null;
}
catch (NullReferenceException)
{
// Lockless queue so something is null or disposed
}
catch (ObjectDisposedException)
{
// Lockless queue so something is null or disposed
}
catch (Exception)
catch
{
// We can't let this crash.
}
}
public static void ProcessSendQueues(double now)
{
int inqueues = m_highPrio.Count + m_midPrio.Count + m_lowPrio.Count;
if(inqueues == 0)
return;
double dt = now - m_lastSendCheckTime;
m_lastSendCheckTime = now;
int totalSending = m_ActiveSendingCount;
int curConcurrentLimit = m_maxConcurrenSend - totalSending;
if(curConcurrentLimit <= 0)
return;
if(curConcurrentLimit > inqueues)
curConcurrentLimit = inqueues;
if (dt > 0.1)
dt = 0.1;
dt /= curConcurrentLimit;
int curbytesLimit = (int)(m_maxBandWidth * dt);
if(curbytesLimit < 8192)
curbytesLimit = 8192;
HttpClientContext ctx;
int sent;
while (curConcurrentLimit > 0)
{
sent = 0;
while (m_highPrio.TryDequeue(out ctx))
{
if(TrySend(ctx, curbytesLimit))
m_highPrio.Enqueue(ctx);
if (m_shuttingDown)
return;
--curConcurrentLimit;
if (++sent == 4)
break;
}
sent = 0;
while(m_midPrio.TryDequeue(out ctx))
{
if(TrySend(ctx, curbytesLimit))
m_midPrio.Enqueue(ctx);
if (m_shuttingDown)
return;
--curConcurrentLimit;
if (++sent >= 2)
break;
}
if (m_lowPrio.TryDequeue(out ctx))
{
--curConcurrentLimit;
if(TrySend(ctx, curbytesLimit))
m_lowPrio.Enqueue(ctx);
}
if (m_shuttingDown)
return;
}
}
private static bool TrySend(HttpClientContext ctx, int bytesLimit)
{
if(!ctx.CanSend())
return false;
return ctx.TrySendResponse(bytesLimit);
}
/// <summary>
/// Causes the watcher to immediately check the connections.
@@ -128,65 +225,29 @@ namespace HttpServer
{
for (int i = 0; i < m_contexts.Count; i++)
{
HttpClientContext context = null;
if (m_contexts.TryDequeue(out context))
if (m_shuttingDown)
return;
if (m_contexts.TryDequeue(out HttpClientContext context))
{
SocketError disconnectError = SocketError.InProgress;
bool disconnect;
if (!ContextTimedOut(context, out disconnectError, out disconnect))
{
if (!ContextTimedOut(context, out SocketError disconnectError))
m_contexts.Enqueue(context);
}
else
{
if (disconnect)
{
context.Disconnect(disconnectError);
}
}
else if(disconnectError != SocketError.InProgress)
context.Disconnect(disconnectError);
}
}
}
catch (NullReferenceException)
{
// Lockless queue so something is null or disposed
}
catch (ObjectDisposedException)
{
// Lockless queue so something is null or disposed
}
catch (Exception)
catch
{
// We can't let this crash.
}
}
private static bool ContextTimedOut(HttpClientContext context, out SocketError disconnectError, out bool disconnect)
private static bool ContextTimedOut(HttpClientContext context, out SocketError disconnectError)
{
disconnect = false;
disconnectError = SocketError.InProgress;
// First our error conditions
if (context == null)
return true;
if (context.Available)
return true;
// Next our special use conditions
// Special case when multiple client contexts are being responded to by a single thread
//if (context.EndWhenDone)
//{
// stopMonitoring = true;
// return true;
//}
// Special case for websockets
if (context.StreamPassedOff)
return true;
// Now for the case when the context has the stop monitoring bool set
if (context.StopMonitoring)
if (context.contextID < 0 || context.StopMonitoring || context.StreamPassedOff)
return true;
// Now we start checking for actual timeouts
@@ -197,13 +258,11 @@ namespace HttpServer
if (EnvironmentTickCountAdd(context.TimeoutFirstLine, context.MonitorStartMS) <= EnvironmentTickCount())
{
disconnectError = SocketError.TimedOut;
disconnect = true;
context.MonitorStartMS = 0;
return true;
}
}
//
if (!context.FullRequestReceived)
{
if (EnvironmentTickCountAdd(context.TimeoutRequestReceived, context.MonitorStartMS) <= EnvironmentTickCount())
@@ -220,7 +279,6 @@ namespace HttpServer
if (EnvironmentTickCountAdd(context.TimeoutFullRequestProcessed, context.MonitorStartMS) <= EnvironmentTickCount())
{
disconnectError = SocketError.TimedOut;
disconnect = true;
context.MonitorStartMS = 0;
return true;
}
@@ -240,7 +298,6 @@ namespace HttpServer
{
disconnectError = SocketError.TimedOut;
context.MonitorStartMS = 0;
disconnect = true;
context.MonitorKeepaliveMS = 0;
return true;
}
@@ -254,6 +311,35 @@ namespace HttpServer
m_contexts.Enqueue(context);
}
public static void EnqueueSend(HttpClientContext context, int priority)
{
switch(priority)
{
case 0:
m_highPrio.Enqueue(context);
break;
case 1:
m_midPrio.Enqueue(context);
break;
case 2:
m_lowPrio.Enqueue(context);
break;
default:
return;
}
m_processWaitEven.Set();
}
public static void ContextEnterActiveSend()
{
Interlocked.Increment(ref m_ActiveSendingCount);
}
public static void ContextLeaveActiveSend()
{
Interlocked.Decrement(ref m_ActiveSendingCount);
}
/// <summary>
/// Environment.TickCount is an int but it counts all 32 bits so it goes positive
/// and negative every 24.9 days. This trims down TickCount so it doesn't wrap
@@ -261,11 +347,11 @@ namespace HttpServer
/// This trims it to a 12 day interval so don't let your frame time get too long.
/// </summary>
/// <returns></returns>
public static Int32 EnvironmentTickCount()
public static int EnvironmentTickCount()
{
return Environment.TickCount & EnvironmentTickCountMask;
}
const Int32 EnvironmentTickCountMask = 0x3fffffff;
const int EnvironmentTickCountMask = 0x3fffffff;
/// <summary>
/// Environment.TickCount is an int but it counts all 32 bits so it goes positive
@@ -275,9 +361,9 @@ namespace HttpServer
/// <param name="newValue"></param>
/// <param name="prevValue"></param>
/// <returns>subtraction of passed prevValue from current Environment.TickCount</returns>
public static Int32 EnvironmentTickCountSubtract(Int32 newValue, Int32 prevValue)
public static int EnvironmentTickCountSubtract(Int32 newValue, Int32 prevValue)
{
Int32 diff = newValue - prevValue;
int diff = newValue - prevValue;
return (diff >= 0) ? diff : (diff + EnvironmentTickCountMask + 1);
}
@@ -289,37 +375,39 @@ namespace HttpServer
/// <param name="newValue"></param>
/// <param name="prevValue"></param>
/// <returns>subtraction of passed prevValue from current Environment.TickCount</returns>
public static Int32 EnvironmentTickCountAdd(Int32 newValue, Int32 prevValue)
public static int EnvironmentTickCountAdd(Int32 newValue, Int32 prevValue)
{
Int32 ret = newValue + prevValue;
int ret = newValue + prevValue;
return (ret >= 0) ? ret : (ret + EnvironmentTickCountMask + 1);
}
/// <summary>
/// Environment.TickCount is an int but it counts all 32 bits so it goes positive
/// and negative every 24.9 days. Subtracts the passed value (previously fetched by
/// 'EnvironmentTickCount()') and accounts for any wrapping.
/// </summary>
/// <returns>subtraction of passed prevValue from current Environment.TickCount</returns>
public static Int32 EnvironmentTickCountSubtract(Int32 prevValue)
public static double TimeStampClockPeriodMS;
public static double TimeStampClockPeriod;
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static double GetTimeStamp()
{
return EnvironmentTickCountSubtract(EnvironmentTickCount(), prevValue);
return Stopwatch.GetTimestamp() * TimeStampClockPeriod;
}
// Returns value of Tick Count A - TickCount B accounting for wrapping of TickCount
// Assumes both tcA and tcB came from previous calls to Util.EnvironmentTickCount().
// A positive return value indicates A occured later than B
public static Int32 EnvironmentTickCountCompare(Int32 tcA, Int32 tcB)
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static double GetTimeStampMS()
{
// A, B and TC are all between 0 and 0x3fffffff
int tc = EnvironmentTickCount();
if (tc - tcA >= 0)
tcA += EnvironmentTickCountMask + 1;
if (tc - tcB >= 0)
tcB += EnvironmentTickCountMask + 1;
return tcA - tcB;
return Stopwatch.GetTimestamp() * TimeStampClockPeriodMS;
}
// doing math in ticks is usefull to avoid loss of resolution
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static long GetTimeStampTicks()
{
return Stopwatch.GetTimestamp();
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static double TimeStampTicksToMS(long ticks)
{
return ticks * TimeStampClockPeriodMS;
}
}
}
+241 -171
View File
@@ -4,12 +4,12 @@ using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using HttpServer.Exceptions;
using HttpServer.Parser;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace HttpServer
{
/// <summary>
@@ -26,17 +26,17 @@ namespace HttpServer
static private int basecontextID;
private readonly byte[] _buffer;
private int _bytesLeft;
private readonly byte[] m_ReceiveBuffer;
private int m_ReceiveBytesLeft;
private ILogWriter _log;
private readonly IHttpRequestParser _parser;
private readonly int _bufferSize;
private IHttpRequest _currentRequest;
private readonly IHttpRequestParser m_parser;
private readonly int m_bufferSize;
private HashSet<uint> requestsInServiceIDs;
private Socket _sock;
private Socket m_sock;
public bool Available = true;
public bool StreamPassedOff = false;
public int MonitorStartMS = 0;
public int MonitorKeepaliveMS = 0;
public bool TriggerKeepalive = false;
@@ -48,39 +48,33 @@ namespace HttpServer
public int m_TimeoutKeepAlive = MAXKEEPALIVE; // 400 seconds before keepalive timeout
// public int TimeoutKeepAlive = 120000; // 400 seconds before keepalive timeout
public int m_MAXRequests = MAXREQUESTS;
public int m_maxRequests = MAXREQUESTS;
public bool FirstRequestLineReceived;
public bool FullRequestReceived;
public bool FullRequestProcessed;
private bool gotResponseClose = false;
private bool isSendingResponse = false;
private HttpRequest m_currentRequest;
private HttpResponse m_currentResponse;
public int contextID { get; private set; }
public int TimeoutKeepAlive
{
get { return m_TimeoutKeepAlive; }
set
{
if (value > MAXKEEPALIVE)
m_TimeoutKeepAlive = MAXKEEPALIVE;
else
m_TimeoutKeepAlive = value;
m_TimeoutKeepAlive = (value > MAXKEEPALIVE) ? MAXKEEPALIVE : value;
}
}
public int MAXRequests
{
get { return m_MAXRequests; }
get { return m_maxRequests; }
set
{
if (value > MAXREQUESTS)
m_MAXRequests = MAXREQUESTS;
else if (value <= 0)
m_MAXRequests = 0;
else
m_MAXRequests = value;
m_maxRequests = value > MAXREQUESTS ? MAXREQUESTS : value;
}
}
@@ -91,14 +85,13 @@ namespace HttpServer
public bool StopMonitoring;
/// <summary>
/// Context have been started (a new client have connected)
/// </summary>
public event EventHandler Started = delegate { };
public event EventHandler Started;
/// <summary>
/// Initializes a new instance of the <see cref="HttpClientContext"/> class.
/// Initializes a new instance of the <see cref="HttpClientContext"/> class.
/// </summary>
/// <param name="secured">true if the connection is secured (SSL/TLS)</param>
/// <param name="remoteEndPoint">client that connected.</param>
@@ -108,7 +101,7 @@ namespace HttpServer
/// <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>
public HttpClientContext(bool secured, IPEndPoint remoteEndPoint,
Stream stream, IRequestParserFactory parserFactory, int bufferSize, Socket sock)
Stream stream, IRequestParserFactory parserFactory, Socket sock)
{
Check.Require(remoteEndPoint, "remoteEndPoint");
Check.NotEmpty(remoteEndPoint.Address.ToString(), "remoteEndPoint.Address");
@@ -119,21 +112,21 @@ namespace HttpServer
if (!stream.CanWrite || !stream.CanRead)
throw new ArgumentException("Stream must be writable and readable.");
_bufferSize = 8192;
RemoteAddress = remoteEndPoint.Address.ToString();
RemotePort = remoteEndPoint.Port.ToString();
_log = NullLogWriter.Instance;
_parser = parserFactory.CreateParser(_log);
_parser.RequestCompleted += OnRequestCompleted;
_parser.RequestLineReceived += OnRequestLine;
_parser.HeaderReceived += OnHeaderReceived;
_parser.BodyBytesReceived += OnBodyBytesReceived;
_currentRequest = new HttpRequest(this);
Available = false;
m_parser = parserFactory.CreateParser(_log);
m_parser.RequestCompleted += OnRequestCompleted;
m_parser.RequestLineReceived += OnRequestLine;
m_parser.HeaderReceived += OnHeaderReceived;
m_parser.BodyBytesReceived += OnBodyBytesReceived;
m_currentRequest = new HttpRequest(this);
IsSecured = secured;
_stream = stream;
_sock = sock;
_buffer = new byte[bufferSize];
m_sock = sock;
m_bufferSize = 8196;
m_ReceiveBuffer = new byte[m_bufferSize];
requestsInServiceIDs = new HashSet<uint>();
SSLCommonName = "";
@@ -149,34 +142,19 @@ namespace HttpServer
}
}
basecontextID++;
if (basecontextID < 0)
++basecontextID;
if (basecontextID <= 0)
basecontextID = 1;
contextID = basecontextID;
}
public int SendBufferSize(int newSize)
{
try
{
if (newSize > 8192)
_sock.SendBufferSize = 8192;
return _sock.SendBufferSize;
}
catch
{
return 8192;
}
}
public bool CanSend()
{
if (Available || contextID < 0)
if (contextID < 0)
return false;
if (Stream == null || _sock == null || !_sock.Connected)
if (Stream == null || m_sock == null || !m_sock.Connected)
return false;
return true;
@@ -189,7 +167,7 @@ namespace HttpServer
/// <param name="e">Bytes</param>
protected virtual void OnBodyBytesReceived(object sender, BodyEventArgs e)
{
_currentRequest.AddToBody(e.Buffer, e.Offset, e.Count);
m_currentRequest.AddToBody(e.Buffer, e.Offset, e.Count);
}
/// <summary>
@@ -204,39 +182,22 @@ namespace HttpServer
lock (requestsInServiceIDs)
{
if (requestsInServiceIDs.Count == 0)
Respond("HTTP/1.1", HttpStatusCode.Continue, "Please continue mate.");
Respond("HTTP/1.1", HttpStatusCode.Continue, "Please continue.");
}
}
_currentRequest.AddHeader(e.Name, e.Value);
m_currentRequest.AddHeader(e.Name, e.Value);
}
private void OnRequestLine(object sender, RequestLineEventArgs e)
{
_currentRequest.Method = e.HttpMethod;
_currentRequest.HttpVersion = e.HttpVersion;
_currentRequest.UriPath = e.UriPath;
_currentRequest.AddHeader("remote_addr", RemoteAddress);
_currentRequest.AddHeader("remote_port", RemotePort);
m_currentRequest.Method = e.HttpMethod;
m_currentRequest.HttpVersion = e.HttpVersion;
m_currentRequest.UriPath = e.UriPath;
m_currentRequest.AddHeader("remote_addr", RemoteAddress);
m_currentRequest.AddHeader("remote_port", RemotePort);
FirstRequestLineReceived = true;
}
/// <summary>
/// Overload to specify own type.
/// </summary>
/// <remarks>
/// Must be specified before the context is being used.
/// </remarks>
protected IHttpRequest CurrentRequest
{
get
{
return _currentRequest;
}
set
{
_currentRequest = value;
}
TriggerKeepalive = false;
MonitorKeepaliveMS = 0;
}
/// <summary>
@@ -247,16 +208,8 @@ namespace HttpServer
/// </remarks>
public virtual void Start()
{
try
{
_stream.BeginRead(_buffer, 0, _bufferSize, OnReceive, null);
}
catch (IOException err)
{
LogWriter.Write(this, LogPrio.Debug, err.ToString());
}
Started(this, EventArgs.Empty);
ReceiveLoop();
Started?.Invoke(this, EventArgs.Empty);
}
/// <summary>
@@ -274,11 +227,14 @@ namespace HttpServer
{
Stream.Close();
Stream = null;
_sock = null;
m_sock = null;
}
_currentRequest.Clear();
m_currentRequest?.Clear();
m_currentRequest = null;
m_currentResponse?.Clear();
m_currentResponse = null;
requestsInServiceIDs.Clear();
_bytesLeft = 0;
FirstRequestLineReceived = false;
FullRequestReceived = false;
@@ -287,17 +243,18 @@ namespace HttpServer
StopMonitoring = true;
MonitorKeepaliveMS = 0;
TriggerKeepalive = false;
gotResponseClose = false;
isSendingResponse = false;
m_ReceiveBytesLeft = 0;
contextID = -100;
_parser.Clear();
m_parser.Clear();
}
public void Close()
{
Cleanup();
Available = true;
}
/// <summary>
@@ -327,7 +284,7 @@ namespace HttpServer
set
{
_log = value ?? NullLogWriter.Instance;
_parser.LogWriter = _log;
m_parser.LogWriter = _log;
}
}
@@ -370,7 +327,7 @@ namespace HttpServer
Stream.Close();
Stream = null;
}
_sock = null;
m_sock = null;
}
catch { }
@@ -404,38 +361,24 @@ namespace HttpServer
Disconnect(SocketError.ConnectionReset);
return;
}
_bytesLeft += bytesRead;
if (_bytesLeft > _buffer.Length)
m_ReceiveBytesLeft += bytesRead;
if (m_ReceiveBytesLeft > m_ReceiveBuffer.Length)
{
#if DEBUG
throw new BadRequestException("Too large HTTP header: " + Encoding.UTF8.GetString(_buffer, 0, bytesRead));
#else
throw new BadRequestException("Too large HTTP header: " + _bytesLeft);
#endif
throw new BadRequestException("HTTP header Too large: " + m_ReceiveBytesLeft);
}
#if DEBUG
#pragma warning disable 219
string temp = Encoding.ASCII.GetString(_buffer, 0, _bytesLeft);
LogWriter.Write(this, LogPrio.Trace, "Received: " + temp);
#pragma warning restore 219
#endif
int offset = _parser.Parse(_buffer, 0, _bytesLeft);
int offset = m_parser.Parse(m_ReceiveBuffer, 0, m_ReceiveBytesLeft);
if (Stream == null)
return; // "Connection: Close" in effect.
// try again to see if we can parse another message (check parser to see if it is looking for a new message)
int nextOffset;
int nextBytesleft = _bytesLeft - offset;
// while (_parser.CurrentState == RequestParserState.FirstLine && offset != 0 && _bytesLeft - offset > 0)
int nextBytesleft = m_ReceiveBytesLeft - offset;
while (offset != 0 && nextBytesleft > 0)
{
#if DEBUG
temp = Encoding.ASCII.GetString(_buffer, offset, nextBytesleft);
LogWriter.Write(this, LogPrio.Trace, "Processing: " + temp);
#endif
nextOffset = _parser.Parse(_buffer, offset, nextBytesleft);
nextOffset = m_parser.Parse(m_ReceiveBuffer, offset, nextBytesleft);
if (Stream == null)
return; // "Connection: Close" in effect.
@@ -444,22 +387,115 @@ namespace HttpServer
break;
offset = nextOffset;
nextBytesleft = _bytesLeft - offset;
nextBytesleft = m_ReceiveBytesLeft - offset;
}
// copy unused bytes to the beginning of the array
if (offset > 0 && _bytesLeft > offset)
Buffer.BlockCopy(_buffer, offset, _buffer, 0, _bytesLeft - offset);
if (offset > 0 && m_ReceiveBytesLeft > offset)
Buffer.BlockCopy(m_ReceiveBuffer, offset, m_ReceiveBuffer, 0, m_ReceiveBytesLeft - offset);
_bytesLeft -= offset;
m_ReceiveBytesLeft -= offset;
if (Stream != null && Stream.CanRead)
{
if (!StreamPassedOff)
Stream.BeginRead(_buffer, _bytesLeft, _buffer.Length - _bytesLeft, OnReceive, null);
Stream.BeginRead(m_ReceiveBuffer, m_ReceiveBytesLeft, m_ReceiveBuffer.Length - m_ReceiveBytesLeft, OnReceive, null);
else
{
_log.Write(this, LogPrio.Warning, "Could not read any more from the socket.");
Disconnect(SocketError.Success);
}
}
}
catch (BadRequestException err)
{
LogWriter.Write(this, LogPrio.Warning, "Bad request, responding with it. Error: " + err);
try
{
Respond("HTTP/1.0", HttpStatusCode.BadRequest, err.Message);
}
catch (Exception err2)
{
LogWriter.Write(this, LogPrio.Fatal, "Failed to reply to a bad request. " + err2);
}
Disconnect(SocketError.NoRecovery);
}
catch (IOException err)
{
LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message);
if (err.InnerException is SocketException)
Disconnect((SocketError)((SocketException)err.InnerException).ErrorCode);
else
Disconnect(SocketError.ConnectionReset);
}
catch (ObjectDisposedException err)
{
LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : " + err.Message);
Disconnect(SocketError.NotSocket);
}
catch (NullReferenceException err)
{
LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : NullRef: " + err.Message);
Disconnect(SocketError.NoRecovery);
}
catch (Exception err)
{
LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message);
Disconnect(SocketError.NoRecovery);
}
}
private async void ReceiveLoop()
{
m_ReceiveBytesLeft = 0;
try
{
while(true)
{
if (_stream == null || !_stream.CanRead)
return;
int bytesRead = await _stream.ReadAsync(m_ReceiveBuffer, m_ReceiveBytesLeft, m_ReceiveBuffer.Length - m_ReceiveBytesLeft).ConfigureAwait(false);
if (bytesRead == 0)
{
Disconnect(SocketError.ConnectionReset);
return;
}
m_ReceiveBytesLeft += bytesRead;
if (m_ReceiveBytesLeft > m_ReceiveBuffer.Length)
throw new BadRequestException("HTTP header Too large: " + m_ReceiveBytesLeft);
int offset = m_parser.Parse(m_ReceiveBuffer, 0, m_ReceiveBytesLeft);
if (Stream == null)
return; // "Connection: Close" in effect.
// try again to see if we can parse another message (check parser to see if it is looking for a new message)
int nextOffset;
int nextBytesleft = m_ReceiveBytesLeft - offset;
while (offset != 0 && nextBytesleft > 0)
{
nextOffset = m_parser.Parse(m_ReceiveBuffer, offset, nextBytesleft);
if (Stream == null)
return; // "Connection: Close" in effect.
if (nextOffset == 0)
break;
offset = nextOffset;
nextBytesleft = m_ReceiveBytesLeft - offset;
}
// copy unused bytes to the beginning of the array
if (offset > 0 && m_ReceiveBytesLeft > offset)
Buffer.BlockCopy(m_ReceiveBuffer, offset, m_ReceiveBuffer, 0, m_ReceiveBytesLeft - offset);
m_ReceiveBytesLeft -= offset;
if (StreamPassedOff)
return; //?
}
}
catch (BadRequestException err)
{
@@ -505,12 +541,12 @@ namespace HttpServer
MonitorKeepaliveMS = 0;
// load cookies if they exist
RequestCookies cookies = _currentRequest.Headers["cookie"] != null
? new RequestCookies(_currentRequest.Headers["cookie"])
RequestCookies cookies = m_currentRequest.Headers["cookie"] != null
? new RequestCookies(m_currentRequest.Headers["cookie"])
: new RequestCookies(String.Empty);
_currentRequest.SetCookies(cookies);
m_currentRequest.SetCookies(cookies);
_currentRequest.Body.Seek(0, SeekOrigin.Begin);
m_currentRequest.Body.Seek(0, SeekOrigin.Begin);
FullRequestReceived = true;
@@ -518,15 +554,15 @@ namespace HttpServer
lock (requestsInServiceIDs)
{
nreqs = requestsInServiceIDs.Count;
requestsInServiceIDs.Add(_currentRequest.ID);
if (m_MAXRequests > 0)
m_MAXRequests--;
requestsInServiceIDs.Add(m_currentRequest.ID);
if (m_maxRequests > 0)
m_maxRequests--;
}
// for now pipeline requests need to be serialized by opensim
RequestReceived(this, new RequestEventArgs(_currentRequest));
RequestReceived(this, new RequestEventArgs(m_currentRequest));
_currentRequest = new HttpRequest(this);
m_currentRequest = new HttpRequest(this);
int nreqsnow;
lock (requestsInServiceIDs)
@@ -544,16 +580,41 @@ namespace HttpServer
isSendingResponse = true;
}
public void StartSendResponse(HttpResponse response)
{
isSendingResponse = true;
m_currentResponse = response;
ContextTimeoutManager.EnqueueSend(this, response.Priority);
}
public bool TrySendResponse(int bytesLimit)
{
if(m_currentResponse == null)
return false;
if (m_currentResponse.Sent)
return false;
if(!CanSend())
return false;
m_currentResponse?.SendNextAsync(bytesLimit);
return false;
}
public void ContinueSendResponse()
{
if(m_currentResponse == null)
return;
ContextTimeoutManager.EnqueueSend(this, m_currentResponse.Priority);
}
public void ReqResponseSent(uint requestID, ConnectionType ctype)
{
if (ctype == ConnectionType.Close)
gotResponseClose = true;
else
{
// breakpoint
}
isSendingResponse = false;
m_currentResponse?.Clear();
m_currentResponse = null;
bool doclose = gotResponseClose;
bool doclose = ctype == ConnectionType.Close;
lock (requestsInServiceIDs)
{
requestsInServiceIDs.Remove(requestID);
@@ -564,7 +625,6 @@ namespace HttpServer
}
}
isSendingResponse = false;
if (doclose)
Disconnect(SocketError.Success);
else
@@ -605,7 +665,7 @@ namespace HttpServer
byte[] buffer = Encoding.ASCII.GetBytes(response);
Send(buffer);
if (_currentRequest.Connection == ConnectionType.Close)
if (m_currentRequest.Connection == ConnectionType.Close)
FullRequestProcessed = true;
}
@@ -657,46 +717,56 @@ namespace HttpServer
public bool Send(byte[] buffer, int offset, int size)
{
// add some trivial checks required by opensim until another fix is possible
// this are needed because opensim doesn't have access to stream state
// and in its current state it will try to send to closed streams.
if (Stream == null || _sock == null || !_sock.Connected)
if (Stream == null || m_sock == null || !m_sock.Connected)
return false;
if (offset + size > buffer.Length)
throw new ArgumentOutOfRangeException("offset", offset, "offset + size is beyond end of buffer.");
bool ok = true;
lock (sendLock) // can't have overlaps here
{
bool ok = true;
if (offset + size > buffer.Length)
throw new ArgumentOutOfRangeException("offset", offset, "offset + size is beyond end of buffer.");
try
{
// we are supposed to block so do block
if (_sock.Poll(30000000, SelectMode.SelectWrite) && Stream != null && _sock != null)
Stream.Write(buffer, offset, size);
else
ok = false;
Stream.Write(buffer, offset, size);
}
// catch(IOException e)
catch
{
// code to handle recoverable errors
//var socketExept = e.InnerException as SocketException;
//if (socketExept != null)
//{
//var errcode = socketExept.ErrorCode;
//}
ok = false;
// throw e; // let it still be visible
}
if (!ok && Stream != null)
Disconnect(SocketError.NoRecovery);
return ok;
}
}
public async Task<bool> SendAsync(byte[] buffer, int offset, int size)
{
if (Stream == null || m_sock == null || !m_sock.Connected)
return false;
if (offset + size > buffer.Length)
throw new ArgumentOutOfRangeException("offset", offset, "offset + size is beyond end of buffer.");
bool ok = true;
ContextTimeoutManager.ContextEnterActiveSend();
try
{
await Stream.WriteAsync(buffer, offset, size).ConfigureAwait(false);
}
catch
{
ok = false;
}
ContextTimeoutManager.ContextLeaveActiveSend();
if (!ok && Stream != null)
Disconnect(SocketError.NoRecovery);
return ok;
}
/// <summary>
/// The context have been disconnected.
/// </summary>
@@ -712,13 +782,13 @@ namespace HttpServer
public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing()
{
StreamPassedOff = true;
_parser.RequestCompleted -= OnRequestCompleted;
_parser.RequestLineReceived -= OnRequestLine;
_parser.HeaderReceived -= OnHeaderReceived;
_parser.BodyBytesReceived -= OnBodyBytesReceived;
_parser.Clear();
m_parser.RequestCompleted -= OnRequestCompleted;
m_parser.RequestLineReceived -= OnRequestLine;
m_parser.HeaderReceived -= OnHeaderReceived;
m_parser.BodyBytesReceived -= OnBodyBytesReceived;
m_parser.Clear();
return new HTTPNetworkContext() { Socket = _sock, Stream = _stream as NetworkStream };
return new HTTPNetworkContext() { Socket = m_sock, Stream = _stream as NetworkStream };
}
public void Dispose()
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.IO;
using System.Net;
using System.Net.Security;
@@ -14,8 +15,7 @@ namespace HttpServer
/// </summary>
public class HttpContextFactory : IHttpContextFactory
{
private readonly int _bufferSize;
private readonly Dictionary<int, HttpClientContext> _activeContexts = new Dictionary<int, HttpClientContext>();
private readonly ConcurrentDictionary<int, HttpClientContext> m_activeContexts = new ConcurrentDictionary<int, HttpClientContext>();
private readonly IRequestParserFactory _factory;
private readonly ILogWriter _logWriter;
@@ -25,12 +25,11 @@ namespace HttpServer
/// <param name="writer">The writer.</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>
public HttpContextFactory(ILogWriter writer, int bufferSize, IRequestParserFactory factory)
public HttpContextFactory(ILogWriter writer, IRequestParserFactory factory)
{
_logWriter = writer;
_bufferSize = bufferSize;
_factory = factory;
ContextTimeoutManager.StartMonitoring();
ContextTimeoutManager.Start();
}
///<summary>
@@ -58,8 +57,7 @@ namespace HttpServer
context.RemotePort = endPoint.Port.ToString();
context.RemoteAddress = endPoint.Address.ToString();
ContextTimeoutManager.StartMonitoringContext(context);
lock (_activeContexts)
_activeContexts[context.contextID] = context;
m_activeContexts[context.contextID] = context;
context.Start();
return context;
}
@@ -73,7 +71,7 @@ namespace HttpServer
/// <returns>A new context (always).</returns>
protected virtual HttpClientContext CreateNewContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock)
{
return new HttpClientContext(isSecured, endPoint, stream, _factory, _bufferSize, sock);
return new HttpClientContext(isSecured, endPoint, stream, _factory, sock);
}
private void OnRequestReceived(object sender, RequestEventArgs e)
@@ -83,15 +81,11 @@ namespace HttpServer
private void OnFreeContext(object sender, DisconnectedEventArgs e)
{
var imp = (HttpClientContext)sender;
if (imp.contextID < 0)
var imp = sender as HttpClientContext;
if (imp == null || imp.contextID < 0)
return;
lock (_activeContexts)
{
if (_activeContexts.ContainsKey(imp.contextID))
_activeContexts.Remove(imp.contextID);
}
m_activeContexts.TryRemove(imp.contextID, out HttpClientContext dummy);
imp.Close();
}
@@ -169,7 +163,7 @@ namespace HttpServer
/// </summary>
public void Shutdown()
{
ContextTimeoutManager.StopMonitoring();
ContextTimeoutManager.Stop();
}
}
+3 -3
View File
@@ -67,7 +67,7 @@ namespace HttpServer
public static HttpListener Create(IPAddress address, int port)
{
RequestParserFactory requestFactory = new RequestParserFactory();
HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, 16384, requestFactory);
HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, requestFactory);
return new HttpListener(address, port, factory);
}
@@ -81,7 +81,7 @@ namespace HttpServer
public static HttpListener Create(IPAddress address, int port, X509Certificate certificate)
{
RequestParserFactory requestFactory = new RequestParserFactory();
HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, 16384, requestFactory);
HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, requestFactory);
return new HttpListener(address, port, factory, certificate);
}
@@ -96,7 +96,7 @@ namespace HttpServer
public static HttpListener Create(IPAddress address, int port, X509Certificate certificate, SslProtocols protocol)
{
RequestParserFactory requestFactory = new RequestParserFactory();
HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, 16384, requestFactory);
HttpContextFactory factory = new HttpContextFactory(NullLogWriter.Instance, requestFactory);
return new HttpListener(address, port, factory, certificate, protocol);
}
+23 -23
View File
@@ -15,7 +15,7 @@ namespace HttpServer
/// <summary>
/// Chars used to split an URL path into multiple parts.
/// </summary>
public static readonly char[] UriSplitters = new[] {'/'};
public static readonly char[] UriSplitters = new[] { '/' };
public static uint baseID = 0;
private readonly NameValueCollection _headers = new NameValueCollection();
@@ -29,8 +29,8 @@ namespace HttpServer
private string _method = string.Empty;
private HttpInput _queryString = HttpInput.Empty;
private Uri _uri = HttpHelper.EmptyUri;
private string _uriPath;
public readonly IHttpClientContext _context;
private string _uriPath;
public readonly IHttpClientContext _context;
public HttpRequest(IHttpClientContext pContext)
{
@@ -38,14 +38,14 @@ namespace HttpServer
_context = pContext;
}
public uint ID {get; private set;}
public uint ID { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="HttpRequest"/> is secure.
/// </summary>
public bool Secure { get; internal set; }
public IHttpClientContext Context { get { return _context; }}
public IHttpClientContext Context { get { return _context; } }
/// <summary>
/// Path and query (will be merged with the host header) and put in Uri
/// </summary>
@@ -61,15 +61,15 @@ namespace HttpServer
{
_queryString = HttpHelper.ParseQueryString(_uriPath.Substring(pos + 1));
_param.SetQueryString(_queryString);
string path = _uriPath.Substring(0, pos);
_uriPath = System.Web.HttpUtility.UrlDecode(path) + "?" + _uriPath.Substring(pos + 1);
string path = _uriPath.Substring(0, pos);
_uriPath = System.Web.HttpUtility.UrlDecode(path) + "?" + _uriPath.Substring(pos + 1);
UriParts = value.Substring(0, pos).Split(UriSplitters, StringSplitOptions.RemoveEmptyEntries);
}
else
{
_uriPath = System.Web.HttpUtility.UrlDecode(_uriPath);
UriParts = value.Split(UriSplitters, StringSplitOptions.RemoveEmptyEntries);
}
_uriPath = System.Web.HttpUtility.UrlDecode(_uriPath);
UriParts = value.Split(UriSplitters, StringSplitOptions.RemoveEmptyEntries);
}
}
}
@@ -257,7 +257,7 @@ namespace HttpServer
request.Uri = _uri;
var buffer = new byte[_body.Length];
_body.Read(buffer, 0, (int) _body.Length);
_body.Read(buffer, 0, (int)_body.Length);
request.Body = new MemoryStream();
request.Body.Write(buffer, 0, buffer.Length);
request.Body.Seek(0, SeekOrigin.Begin);
@@ -299,16 +299,16 @@ namespace HttpServer
Cookies = cookies;
}
/// <summary>
/// Create a response object.
/// </summary>
/// <returns>A new <see cref="IHttpResponse"/>.</returns>
public IHttpResponse CreateResponse(IHttpClientContext context)
{
return new HttpResponse(context, this);
}
/// <summary>
/// Create a response object.
/// </summary>
/// <returns>A new <see cref="IHttpResponse"/>.</returns>
public IHttpResponse CreateResponse(IHttpClientContext context)
{
return new HttpResponse(context, this);
}
/// <summary>
/// <summary>
/// Called during parsing of a <see cref="IHttpRequest"/>.
/// </summary>
/// <param name="name">Name of the header, should not be URL encoded</param>
@@ -372,7 +372,7 @@ namespace HttpServer
case "expect":
if (value.Contains("100-continue"))
{
}
_headers.Add(name, value);
break;
@@ -420,7 +420,7 @@ namespace HttpServer
/// </summary>
public void Clear()
{
if(_body != null && _body.CanRead)
if (_body != null && _body.CanRead)
_body.Dispose();
_body = null;
_contentLength = 0;
@@ -432,7 +432,7 @@ namespace HttpServer
_connection = ConnectionType.KeepAlive;
IsAjax = false;
_form.Clear();
}
}
#endregion
}
+301 -111
View File
@@ -4,54 +4,20 @@ using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace HttpServer
{
/// <summary>
/// Response that is sent back to the web browser / client.
/// </summary>
/// <remarks>
/// <para>
/// A response can be sent if different ways. The easiest one is
/// to just fill the Body stream with content, everything else
/// will then be taken care of by the framework. The default content-type
/// is text/html, you should change it if you send anything else.
/// </para><para>
/// The second and slightly more complex way is to send the response
/// as parts. Start with sending the header using the SendHeaders method and
/// then you can send the body using SendBody method, but do not forget
/// to set <see cref="ContentType"/> and <see cref="ContentLength"/> before doing so.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// // Example using response body.
/// class MyModule : HttpModule
/// {
/// public override bool Process(IHttpRequest request, IHttpResponse response, IHttpSession session)
/// {
/// StreamWriter writer = new StreamWriter(response.Body);
/// writer.WriteLine("Hello dear World!");
/// writer.Flush();
///
/// // return true to tell webserver that we've handled the url
/// return true;
/// }
/// }
/// </code>
/// </example>
/// todo: add two examples, using SendHeaders/SendBody and just the Body stream.
public class HttpResponse : IHttpResponse
{
private const string DefaultContentType = "text/html;charset=UTF-8";
private readonly IHttpClientContext _context;
private readonly IHttpClientContext m_context;
private readonly ResponseCookies _cookies = new ResponseCookies();
private readonly NameValueCollection _headers = new NameValueCollection();
private readonly NameValueCollection m_headers = new NameValueCollection();
private string _httpVersion;
private Stream _body = new MemoryStream();
private Stream _body;
private long _contentLength;
private string _contentType;
private bool _contentTypeChangedByCode;
private Encoding _encoding = Encoding.UTF8;
private int _keepAlive = 60;
public uint requestID { get; private set; }
@@ -59,6 +25,8 @@ namespace HttpServer
public int RawBufferStart { get; set; }
public int RawBufferLen { get; set; }
internal byte[] m_headerBytes = null;
/// <summary>
/// Initializes a new instance of the <see cref="IHttpResponse"/> class.
/// </summary>
@@ -72,10 +40,10 @@ namespace HttpServer
_httpVersion = request.HttpVersion;
if (string.IsNullOrEmpty(_httpVersion))
throw new ArgumentException("HttpVersion in IHttpRequest cannot be empty.");
_httpVersion = "HTTP/1.0";
Status = HttpStatusCode.OK;
_context = context;
m_context = context;
m_Connetion = request.Connection;
requestID = request.ID;
RawBufferStart = -1;
@@ -93,7 +61,7 @@ namespace HttpServer
Check.NotEmpty(httpVersion, "httpVersion");
Status = HttpStatusCode.OK;
_context = context;
m_context = context;
_httpVersion = httpVersion;
m_Connetion = connectionType;
}
@@ -104,10 +72,11 @@ namespace HttpServer
set { return; }
}
internal bool ContentTypeChangedByCode
private int m_priority = 0;
public int Priority
{
get { return _contentTypeChangedByCode; }
set { _contentTypeChangedByCode = value; }
get { return m_priority;}
set { m_priority = (value > 0 && m_priority < 3)? value : 0;}
}
#region IHttpResponse Members
@@ -119,7 +88,12 @@ namespace HttpServer
/// </summary>
public Stream Body
{
get { return _body; }
get
{
if(_body == null)
_body = new MemoryStream();
return _body;
}
set { _body = value; }
}
@@ -201,11 +175,7 @@ namespace HttpServer
public string ContentType
{
get { return _contentType; }
set
{
_contentType = value;
_contentTypeChangedByCode = true;
}
set { _contentType = value; }
}
/// <summary>
@@ -248,35 +218,35 @@ namespace HttpServer
throw new ArgumentException("Invalid new line sequence, should be \\r\\n (crlf).");
}
_headers[name] = value;
m_headers[name] = value;
}
/// <summary>
/// Send headers and body to the browser.
/// </summary>
/// <exception cref="InvalidOperationException">If content have already been sent.</exception>
public void Send()
public void SendOri()
{
if (Sent)
throw new InvalidOperationException("Everything have already been sent.");
_context.ReqResponseAboutToSend(requestID);
if (_context.MAXRequests == 0 || _keepAlive == 0)
m_context.ReqResponseAboutToSend(requestID);
if (m_context.MAXRequests == 0 || _keepAlive == 0)
{
Connection = ConnectionType.Close;
_context.TimeoutKeepAlive = 0;
m_context.TimeoutKeepAlive = 0;
}
else
{
if (_keepAlive > 0)
_context.TimeoutKeepAlive = _keepAlive * 1000;
m_context.TimeoutKeepAlive = _keepAlive * 1000;
}
if (!HeadersSent)
{
if (!SendHeaders())
{
Body.Dispose();
_body.Dispose();
Sent = true;
return;
}
@@ -292,6 +262,7 @@ namespace HttpServer
if (RawBufferLen + RawBufferStart > RawBuffer.Length)
RawBufferLen = RawBuffer.Length - RawBufferStart;
/*
int curlen;
while(RawBufferLen > 0)
{
@@ -309,6 +280,20 @@ namespace HttpServer
RawBufferLen -= curlen;
RawBufferStart += curlen;
}
*/
if(RawBufferLen > 0)
{
if (!m_context.Send(RawBuffer, RawBufferStart, RawBufferLen))
{
RawBuffer = null;
RawBufferStart = -1;
RawBufferLen = -1;
if(_body != null)
_body.Dispose();
Sent = true;
return;
}
}
}
RawBuffer = null;
@@ -316,35 +301,27 @@ namespace HttpServer
RawBufferLen = -1;
}
if (Body.Length == 0)
if(_body != null && _body.Length > 0)
{
Body.Dispose();
Sent = true;
_context.ReqResponseSent(requestID, Connection);
return;
}
_body.Flush();
_body.Seek(0, SeekOrigin.Begin);
Body.Flush();
Body.Seek(0, SeekOrigin.Begin);
var buffer = new byte[8192];
int bytesRead = Body.Read(buffer, 0, 8192);
while (bytesRead > 0)
{
if (!_context.Send(buffer, 0, bytesRead))
var buffer = new byte[8192];
int bytesRead = _body.Read(buffer, 0, 8192);
while (bytesRead > 0)
{
Body.Dispose();
return;
if (!m_context.Send(buffer, 0, bytesRead))
break;
bytesRead = _body.Read(buffer, 0, 8192);
}
bytesRead = Body.Read(buffer, 0, 8192);
}
Body.Dispose();
_body.Dispose();
}
Sent = true;
_context.ReqResponseSent(requestID, Connection);
m_context.ReqResponseSent(requestID, Connection);
}
/// <summary>
/// Make sure that you have specified <see cref="ContentLength"/> and sent the headers first.
/// </summary>
@@ -362,10 +339,10 @@ namespace HttpServer
if (!HeadersSent)
throw new InvalidOperationException("Send headers, and remember to specify ContentLength first.");
bool sent = _context.Send(buffer, offset, count);
bool sent = m_context.Send(buffer, offset, count);
Sent = true;
if (sent)
_context.ReqResponseSent(requestID, Connection);
m_context.ReqResponseSent(requestID, Connection);
return sent;
}
@@ -384,9 +361,9 @@ namespace HttpServer
if (!HeadersSent)
throw new InvalidOperationException("Send headers, and remember to specify ContentLength first.");
bool sent = _context.Send(buffer);
bool sent = m_context.Send(buffer);
if (sent)
_context.ReqResponseSent(requestID, Connection);
m_context.ReqResponseSent(requestID, Connection);
Sent = true;
return sent;
}
@@ -405,32 +382,42 @@ namespace HttpServer
HeadersSent = true;
if (_headers["Date"] == null)
_headers["Date"] = DateTime.Now.ToString("r");
if (_headers["Content-Length"] == null)
_headers["Content-Length"] = _contentLength == 0 ? Body.Length.ToString() : _contentLength.ToString();
if (_headers["Content-Type"] == null)
_headers["Content-Type"] = _contentType ?? DefaultContentType;
if (_headers["Server"] == null)
_headers["Server"] = "Tiny WebServer";
int keepaliveS = _context.TimeoutKeepAlive / 1000;
if (Connection == ConnectionType.KeepAlive && keepaliveS > 0 && _context.MAXRequests > 0)
if (m_headers["Date"] == null)
m_headers["Date"] = DateTime.Now.ToString("r");
if (m_headers["Content-Length"] == null)
{
_headers["Keep-Alive"] = "timeout=" + keepaliveS + ", max=" + _context.MAXRequests;
_headers["Connection"] = "Keep-Alive";
int len = (int)_contentLength;
if(len == 0)
{
if(_body != null)
len = (int)_body.Length;
if(RawBuffer != null)
len += RawBufferLen;
}
m_headers["Content-Length"] = len.ToString();
}
if (m_headers["Content-Type"] == null)
m_headers["Content-Type"] = _contentType ?? DefaultContentType;
if (m_headers["Server"] == null)
m_headers["Server"] = "Tiny WebServer";
int keepaliveS = m_context.TimeoutKeepAlive / 1000;
if (Connection == ConnectionType.KeepAlive && keepaliveS > 0 && m_context.MAXRequests > 0)
{
m_headers["Keep-Alive"] = "timeout=" + keepaliveS + ", max=" + m_context.MAXRequests;
m_headers["Connection"] = "Keep-Alive";
}
else
_headers["Connection"] = "close";
m_headers["Connection"] = "close";
var sb = new StringBuilder();
sb.AppendFormat("{0} {1} {2}\r\n", _httpVersion, (int)Status,
string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
for (int i = 0; i < _headers.Count; ++i)
for (int i = 0; i < m_headers.Count; ++i)
{
string headerName = _headers.AllKeys[i];
string[] values = _headers.GetValues(i);
string headerName = m_headers.AllKeys[i];
string[] values = m_headers.GetValues(i);
if (values == null) continue;
foreach (string value in values)
sb.AppendFormat("{0}: {1}\r\n", headerName, value);
@@ -441,9 +428,215 @@ namespace HttpServer
sb.Append("\r\n");
_headers.Clear();
m_headers.Clear();
return _context.Send(Encoding.GetBytes(sb.ToString()));
return m_context.Send(Encoding.GetBytes(sb.ToString()));
}
public byte[] GetHeaders()
{
HeadersSent = true;
var sb = new StringBuilder();
if(string.IsNullOrWhiteSpace(_httpVersion))
sb.AppendFormat("HTTP1/0 {0} {1}\r\n", (int)Status,
string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
else
sb.AppendFormat("{0} {1} {2}\r\n", _httpVersion, (int)Status,
string.IsNullOrEmpty(Reason) ? Status.ToString() : Reason);
if (m_headers["Date"] == null)
sb.AppendFormat("Date: {0}\r\n", DateTime.Now.ToString("r"));
if (m_headers["Content-Length"] == null)
{
long len = _contentLength;
if (len == 0)
{
len = Body.Length;
if (RawBuffer != null && RawBufferLen > 0)
len += RawBufferLen;
}
sb.AppendFormat("Content-Length: {0}\r\n", len);
}
if (m_headers["Content-Type"] == null)
sb.AppendFormat("Content-Type: {0}\r\n", _contentType ?? DefaultContentType);
if (m_headers["Server"] == null)
sb.Append("Server: OSWebServer\r\n");
int keepaliveS = m_context.TimeoutKeepAlive / 1000;
if (Connection == ConnectionType.KeepAlive && keepaliveS > 0 && m_context.MAXRequests > 0)
{
sb.AppendFormat("Keep-Alive:timeout={0}, max={1}\r\n", keepaliveS, m_context.MAXRequests);
sb.Append("Connection: Keep-Alive\r\n");
}
else
sb.Append("Connection: close\r\n");
if (m_headers["Connection"] != null)
m_headers["Connection"] = null;
if (m_headers["Keep-Alive"] != null)
m_headers["Keep-Alive"] = null;
for (int i = 0; i < m_headers.Count; ++i)
{
string headerName = m_headers.AllKeys[i];
string[] values = m_headers.GetValues(i);
if (values == null) continue;
foreach (string value in values)
sb.AppendFormat("{0}: {1}\r\n", headerName, value);
}
foreach (ResponseCookie cookie in Cookies)
sb.AppendFormat("Set-Cookie: {0}\r\n", cookie);
sb.Append("\r\n");
m_headers.Clear();
return Encoding.GetBytes(sb.ToString());
}
public void Send()
{
if (Sent)
throw new InvalidOperationException("Everything have already been sent.");
if (m_context.MAXRequests == 0 || _keepAlive == 0)
{
Connection = ConnectionType.Close;
m_context.TimeoutKeepAlive = 0;
}
else
{
if (_keepAlive > 0)
m_context.TimeoutKeepAlive = _keepAlive * 1000;
}
m_headerBytes = GetHeaders();
if (RawBuffer != null)
{
if (RawBufferStart < 0 || RawBufferStart > RawBuffer.Length)
return;
if (RawBufferLen < 0)
RawBufferLen = RawBuffer.Length;
if (RawBufferLen + RawBufferStart > RawBuffer.Length)
RawBufferLen = RawBuffer.Length - RawBufferStart;
int tlen = m_headerBytes.Length + RawBufferLen;
if(RawBufferLen > 0 && tlen < 16384)
{
byte[] tmp = new byte[tlen];
Array.Copy(m_headerBytes, tmp, m_headerBytes.Length);
Array.Copy(RawBuffer, RawBufferStart, tmp, m_headerBytes.Length, RawBufferLen);
m_headerBytes = null;
RawBuffer = tmp;
RawBufferStart = 0;
RawBufferLen = tlen;
}
}
m_context.StartSendResponse(this);
}
public async Task SendNextAsync(int bytesLimit)
{
if (m_headerBytes != null)
{
if(!await m_context.SendAsync(m_headerBytes, 0, m_headerBytes.Length).ConfigureAwait(false))
{
if(_body != null)
_body.Dispose();
RawBuffer = null;
Sent = true;
return;
}
bytesLimit -= m_headerBytes.Length;
m_headerBytes = null;
if(bytesLimit <= 0)
{
m_context.ContinueSendResponse();
return;
}
}
if (RawBuffer != null)
{
if (RawBufferLen > 0)
{
bool sendRes;
if(RawBufferLen > bytesLimit)
{
sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, bytesLimit).ConfigureAwait(false);
RawBufferLen -= bytesLimit;
RawBufferStart += bytesLimit;
}
else
{
sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, RawBufferLen).ConfigureAwait(false);
RawBufferLen = 0;
}
if (!sendRes)
{
RawBuffer = null;
if(_body != null)
Body.Dispose();
Sent = true;
return;
}
}
if (RawBufferLen <= 0)
RawBuffer = null;
else
{
m_context.ContinueSendResponse();
return;
}
}
if (_body != null && _body.Length != 0)
{
_body.Flush();
_body.Seek(0, SeekOrigin.Begin);
RawBuffer = new byte[_body.Length];
RawBufferLen = _body.Read(RawBuffer, 0, (int)_body.Length);
_body.Dispose();
if(RawBufferLen > 0)
{
bool sendRes;
if (RawBufferLen > bytesLimit)
{
sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, bytesLimit).ConfigureAwait(false);
RawBufferLen -= bytesLimit;
RawBufferStart += bytesLimit;
}
else
{
sendRes = await m_context.SendAsync(RawBuffer, RawBufferStart, RawBufferLen).ConfigureAwait(false);
RawBufferLen = 0;
}
if (!sendRes)
{
RawBuffer = null;
Sent = true;
return;
}
}
if (RawBufferLen > 0)
{
m_context.ContinueSendResponse();
return;
}
}
if (_body != null)
_body.Dispose();
Sent = true;
m_context.ReqResponseSent(requestID, Connection);
}
/// <summary>
@@ -456,7 +649,7 @@ namespace HttpServer
public void Redirect(Uri uri)
{
Status = HttpStatusCode.Redirect;
_headers["location"] = uri.ToString();
m_headers["location"] = uri.ToString();
}
/// <summary>
@@ -469,17 +662,14 @@ namespace HttpServer
public void Redirect(string url)
{
Status = HttpStatusCode.Redirect;
_headers["location"] = url;
m_headers["location"] = url;
}
public void Clear()
{
if(Body != null && Body.CanRead)
Body.Dispose();
}
#endregion
/*
* HTTP/1.1 200 OK
Date: Sun, 16 Mar 2008 08:01:36 GMT
Server: Apache/2.2.6 (Win32) PHP/5.2.4
Content-Length: 685
Connection: close
Content-Type: text/html;charset=UTF-8*/
}
}
+1 -3
View File
@@ -461,9 +461,7 @@ namespace HttpServer
if (!_components.Contains(typeof(IRequestParserFactory)))
_components.Add<IRequestParserFactory, RequestParserFactory>();
if (!_components.Contains(typeof(IHttpContextFactory)))
_components.AddInstance<IHttpContextFactory>(new HttpContextFactory(LogWriter, 16384,
_components.Get
<IRequestParserFactory>()));
_components.AddInstance<IHttpContextFactory>(new HttpContextFactory(LogWriter, _components.Get<IRequestParserFactory>()));
// the special folder does not exist on mono
string tempPath = Environment.GetFolderPath(Environment.SpecialFolder.InternetCache);
@@ -120,7 +120,6 @@
<Compile Include="IHttpRequestParser.cs" />
<Compile Include="IHttpResponse.cs" />
<Compile Include="ILogWriter.cs" />
<Compile Include="LocklessQueue.cs" />
<Compile Include="Method.cs" />
<Compile Include="Parser\BodyEventArgs.cs" />
<Compile Include="Parser\HeaderEventArgs.cs" />
@@ -1,6 +1,7 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace HttpServer
{
@@ -30,7 +31,6 @@ namespace HttpServer
int TimeoutKeepAlive {get; set; }
int MAXRequests{get; set; }
int SendBufferSize(int newSize);
bool CanSend();
bool IsSending();
@@ -81,6 +81,7 @@ namespace HttpServer
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
bool Send(byte[] buffer, int offset, int size);
Task<bool> SendAsync(byte[] buffer, int offset, int size);
/// <summary>
/// Closes the streams and disposes of the unmanaged resources
@@ -102,9 +103,11 @@ namespace HttpServer
HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing();
void StartSendResponse(HttpResponse response);
void ContinueSendResponse();
void ReqResponseAboutToSend(uint requestID);
void ReqResponseSent(uint requestID, ConnectionType connection);
bool TrySendResponse(int limit);
}
public class HTTPNetworkContext
{
+137 -154
View File
@@ -5,179 +5,162 @@ using System.Text;
namespace HttpServer
{
/// <summary>
/// Response that is sent back to the web browser / client.
///
/// A response can be sent if different ways. The easiest one is
/// to just fill the Body stream with content, everything else
/// will then be taken care of by the framework. The default content-type
/// is text/html, you should change it if you send anything else.
///
/// The second and slighty more complex way is to send the response
/// as parts. Start with sending the header using the SendHeaders method and
/// then you can send the body using SendBody method, but do not forget
/// to set ContentType and ContentLength before doing so.
/// </summary>
/// <example>
/// public void MyHandler(IHttpRequest request, IHttpResponse response)
/// {
///
/// }
/// </example>
public interface IHttpResponse
{
/// <summary>
/// Response that is sent back to the web browser / client.
///
/// A response can be sent if different ways. The easiest one is
/// to just fill the Body stream with content, everything else
/// will then be taken care of by the framework. The default content-type
/// is text/html, you should change it if you send anything else.
///
/// The second and slighty more complex way is to send the response
/// as parts. Start with sending the header using the SendHeaders method and
/// then you can send the body using SendBody method, but do not forget
/// to set ContentType and ContentLength before doing so.
/// The body stream is used to cache the body contents
/// before sending everything to the client. It's the simplest
/// way to serve documents.
/// </summary>
/// <example>
/// public void MyHandler(IHttpRequest request, IHttpResponse response)
/// {
///
/// }
/// </example>
public interface IHttpResponse
{
/// <summary>
/// The body stream is used to cache the body contents
/// before sending everything to the client. It's the simplest
/// way to serve documents.
/// </summary>
Stream Body { get; set; }
byte[] RawBuffer { get; set; }
int RawBufferStart { get; set; }
int RawBufferLen { get; set; }
uint requestID {get;}
Stream Body { get; set; }
byte[] RawBuffer { get; set; }
int RawBufferStart { get; set; }
int RawBufferLen { get; set; }
uint requestID { get; }
/// <summary>
/// Defines the version of the HTTP Response for applications where it's required
/// for this to be forced.
/// </summary>
string ProtocolVersion { get; set; }
/// <summary>
/// Defines the version of the HTTP Response for applications where it's required
/// for this to be forced.
/// </summary>
string ProtocolVersion { get; set; }
int Priority { get; set; }
/// <summary>
/// The chunked encoding modifies the body of a message in order to
/// transfer it as a series of chunks, each with its own size indicator,
/// followed by an OPTIONAL trailer containing entity-header fields. This
/// allows dynamically produced content to be transferred along with the
/// information necessary for the recipient to verify that it has
/// received the full message.
/// </summary>
bool Chunked { get; set; }
/// <summary>
/// The chunked encoding modifies the body of a message in order to
/// transfer it as a series of chunks, each with its own size indicator,
/// followed by an OPTIONAL trailer containing entity-header fields. This
/// allows dynamically produced content to be transferred along with the
/// information necessary for the recipient to verify that it has
/// received the full message.
/// </summary>
bool Chunked { get; set; }
/// <summary>
/// Kind of connection
/// </summary>
ConnectionType Connection { get; set; }
/// <summary>
/// Kind of connection
/// </summary>
ConnectionType Connection { get; set; }
/// <summary>
/// Encoding to use when sending stuff to the client.
/// </summary>
/// <remarks>Default is UTF8</remarks>
Encoding Encoding { get; set; }
/// <summary>
/// Encoding to use when sending stuff to the client.
/// </summary>
/// <remarks>Default is UTF8</remarks>
Encoding Encoding { get; set; }
/// <summary>
/// Number of seconds to keep connection alive
/// </summary>
/// <remarks>Only used if Connection property is set to ConnectionType.KeepAlive</remarks>
int KeepAlive { get; set; }
/// <summary>
/// Number of seconds to keep connection alive
/// </summary>
/// <remarks>Only used if Connection property is set to ConnectionType.KeepAlive</remarks>
int KeepAlive { get; set; }
/// <summary>
/// Status code that is sent to the client.
/// </summary>
/// <remarks>Default is HttpStatusCode.Ok</remarks>
HttpStatusCode Status { get; set; }
/// <summary>
/// Status code that is sent to the client.
/// </summary>
/// <remarks>Default is HttpStatusCode.Ok</remarks>
HttpStatusCode Status { get; set; }
/// <summary>
/// Information about why a specific status code was used.
/// </summary>
string Reason { get; set; }
/// <summary>
/// Information about why a specific status code was used.
/// </summary>
string Reason { get; set; }
/// <summary>
/// Size of the body. MUST be specified before sending the header,
/// unless property Chunked is set to true.
/// </summary>
long ContentLength { get; set; }
/// <summary>
/// Size of the body. MUST be specified before sending the header,
/// unless property Chunked is set to true.
/// </summary>
long ContentLength { get; set; }
/// <summary>
/// Kind of content in the body
/// </summary>
/// <remarks>Default is text/html</remarks>
string ContentType { get; set; }
/// <summary>
/// Kind of content in the body
/// </summary>
/// <remarks>Default is text/html</remarks>
string ContentType { get; set; }
/// <summary>
/// Headers have been sent to the client-
/// </summary>
/// <remarks>You can not send any additional headers if they have already been sent.</remarks>
bool HeadersSent { get; }
/// <summary>
/// Headers have been sent to the client-
/// </summary>
/// <remarks>You can not send any additional headers if they have already been sent.</remarks>
bool HeadersSent { get; }
/// <summary>
/// The whole response have been sent.
/// </summary>
bool Sent { get; }
/// <summary>
/// The whole response have been sent.
/// </summary>
bool Sent { get; }
/// <summary>
/// Cookies that should be created/changed.
/// </summary>
ResponseCookies Cookies { get; }
/// <summary>
/// Cookies that should be created/changed.
/// </summary>
ResponseCookies Cookies { get; }
/// <summary>
/// Add another header to the document.
/// </summary>
/// <param name="name">Name of the header, case sensitive, use lower cases.</param>
/// <param name="value">Header values can span over multiple lines as long as each line starts with a white space. New line chars should be \r\n</param>
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
/// <exception cref="ArgumentException">If value conditions have not been met.</exception>
/// <remarks>Adding any header will override the default ones and those specified by properties.</remarks>
void AddHeader(string name, string value);
/// <summary>
/// Add another header to the document.
/// </summary>
/// <param name="name">Name of the header, case sensitive, use lower cases.</param>
/// <param name="value">Header values can span over multiple lines as long as each line starts with a white space. New line chars should be \r\n</param>
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
/// <exception cref="ArgumentException">If value conditions have not been met.</exception>
/// <remarks>Adding any header will override the default ones and those specified by properties.</remarks>
void AddHeader(string name, string value);
/// <summary>
/// Send headers and body to the browser.
/// </summary>
/// <exception cref="InvalidOperationException">If content have already been sent.</exception>
void Send();
/// <summary>
/// Send headers and body to the browser.
/// </summary>
/// <exception cref="InvalidOperationException">If content have already been sent.</exception>
void Send();
/// <summary>
/// Make sure that you have specified ContentLength and sent the headers first.
/// </summary>
/// <param name="buffer"></param>
/// <exception cref="InvalidOperationException">If headers have not been sent.</exception>
/// <see cref="IHttpResponse.SendHeaders"/>
/// <param name="offset">offest of first byte to send</param>
/// <param name="count">number of bytes to send.</param>
/// <seealso cref="IHttpResponse.Send"/>
/// <seealso cref="IHttpResponse.SendHeaders"/>
/// <remarks>This method can be used if you want to send body contents without caching them first. This
/// is recommended for larger files to keep the memory usage low.</remarks>
bool SendBody(byte[] buffer, int offset, int count);
/// <summary>
/// Make sure that you have specified ContentLength and sent the headers first.
/// </summary>
/// <param name="buffer"></param>
/// <exception cref="InvalidOperationException">If headers have not been sent.</exception>
/// <see cref="IHttpResponse.SendHeaders"/>
/// <param name="offset">offest of first byte to send</param>
/// <param name="count">number of bytes to send.</param>
/// <seealso cref="IHttpResponse.Send"/>
/// <seealso cref="IHttpResponse.SendHeaders"/>
/// <remarks>This method can be used if you want to send body contents without caching them first. This
/// is recommended for larger files to keep the memory usage low.</remarks>
bool SendBody(byte[] buffer, int offset, int count);
/// <summary>
/// Make sure that you have specified ContentLength and sent the headers first.
/// </summary>
/// <param name="buffer"></param>
/// <exception cref="InvalidOperationException">If headers have not been sent.</exception>
/// <see cref="IHttpResponse.SendHeaders"/>
/// <seealso cref="IHttpResponse.Send"/>
/// <seealso cref="IHttpResponse.SendHeaders"/>
/// <remarks>This method can be used if you want to send body contents without caching them first. This
/// is recommended for larger files to keep the memory usage low.</remarks>
bool SendBody(byte[] buffer);
/// <summary>
/// Make sure that you have specified ContentLength and sent the headers first.
/// </summary>
/// <param name="buffer"></param>
/// <exception cref="InvalidOperationException">If headers have not been sent.</exception>
/// <see cref="IHttpResponse.SendHeaders"/>
/// <seealso cref="IHttpResponse.Send"/>
/// <seealso cref="IHttpResponse.SendHeaders"/>
/// <remarks>This method can be used if you want to send body contents without caching them first. This
/// is recommended for larger files to keep the memory usage low.</remarks>
bool SendBody(byte[] buffer);
/// <summary>
/// Send headers to the client.
/// </summary>
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
/// <seealso cref="IHttpResponse.AddHeader"/>
/// <seealso cref="IHttpResponse.Send"/>
/// <seealso cref="IHttpResponse.SendBody(byte[])"/>
bool SendHeaders();
/// <summary>
/// Redirect client to somewhere else using the 302 status code.
/// </summary>
/// <param name="uri">Destination of the redirect</param>
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
/// <remarks>You can not do anything more with the request when a redirect have been done. This should be your last
/// action.</remarks>
void Redirect(Uri uri);
/// <summary>
/// redirect to somewhere
/// </summary>
/// <param name="url">where the redirect should go</param>
/// <remarks>
/// No body are allowed when doing redirects.
/// </remarks>
void Redirect(string url);
}
/// <summary>
/// Send headers to the client.
/// </summary>
/// <exception cref="InvalidOperationException">If headers already been sent.</exception>
/// <seealso cref="IHttpResponse.AddHeader"/>
/// <seealso cref="IHttpResponse.Send"/>
/// <seealso cref="IHttpResponse.SendBody(byte[])"/>
bool SendHeaders();
}
/// <summary>
/// Type of HTTP connection
@@ -1,139 +0,0 @@
/*
* Copyright (c) 2009, openmetaverse.org
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.org nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Threading;
namespace HttpServer
{
public class LocklessQueue<T>
{
private sealed class SingleLinkNode
{
public SingleLinkNode Next;
public T Item;
}
SingleLinkNode head;
SingleLinkNode tail;
int count;
public virtual int Count { get { return count; } }
public LocklessQueue()
{
Init();
}
public void Enqueue(T item)
{
SingleLinkNode oldTail = null;
SingleLinkNode oldTailNext;
SingleLinkNode newNode = new SingleLinkNode();
newNode.Item = item;
bool newNodeWasAdded = false;
while (!newNodeWasAdded)
{
oldTail = tail;
oldTailNext = oldTail.Next;
if (tail == oldTail)
{
if (oldTailNext == null)
newNodeWasAdded = CAS(ref tail.Next, null, newNode);
else
CAS(ref tail, oldTail, oldTailNext);
}
}
CAS(ref tail, oldTail, newNode);
Interlocked.Increment(ref count);
}
public virtual bool TryDequeue(out T item)
{
item = default(T);
SingleLinkNode oldHead = null;
bool haveAdvancedHead = false;
while (!haveAdvancedHead)
{
oldHead = head;
SingleLinkNode oldTail = tail;
SingleLinkNode oldHeadNext = oldHead.Next;
if (oldHead == head)
{
if (oldHead == oldTail)
{
if (oldHeadNext == null)
return false;
CAS(ref tail, oldTail, oldHeadNext);
}
else
{
item = oldHeadNext.Item;
haveAdvancedHead = CAS(ref head, oldHead, oldHeadNext);
if (haveAdvancedHead)
{
oldHeadNext.Item = default(T);
oldHead.Next = null;
}
}
}
}
Interlocked.Decrement(ref count);
return true;
}
public void Clear()
{
// ugly
T item;
while(count > 0)
TryDequeue(out item);
Init();
}
private void Init()
{
count = 0;
head = tail = new SingleLinkNode();
}
private static bool CAS(ref SingleLinkNode location, SingleLinkNode comparand, SingleLinkNode newValue)
{
return
(object)comparand ==
(object)Interlocked.CompareExchange<SingleLinkNode>(ref location, newValue, comparand);
}
}
}