Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true' and '$(ForceManagedImplementation)' != 'true'">
<Compile Include="System\Net\Windows\HttpListener.Windows.cs" />
<Compile Include="System\Net\Windows\HttpListenerSession.Windows.cs" />
<Compile Include="System\Net\Windows\HttpListenerContext.Windows.cs" />
<Compile Include="System\Net\Windows\HttpListenerRequest.Windows.cs" />
<Compile Include="System\Net\Windows\HttpListenerResponse.Windows.cs" />
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ namespace System.Net
public sealed unsafe partial class HttpListenerContext
{
private string _mutualAuthentication;
internal HttpListenerSession ListenerSession { get; private set; }

internal HttpListenerContext(HttpListener httpListener, RequestContextBase memoryBlob)
internal HttpListenerContext(HttpListenerSession session, RequestContextBase memoryBlob)
{
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"httpListener {httpListener} requestBlob={((IntPtr)memoryBlob.RequestBlob)}");
_listener = httpListener;
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"httpListener {session.Listener} requestBlob={((IntPtr)memoryBlob.RequestBlob)}");
_listener = session.Listener;
ListenerSession = session;
Request = new HttpListenerRequest(this, memoryBlob);
AuthenticationSchemes = httpListener.AuthenticationSchemes;
ExtendedProtectionPolicy = httpListener.ExtendedProtectionPolicy;
AuthenticationSchemes = _listener.AuthenticationSchemes;
ExtendedProtectionPolicy = _listener.ExtendedProtectionPolicy;
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"HttpListener: {_listener} HttpListenerRequest: {Request}");
}

Expand All @@ -41,9 +43,9 @@ internal void SetIdentity(IPrincipal principal, string mutualAuthentication)

internal HttpListener Listener => _listener;

internal SafeHandle RequestQueueHandle => _listener.RequestQueueHandle;
internal SafeHandle RequestQueueHandle => ListenerSession.RequestQueueHandle;

internal ThreadPoolBoundHandle RequestQueueBoundHandle => _listener.RequestQueueBoundHandle;
internal ThreadPoolBoundHandle RequestQueueBoundHandle => ListenerSession.RequestQueueBoundHandle;

internal ulong RequestId => Request.RequestId;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ private Uri RequestUri

internal ChannelBinding GetChannelBinding()
{
return HttpListenerContext.Listener.GetChannelBindingFromTls(_connectionId);
return HttpListener.GetChannelBindingFromTls(HttpListenerContext.ListenerSession, _connectionId);
}

internal void CheckDisposed()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Runtime.InteropServices;
using System.Threading;

namespace System.Net
{
internal sealed class HttpListenerSession
{
public readonly HttpListener Listener;
public readonly SafeHandle RequestQueueHandle;
private ThreadPoolBoundHandle _requestQueueBoundHandle;

public ThreadPoolBoundHandle RequestQueueBoundHandle
{
get
{
if (_requestQueueBoundHandle == null)
{
lock (this)
{
if (_requestQueueBoundHandle == null)
{
_requestQueueBoundHandle = ThreadPoolBoundHandle.BindHandle(RequestQueueHandle);
if (NetEventSource.IsEnabled) NetEventSource.Info($"ThreadPoolBoundHandle.BindHandle({RequestQueueHandle}) -> {_requestQueueBoundHandle}");
}
}
}

return _requestQueueBoundHandle;
}
}

public unsafe HttpListenerSession(HttpListener listener)
{
Listener = listener;

uint statusCode =
Interop.HttpApi.HttpCreateRequestQueue(
Interop.HttpApi.s_version, null, null, 0, out HttpRequestQueueV2Handle requestQueueHandle);

if (statusCode != Interop.HttpApi.ERROR_SUCCESS)
{
throw new HttpListenerException((int)statusCode);
}

// Disabling callbacks when IO operation completes synchronously (returns ErrorCodes.ERROR_SUCCESS)
if (HttpListener.SkipIOCPCallbackOnSuccess &&
!Interop.Kernel32.SetFileCompletionNotificationModes(
requestQueueHandle,
Interop.Kernel32.FileCompletionNotificationModes.SkipCompletionPortOnSuccess |
Interop.Kernel32.FileCompletionNotificationModes.SkipSetEventOnHandle))
{
throw new HttpListenerException(Marshal.GetLastWin32Error());
}

RequestQueueHandle = requestQueueHandle;
}

public unsafe void CloseRequestQueueHandle()
{
lock (this)
{
if (!RequestQueueHandle.IsInvalid)
{
if (NetEventSource.IsEnabled) NetEventSource.Info($"Dispose ThreadPoolBoundHandle: {_requestQueueBoundHandle}");
_requestQueueBoundHandle?.Dispose();
RequestQueueHandle.Dispose();

// CancelIoEx is called after Dispose to prevent a race condition involving parallel GetContext and
// HttpReceiveHttpRequest calls. Otherwise, calling CancelIoEx before Dispose might block the synchronous
// GetContext call until the next request arrives.
try
{
Interop.Kernel32.CancelIoEx(RequestQueueHandle, null); // This cancels the synchronous call to HttpReceiveHttpRequest
}
catch (ObjectDisposedException)
{
// Ignore the exception since it only means that the queue handle has been successfully disposed
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ internal sealed unsafe class ListenerAsyncResult : LazyAsyncResult

internal static IOCompletionCallback IOCallback => s_ioCallback;

internal ListenerAsyncResult(HttpListener listener, object userState, AsyncCallback callback) :
base(listener, userState, callback)
internal ListenerAsyncResult(HttpListenerSession session, object userState, AsyncCallback callback) :
base(session, userState, callback)
{
_requestContext = new AsyncRequestContext(listener.RequestQueueBoundHandle, this);
_requestContext = new AsyncRequestContext(session.RequestQueueBoundHandle, this);
}

private static void IOCompleted(ListenerAsyncResult asyncResult, uint errorCode, uint numBytes)
Expand All @@ -34,35 +34,35 @@ private static void IOCompleted(ListenerAsyncResult asyncResult, uint errorCode,
}
else
{
HttpListener httpWebListener = asyncResult.AsyncObject as HttpListener;
HttpListenerSession listenerSession = asyncResult.AsyncObject as HttpListenerSession;
if (errorCode == Interop.HttpApi.ERROR_SUCCESS)
{
// at this point we have received an unmanaged HTTP_REQUEST and memoryBlob
// points to it we need to hook up our authentication handling code here.
bool stoleBlob = false;
try
{
if (httpWebListener.ValidateRequest(asyncResult._requestContext))
if (HttpListener.ValidateRequest(listenerSession, asyncResult._requestContext))
{
result = httpWebListener.HandleAuthentication(asyncResult._requestContext, out stoleBlob);
result = listenerSession.Listener.HandleAuthentication(listenerSession, asyncResult._requestContext, out stoleBlob);
}
}
finally
{
if (stoleBlob)
{
// The request has been handed to the user, which means this code can't reuse the blob. Reset it here.
asyncResult._requestContext = result == null ? new AsyncRequestContext(httpWebListener.RequestQueueBoundHandle, asyncResult) : null;
asyncResult._requestContext = result == null ? new AsyncRequestContext(listenerSession.RequestQueueBoundHandle, asyncResult) : null;
}
else
{
asyncResult._requestContext.Reset(httpWebListener.RequestQueueBoundHandle, 0, 0);
asyncResult._requestContext.Reset(listenerSession.RequestQueueBoundHandle, 0, 0);
}
}
}
else
{
asyncResult._requestContext.Reset(httpWebListener.RequestQueueBoundHandle, asyncResult._requestContext.RequestBlob->RequestId, numBytes);
asyncResult._requestContext.Reset(listenerSession.RequestQueueBoundHandle, asyncResult._requestContext.RequestBlob->RequestId, numBytes);
}

// We need to issue a new request, either because auth failed, or because our buffer was too small the first time.
Expand Down Expand Up @@ -107,9 +107,9 @@ internal uint QueueBeginGetContext()
{
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Calling Interop.HttpApi.HttpReceiveHttpRequest RequestId: {_requestContext.RequestBlob->RequestId}Buffer:0x {((IntPtr)_requestContext.RequestBlob).ToString("x")} Size: {_requestContext.Size}");
uint bytesTransferred = 0;
HttpListener listener = (HttpListener)AsyncObject;
HttpListenerSession listenerSession = (HttpListenerSession)AsyncObject;
statusCode = Interop.HttpApi.HttpReceiveHttpRequest(
listener.RequestQueueHandle,
listenerSession.RequestQueueHandle,
_requestContext.RequestBlob->RequestId,
(uint)Interop.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY,
_requestContext.RequestBlob,
Expand All @@ -129,7 +129,7 @@ internal uint QueueBeginGetContext()
{
// the buffer was not big enough to fit the headers, we need
// to read the RequestId returned, allocate a new buffer of the required size
_requestContext.Reset(listener.RequestQueueBoundHandle, _requestContext.RequestBlob->RequestId, bytesTransferred);
_requestContext.Reset(listenerSession.RequestQueueBoundHandle, _requestContext.RequestBlob->RequestId, bytesTransferred);
continue;
}
else if (statusCode == Interop.HttpApi.ERROR_SUCCESS && HttpListener.SkipIOCPCallbackOnSuccess)
Expand Down
34 changes: 14 additions & 20 deletions src/libraries/System.Net.HttpListener/tests/SimpleHttpTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,23 +168,10 @@ public async Task UnknownHeaders_Success(int numHeaders)
}
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/30284")]
public void ListenerRestart_BeginGetContext_Success()
{
using (HttpListenerFactory factory = new HttpListenerFactory())
{
HttpListener listener = factory.GetListener();
listener.BeginGetContext((f) => { }, null);
listener.Stop();
listener.Start();
listener.BeginGetContext((f) => { }, null);
}
}

[ConditionalFact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/30284")]
public async Task ListenerRestart_GetContext_Success()
[ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
public async Task ListenerRestart_Success(bool sync)
{
const string Content = "ListenerRestart_GetContext_Success";
using (HttpListenerFactory factory = new HttpListenerFactory())
Expand All @@ -195,7 +182,10 @@ public async Task ListenerRestart_GetContext_Success()
_output.WriteLine("Connecting to {0}", factory.ListeningUrl);
Task<string> clientTask = client.GetStringAsync(factory.ListeningUrl);

HttpListenerContext context = listener.GetContext();
HttpListenerContext context = sync
? listener.GetContext()
: listener.EndGetContext(listener.BeginGetContext(ar => { }, null));

HttpListenerResponse response = context.Response;
response.OutputStream.Write(Encoding.UTF8.GetBytes(Content));
response.OutputStream.Close();
Expand All @@ -210,7 +200,7 @@ public async Task ListenerRestart_GetContext_Success()
// This may fail if something else took our port while restarting.
listener.Start();
}
catch (Exception e)
catch (HttpListenerException e)
{
_output.WriteLine(e.Message);
// Skip test if we lost race and we are unable to bind on same port again.
Expand All @@ -221,7 +211,11 @@ public async Task ListenerRestart_GetContext_Success()

// Repeat request to be sure listener is working.
clientTask = client.GetStringAsync(factory.ListeningUrl);
context = listener.GetContext();

context = sync
? listener.GetContext()
: listener.EndGetContext(listener.BeginGetContext(ar => { }, null));

response = context.Response;
response.OutputStream.Write(Encoding.UTF8.GetBytes(Content));
response.OutputStream.Close();
Expand Down