mirror of
git://opensimulator.org/git/opensim-libs
synced 2026-08-14 00:58:00 +00:00
update last changes to now unused httpserver
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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*/
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user