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
495 changes: 495 additions & 0 deletions SocketHttpListener.Test/HttpListenerTimeoutTest.cs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions SocketHttpListener.Test/SocketHttpListener.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="HttpListenerTimeoutTest.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
30 changes: 25 additions & 5 deletions SocketHttpListener/Net/HttpConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ 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;
IPEndPoint local_ep;
HttpListener last_listener;
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 Down Expand Up @@ -74,6 +74,26 @@ public HttpConnection(ILogger logger, Socket sock, EndPointListener epl, bool se
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 @@ -157,9 +177,7 @@ public void BeginReadRequest()
buffer = new byte[BufferSize];
try
{
//if (reuses == 1)
// s_timeout = 15000;
timer.Change(s_timeout, Timeout.Infinite);
timer.Change(this.epl.Listener.TimeoutManager.IdleConnection, Timeout.InfiniteTimeSpan);
stream.BeginRead(buffer, 0, BufferSize, onread_cb, this);
}
catch
Expand Down Expand Up @@ -273,6 +291,7 @@ void OnReadInternal(IAsyncResult ares)
}
try
{
timer.Change(this.epl.Listener.TimeoutManager.HeaderWait, Timeout.InfiniteTimeSpan);
stream.BeginRead(buffer, 0, BufferSize, onread_cb, this);
}
catch (IOException ex)
Expand Down Expand Up @@ -511,7 +530,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 +542,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/SocketHttpListener.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<Compile Include="Net\HttpListenerPrefixCollection.cs" />
<Compile Include="Net\HttpListenerRequest.cs" />
<Compile Include="Net\HttpListenerResponse.cs" />
<Compile Include="Net\HttpListenerTimeoutManager.cs" />
<Compile Include="Net\HttpStatusCode.cs" />
<Compile Include="Net\HttpStreamAsyncResult.cs" />
<Compile Include="Net\HttpVersion.cs" />
Expand Down