Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Moved
=====

This project is now at https://gitlab.com/derbyinsight/SocketHttpListener

SocketHttpListener
==================

Expand Down
495 changes: 495 additions & 0 deletions SocketHttpListener.Test/HttpListenerTimeoutTest.cs

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions SocketHttpListener.Test/ResumableTimerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using ResumableTimer = SocketHttpListener.Net.ResumableTimer;

namespace SocketHttpListener.Test
{
[TestClass]
public class ResumableTimerTest
{
object timeoutData;

[TestInitialize]
public void TestInit()
{
this.timeoutData = 1;
}

[TestCleanup]
public void TestCleanup()
{
}

[TestMethod]
public async Task TestGenericTimeout()
{
ResumableTimer rt = new ResumableTimer(TimeoutCallback, 2);

rt.Start(TimeSpan.FromMilliseconds(100));
Assert.AreEqual(this.timeoutData, 1);
await Task.Delay(250);
Assert.AreEqual(this.timeoutData, 2);
}

[TestMethod]
public async Task TestNoTimeoutAndReset()
{
ResumableTimer rt = new ResumableTimer(TimeoutCallback, 2);

rt.Start(TimeSpan.FromHours(500));
await Task.Delay(250);
Assert.AreEqual(this.timeoutData, 1);

rt.Start(TimeSpan.FromMilliseconds(100));
await Task.Delay(250);
Assert.AreEqual(this.timeoutData, 2);
}

[TestMethod]
public async Task TestStopAndResume()
{
ResumableTimer rt = new ResumableTimer(TimeoutCallback, 2);

rt.Start(TimeSpan.FromMilliseconds(150));
await Task.Delay(10);
Assert.AreEqual(this.timeoutData, 1);

rt.Stop();
rt.Resume();
await Task.Delay(10);
Assert.AreEqual(this.timeoutData, 1, "How long did stop/resume take?");

rt.Stop();
await Task.Delay(250);
Assert.AreEqual(this.timeoutData, 1, "Ensure that Stop/Resume work without timeout");

rt.Resume();
await Task.Delay(10);
Assert.AreEqual(this.timeoutData, 2, "Ensure that Resume worked");
}

void TimeoutCallback(object data)
{
timeoutData = data;
}
}
}
2 changes: 2 additions & 0 deletions SocketHttpListener.Test/SocketHttpListener.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="HttpListenerTimeoutTest.cs" />
<Compile Include="ResumableTimerTest.cs" />
<Compile Include="Utility.cs" />
<Compile Include="HttpConnectionTest.cs" />
<Compile Include="LoggerFactory.cs" />
Expand Down
3 changes: 3 additions & 0 deletions SocketHttpListener.Test/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ namespace SocketHttpListener.Test
{
internal static class Utility
{
internal const string SITE_HOSTNAME = "localhost";
internal const int SITE_PORT = 12345;
internal const string SITE_PREFIX = "/Testing";
internal const string SITE_URL = "localhost:12345/Testing/";
internal const string TEXT_TO_WRITE = "TESTING12345";

Expand Down
37 changes: 30 additions & 7 deletions SocketHttpListener/Net/HttpConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ sealed class HttpConnection
int reuses;
bool context_bound;
bool secure;
int s_timeout = 300000; // 90k ms for first request, 15k ms from then on
Timer timer;
ResumableTimer timer;
IPEndPoint local_ep;
HttpListener last_listener;
int[] client_cert_errors;
Expand All @@ -47,6 +46,7 @@ public HttpConnection(ILogger logger, Socket sock, EndPointListener epl, bool se
this.epl = epl;
this.secure = secure;
this.cert = cert;
this.SetSocketTimeout(sock);
if (secure == false)
{
stream = new NetworkStream(sock, false);
Expand All @@ -70,10 +70,30 @@ public HttpConnection(ILogger logger, Socket sock, EndPointListener epl, bool se
ssl_stream.AuthenticateAsServer(cert);
stream = ssl_stream;
}
timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
timer = new ResumableTimer(OnTimeout);
Init();
}

void SetSocketTimeout(Socket sock)
{
// Socket timeout should be >= the largest applicable http listener timeout, and
// Certainly > 0.
HttpListenerTimeoutManager mgr = this.epl.Listener.TimeoutManager;

TimeSpan readTimeout = TimeSpan.FromMilliseconds(50);
if (mgr.EntityBody > readTimeout)
readTimeout = mgr.EntityBody;
if (mgr.HeaderWait > readTimeout)
readTimeout = mgr.HeaderWait;

TimeSpan writeTimeout = TimeSpan.FromMilliseconds(50);
if (mgr.DrainEntityBody > writeTimeout)
writeTimeout = mgr.DrainEntityBody;

sock.ReceiveTimeout = (int)readTimeout.TotalMilliseconds;
sock.SendTimeout = (int)writeTimeout.TotalMilliseconds;
}

public Stream Stream
{
get
Expand Down Expand Up @@ -159,12 +179,13 @@ public void BeginReadRequest()
{
//if (reuses == 1)
// s_timeout = 15000;
timer.Change(s_timeout, Timeout.Infinite);

timer.Start(this.epl.Listener.TimeoutManager.IdleConnection);
stream.BeginRead(buffer, 0, BufferSize, onread_cb, this);
}
catch
{
timer.Change(Timeout.Infinite, Timeout.Infinite);
timer.Stop();
CloseSocket();
Unbind();
}
Expand Down Expand Up @@ -213,7 +234,7 @@ static void OnRead(IAsyncResult ares)

void OnReadInternal(IAsyncResult ares)
{
timer.Change(Timeout.Infinite, Timeout.Infinite);
timer.Stop();
int nread = -1;
try
{
Expand Down Expand Up @@ -273,6 +294,7 @@ void OnReadInternal(IAsyncResult ares)
}
try
{
timer.Start(this.epl.Listener.TimeoutManager.HeaderWait);
stream.BeginRead(buffer, 0, BufferSize, onread_cb, this);
}
catch (IOException ex)
Expand Down Expand Up @@ -511,7 +533,7 @@ internal void Close(bool force_close)
}
*/

if (!force_close && context.Request.FlushInput())
if (!force_close && context.Request.FlushInput(this.epl.Listener.TimeoutManager.EntityBody))
{
if (chunked && context.Response.ForceCloseChunked == false)
{
Expand All @@ -523,6 +545,7 @@ internal void Close(bool force_close)
return;
}

// BUG: isn't this exactly the same code that is in the if() block above?
reuses++;
Unbind();
Init();
Expand Down
9 changes: 9 additions & 0 deletions SocketHttpListener/Net/HttpListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate
}
}

HttpListenerTimeoutManager timeoutManager = new HttpListenerTimeoutManager();
public HttpListenerTimeoutManager TimeoutManager
{
get
{
return this.timeoutManager;
}
}

public bool IgnoreWriteExceptions
{
get { return ignore_write_exceptions; }
Expand Down
13 changes: 7 additions & 6 deletions SocketHttpListener/Net/HttpListenerRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ internal void FinishInitialization()
if (!Uri.TryCreate(base_uri + path, UriKind.Absolute, out url))
{
context.ErrorMessage = WebUtility.HtmlEncode("Invalid url: " + base_uri + path);
return; return;
return;
}

CreateQueryString(url.Query);
Expand Down Expand Up @@ -383,7 +383,7 @@ internal void AddHeader(string header)
}

// returns true is the stream could be reused.
internal bool FlushInput()
internal bool FlushInput(TimeSpan timeout)
{
if (!HasEntityBody)
return true;
Expand All @@ -395,13 +395,14 @@ internal bool FlushInput()
byte[] bytes = new byte[length];
while (true)
{
// TODO: test if MS has a timeout when doing this
try
{
IAsyncResult ares = InputStream.BeginRead(bytes, 0, length, null, null);
if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne(1000))
Task<int> readTask = InputStream.ReadAsync(bytes, 0, length);
if (!readTask.Wait(timeout))
return false;
if (InputStream.EndRead(ares) <= 0)

int bytesRead = readTask.Result;
if (bytesRead <= 0)
return true;
}
catch (ObjectDisposedException e)
Expand Down
99 changes: 99 additions & 0 deletions SocketHttpListener/Net/HttpListenerTimeoutManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SocketHttpListener.Net
{
/// <summary>
/// Timeouts.
/// </summary>
public class HttpListenerTimeoutManager
{
private TimeSpan idleConnection = TimeSpan.FromMinutes(5);

/// <summary>
/// Time the listener will wait for the next HTTP request.
///
/// Defaults to 5 minutes for backward compatibility.
///
/// This is the default/maximum timeout that will be used while waiting for
/// a KeepAlive session.
/// </summary>
public TimeSpan IdleConnection
{
get
{
return this.idleConnection;
}
set
{
this.idleConnection = value;
}
}

private TimeSpan headerWait = TimeSpan.FromMinutes(5);
///// <summary>
///// Maximum time the listener will spend reading an HTTP request's headers.
/////
///// The network read timeout used when http headers have started
///// to arrive but not finished.
///// </summary>
public TimeSpan HeaderWait
{
get
{
return this.headerWait;
}
set
{
this.headerWait = value;
}
}

private TimeSpan entityBody = TimeSpan.FromMinutes(5);
/// <summary>
/// The read timeout when reading the body. Set for synchronous IO.
///
/// Not enforced. If the OnContext handler uses async IO, it will need to
/// handle timeouts man ually.
///
/// Defaults to 5 minutes.
/// </summary>
public TimeSpan EntityBody
{
get
{
return this.entityBody;
}
set
{
this.entityBody = value;
}
}

private TimeSpan drainEntityBody = TimeSpan.FromMinutes(5);
/// <summary>
/// The write timeout, used during the body write. Set for synchronous IO.
///
/// If the OnContext handler uses async IO, it will need to handle timeouts
/// manually.
///
/// This timeout is used for all HTTP writes, including headers, because the
/// HttpListenerTimeoutManager spec does not have a separate timeout field for
/// this.
/// </summary>
public TimeSpan DrainEntityBody
{
get
{
return this.drainEntityBody;
}
set
{
this.drainEntityBody = value;
}
}
}
}
1 change: 1 addition & 0 deletions SocketHttpListener/Net/ResponseStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ public override void Write(byte[] buffer, int offset, int count)
InternalWrite(crlf, 0, 2);
}

// TODO: sending headers is not async, BeginWrite can block until socket timeout
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (disposed)
Expand Down
Loading