Factoring out UrlGroup, ServerSession, RequestQueue, ChannelBinding,

Disconnect Listener.
This commit is contained in:
Chris R 2016-07-15 16:34:20 -07:00
Родитель 07b078d4e3
Коммит 3d2e1c4d3e
17 изменённых файлов: 644 добавлений и 603 удалений

Просмотреть файл

@ -165,7 +165,7 @@ namespace Microsoft.Net.Http.Server
retry = false;
uint bytesTransferred = 0;
statusCode = UnsafeNclNativeMethods.HttpApi.HttpReceiveHttpRequest(
Server.RequestQueueHandle,
Server.RequestQueue.Handle,
_nativeRequestContext.RequestBlob->RequestId,
(uint)UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY,
_nativeRequestContext.RequestBlob,

Просмотреть файл

@ -100,7 +100,7 @@ namespace Microsoft.Net.Http.Server
IntPtr infoptr = new IntPtr(&authInfo);
_server.SetUrlGroupProperty(
_server.UrlGroup.SetProperty(
UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY.HttpServerAuthenticationProperty,
infoptr, (uint)AuthInfoSize);
}

Просмотреть файл

@ -0,0 +1,165 @@
// Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing
// permissions and limitations under the License.
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Threading;
using Microsoft.Extensions.Logging;
namespace Microsoft.Net.Http.Server
{
internal class DisconnectListener
{
private readonly ConcurrentDictionary<ulong, ConnectionCancellation> _connectionCancellationTokens
= new ConcurrentDictionary<ulong, ConnectionCancellation>();
private readonly RequestQueue _requestQueue;
private readonly ILogger _logger;
internal DisconnectListener(RequestQueue requestQueue, ILogger logger)
{
_requestQueue = requestQueue;
_logger = logger;
}
internal CancellationToken GetTokenForConnection(ulong connectionId)
{
try
{
// Create exactly one CancellationToken per connection.
return GetOrCreateDisconnectToken(connectionId);
}
catch (Win32Exception exception)
{
LogHelper.LogException(_logger, "GetConnectionToken", exception);
return CancellationToken.None;
}
}
private CancellationToken GetOrCreateDisconnectToken(ulong connectionId)
{
// Read case is performance sensitive
ConnectionCancellation cancellation;
if (!_connectionCancellationTokens.TryGetValue(connectionId, out cancellation))
{
cancellation = GetCreatedConnectionCancellation(connectionId);
}
return cancellation.GetCancellationToken(connectionId);
}
private ConnectionCancellation GetCreatedConnectionCancellation(ulong connectionId)
{
// Race condition on creation has no side effects
var cancellation = new ConnectionCancellation(this);
return _connectionCancellationTokens.GetOrAdd(connectionId, cancellation);
}
private unsafe CancellationToken CreateDisconnectToken(ulong connectionId)
{
LogHelper.LogDebug(_logger, "CreateDisconnectToken", "Registering connection for disconnect for connection ID: " + connectionId);
// Create a nativeOverlapped callback so we can register for disconnect callback
var cts = new CancellationTokenSource();
SafeNativeOverlapped nativeOverlapped = null;
var boundHandle = _requestQueue.BoundHandle;
nativeOverlapped = new SafeNativeOverlapped(boundHandle, boundHandle.AllocateNativeOverlapped(
(errorCode, numBytes, overlappedPtr) =>
{
LogHelper.LogDebug(_logger, "CreateDisconnectToken", "http.sys disconnect callback fired for connection ID: " + connectionId);
// Free the overlapped
nativeOverlapped.Dispose();
// Pull the token out of the list and Cancel it.
ConnectionCancellation token;
_connectionCancellationTokens.TryRemove(connectionId, out token);
try
{
cts.Cancel();
}
catch (AggregateException exception)
{
LogHelper.LogException(_logger, "CreateDisconnectToken Callback", exception);
}
cts.Dispose();
},
null, null));
uint statusCode;
try
{
statusCode = UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnectEx(requestQueueHandle: _requestQueue.Handle,
connectionId: connectionId, reserved: 0, overlapped: nativeOverlapped);
}
catch (Win32Exception exception)
{
statusCode = (uint)exception.NativeErrorCode;
LogHelper.LogException(_logger, "CreateDisconnectToken", exception);
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING &&
statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
// We got an unknown result so return a None
// TODO: return a canceled token?
return CancellationToken.None;
}
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
// TODO: return a canceled token?
return CancellationToken.None;
}
return cts.Token;
}
private class ConnectionCancellation
{
private readonly DisconnectListener _parent;
private volatile bool _initialized; // Must be volatile because initialization is synchronized
private CancellationToken _cancellationToken;
public ConnectionCancellation(DisconnectListener parent)
{
_parent = parent;
}
internal CancellationToken GetCancellationToken(ulong connectionId)
{
// Initialized case is performance sensitive
if (_initialized)
{
return _cancellationToken;
}
return InitializeCancellationToken(connectionId);
}
private CancellationToken InitializeCancellationToken(ulong connectionId)
{
object syncObject = this;
#pragma warning disable 420 // Disable warning about volatile by reference since EnsureInitialized does volatile operations
return LazyInitializer.EnsureInitialized(ref _cancellationToken, ref _initialized, ref syncObject, () => _parent.CreateDisconnectToken(connectionId));
#pragma warning restore 420
}
}
}
}

Просмотреть файл

@ -0,0 +1,119 @@
// Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing
// permissions and limitations under the License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Microsoft.Net.Http.Server
{
internal class RequestQueue
{
private static readonly int BindingInfoSize =
Marshal.SizeOf<UnsafeNclNativeMethods.HttpApi.HTTP_BINDING_INFO>();
private readonly UrlGroup _urlGroup;
private readonly ILogger _logger;
internal RequestQueue(UrlGroup urlGroup, ILogger logger)
{
_urlGroup = urlGroup;
_logger = logger;
HttpRequestQueueV2Handle requestQueueHandle = null;
var statusCode = UnsafeNclNativeMethods.SafeNetHandles.HttpCreateRequestQueue(
UnsafeNclNativeMethods.HttpApi.Version, null, null, 0, out requestQueueHandle);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
throw new WebListenerException((int)statusCode);
}
// Disabling callbacks when IO operation completes synchronously (returns ErrorCodes.ERROR_SUCCESS)
if (WebListener.SkipIOCPCallbackOnSuccess &&
!UnsafeNclNativeMethods.SetFileCompletionNotificationModes(
requestQueueHandle,
UnsafeNclNativeMethods.FileCompletionNotificationModes.SkipCompletionPortOnSuccess |
UnsafeNclNativeMethods.FileCompletionNotificationModes.SkipSetEventOnHandle))
{
throw new WebListenerException(Marshal.GetLastWin32Error());
}
Handle = requestQueueHandle;
BoundHandle = ThreadPoolBoundHandle.BindHandle(Handle);
}
internal SafeHandle Handle { get; }
internal ThreadPoolBoundHandle BoundHandle { get; }
internal unsafe void AttachToUrlGroup()
{
// Set the association between request queue and url group. After this, requests for registered urls will
// get delivered to this request queue.
var info = new UnsafeNclNativeMethods.HttpApi.HTTP_BINDING_INFO();
info.Flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
info.RequestQueueHandle = Handle.DangerousGetHandle();
var infoptr = new IntPtr(&info);
_urlGroup.SetProperty(UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
infoptr, (uint)BindingInfoSize);
}
internal unsafe void DetachFromUrlGroup()
{
// Break the association between request queue and url group. After this, requests for registered urls
// will get 503s.
// Note that this method may be called multiple times (Stop() and then Abort()). This
// is fine since http.sys allows to set HttpServerBindingProperty multiple times for valid
// Url groups.
var info = new UnsafeNclNativeMethods.HttpApi.HTTP_BINDING_INFO();
info.Flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE;
info.RequestQueueHandle = IntPtr.Zero;
var infoptr = new IntPtr(&info);
_urlGroup.SetProperty(UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
infoptr, (uint)BindingInfoSize, throwOnError: false);
}
// The listener must be active for this to work.
internal unsafe void SetLengthLimit(long length)
{
var result = UnsafeNclNativeMethods.HttpApi.HttpSetRequestQueueProperty(Handle,
UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY.HttpServerQueueLengthProperty,
new IntPtr((void*)&length), (uint)Marshal.SizeOf<long>(), 0, IntPtr.Zero);
if (result != 0)
{
throw new WebListenerException((int)result);
}
}
public void Dispose()
{
BoundHandle.Dispose();
Handle.Dispose();
}
}
}

Просмотреть файл

@ -0,0 +1,48 @@
// Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing
// permissions and limitations under the License.
using System;
using System.Diagnostics;
namespace Microsoft.Net.Http.Server
{
internal class ServerSession : IDisposable
{
internal unsafe ServerSession()
{
ulong serverSessionId = 0;
var statusCode = UnsafeNclNativeMethods.HttpApi.HttpCreateServerSession(
UnsafeNclNativeMethods.HttpApi.Version, &serverSessionId, 0);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
throw new WebListenerException((int)statusCode);
}
Debug.Assert(serverSessionId != 0, "Invalid id returned by HttpCreateServerSession");
Id = new HttpServerSessionHandle(serverSessionId);
}
public HttpServerSessionHandle Id { get; private set; }
public void Dispose()
{
Id.Dispose();
}
}
}

Просмотреть файл

@ -1025,15 +1025,7 @@ namespace Microsoft.Net.Http.Server
version.HttpApiMajorVersion = majorVersion;
version.HttpApiMinorVersion = minorVersion;
// For pre-Win7 OS versions, we need to check whether http.sys contains the CBT patch.
// We do so by passing HTTP_INITIALIZE_CBT flag to HttpInitialize. If the flag is not
// supported, http.sys is not patched. Note that http.sys will return invalid parameter
// also on Win7, even though it shipped with CBT support. Therefore we must not pass
// the flag on Win7 and later.
uint statusCode = ErrorCodes.ERROR_SUCCESS;
// on Win7 and later, we don't pass the CBT flag. CBT is always supported.
statusCode = HttpApi.HttpInitialize(version, (uint)HTTP_FLAGS.HTTP_INITIALIZE_SERVER, null);
var statusCode = HttpApi.HttpInitialize(version, (uint)HTTP_FLAGS.HTTP_INITIALIZE_SERVER, null);
supported = statusCode == ErrorCodes.ERROR_SUCCESS;
}

Просмотреть файл

@ -0,0 +1,119 @@
// Copyright (c) Microsoft Open Technologies, Inc.
// All Rights Reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
// NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing
// permissions and limitations under the License.
using System;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Microsoft.Net.Http.Server
{
internal class UrlGroup : IDisposable
{
private ServerSession _serverSession;
private ILogger _logger;
private bool _disposed;
internal unsafe UrlGroup(ServerSession serverSession, ILogger logger)
{
_serverSession = serverSession;
_logger = logger;
ulong urlGroupId = 0;
var statusCode = UnsafeNclNativeMethods.HttpApi.HttpCreateUrlGroup(
_serverSession.Id.DangerousGetServerSessionId(), &urlGroupId, 0);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
throw new WebListenerException((int)statusCode);
}
Debug.Assert(urlGroupId != 0, "Invalid id returned by HttpCreateUrlGroup");
Id = urlGroupId;
}
internal ulong Id { get; private set; }
internal void SetProperty(UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true)
{
Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer");
var statusCode = UnsafeNclNativeMethods.HttpApi.HttpSetUrlGroupProperty(Id, property, info, infosize);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
var exception = new WebListenerException((int)statusCode);
LogHelper.LogException(_logger, "SetUrlGroupProperty", exception);
if (throwOnError)
{
throw exception;
}
}
}
internal void RegisterPrefix(string uriPrefix, int contextId)
{
LogHelper.LogInfo(_logger, "Listening on prefix: " + uriPrefix);
var statusCode = UnsafeNclNativeMethods.HttpApi.HttpAddUrlToUrlGroup(Id, uriPrefix, (ulong)contextId, 0);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS)
{
throw new WebListenerException((int)statusCode, string.Format(Resources.Exception_PrefixAlreadyRegistered, uriPrefix));
}
else
{
throw new WebListenerException((int)statusCode);
}
}
}
internal bool UnregisterPrefix(string uriPrefix)
{
LogHelper.LogInfo(_logger, "Stop listening on prefix: " + uriPrefix);
var statusCode = UnsafeNclNativeMethods.HttpApi.HttpRemoveUrlFromUrlGroup(Id, uriPrefix, 0);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND)
{
return false;
}
return true;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
Debug.Assert(Id != 0, "HttpCloseUrlGroup called with invalid url group id");
uint statusCode = UnsafeNclNativeMethods.HttpApi.HttpCloseUrlGroup(Id);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
LogHelper.LogError(_logger, "CleanupV2Config", "Result: " + statusCode);
}
Id = 0;
}
}
}

Просмотреть файл

@ -22,14 +22,17 @@
// -----------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Microsoft.Net.Http.Server
{
@ -39,7 +42,9 @@ namespace Microsoft.Net.Http.Server
{
private const uint CertBoblSize = 1500;
private static readonly IOCompletionCallback IOCallback = new IOCompletionCallback(WaitCallback);
private static readonly int RequestChannelBindStatusSize =
Marshal.SizeOf<UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_CHANNEL_BIND_STATUS>();
private SafeNativeOverlapped _overlapped;
private byte[] _backingBuffer;
private UnsafeNclNativeMethods.HttpApi.HTTP_SSL_CLIENT_CERT_INFO* _memoryBlob;
@ -144,7 +149,7 @@ namespace Microsoft.Net.Http.Server
return;
}
_backingBuffer = new byte[checked((int)size)];
var boundHandle = RequestContext.Server.BoundHandle;
var boundHandle = RequestContext.Server.RequestQueue.BoundHandle;
_overlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, _backingBuffer));
_memoryBlob = (UnsafeNclNativeMethods.HttpApi.HTTP_SSL_CLIENT_CERT_INFO*)Marshal.UnsafeAddrOfPinnedArrayElement(_backingBuffer, 0);
@ -362,5 +367,84 @@ namespace Microsoft.Net.Http.Server
{
get { return _tcs.Task.IsCompleted; }
}
internal static unsafe ChannelBinding GetChannelBindingFromTls(RequestQueue requestQueue, ulong connectionId, ILogger logger)
{
// +128 since a CBT is usually <128 thus we need to call HRCC just once. If the CBT
// is >128 we will get ERROR_MORE_DATA and call again
int size = RequestChannelBindStatusSize + 128;
Debug.Assert(size >= 0);
byte[] blob = null;
SafeLocalFreeChannelBinding token = null;
uint bytesReceived = 0; ;
uint statusCode;
do
{
blob = new byte[size];
fixed (byte* blobPtr = blob)
{
// Http.sys team: ServiceName will always be null if
// HTTP_RECEIVE_SECURE_CHANNEL_TOKEN flag is set.
statusCode = UnsafeNclNativeMethods.HttpApi.HttpReceiveClientCertificate(
requestQueue.Handle,
connectionId,
(uint)UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_SECURE_CHANNEL_TOKEN,
blobPtr,
(uint)size,
&bytesReceived,
SafeNativeOverlapped.Zero);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
int tokenOffset = GetTokenOffsetFromBlob((IntPtr)blobPtr);
int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr);
Debug.Assert(tokenSize < Int32.MaxValue);
token = SafeLocalFreeChannelBinding.LocalAlloc(tokenSize);
Marshal.Copy(blob, tokenOffset, token.DangerousGetHandle(), tokenSize);
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA)
{
int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr);
Debug.Assert(tokenSize < Int32.MaxValue);
size = RequestChannelBindStatusSize + tokenSize;
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER)
{
LogHelper.LogError(logger, "GetChannelBindingFromTls", "Channel binding is not supported.");
return null; // old schannel library which doesn't support CBT
}
else
{
// It's up to the consumer to fail if the missing ChannelBinding matters to them.
LogHelper.LogException(logger, "GetChannelBindingFromTls", new WebListenerException((int)statusCode));
break;
}
}
}
while (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS);
return token;
}
private static int GetTokenOffsetFromBlob(IntPtr blob)
{
Debug.Assert(blob != IntPtr.Zero);
IntPtr tokenPointer = Marshal.ReadIntPtr(blob, (int)Marshal.OffsetOf<UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_CHANNEL_BIND_STATUS>("ChannelToken"));
Debug.Assert(tokenPointer != IntPtr.Zero);
return (int)IntPtrHelper.Subtract(tokenPointer, blob);
}
private static int GetTokenSizeFromBlob(IntPtr blob)
{
Debug.Assert(blob != IntPtr.Zero);
return Marshal.ReadInt32(blob, (int)Marshal.OffsetOf<UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_CHANNEL_BIND_STATUS>("ChannelTokenSize"));
}
}
}

Просмотреть файл

@ -182,7 +182,7 @@ namespace Microsoft.Net.Http.Server
uint newSize = size != 0 ? size : RequestBuffer == null ? DefaultBufferSize : Size;
SetBuffer(checked((int)newSize));
var boundHandle = _acceptResult.Server.BoundHandle;
var boundHandle = _acceptResult.Server.RequestQueue.BoundHandle;
_nativeOverlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(AsyncAcceptContext.IOCallback, _acceptResult, RequestBuffer));

Просмотреть файл

@ -22,9 +22,11 @@
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
@ -84,7 +86,7 @@ namespace Microsoft.Net.Http.Server
// We need to be able to dispose of the registrations each request to prevent leaks.
if (!_disconnectToken.HasValue)
{
var connectionDisconnectToken = _server.RegisterForDisconnectNotification(this);
var connectionDisconnectToken = _server.DisconnectListener.GetTokenForConnection(Request.ConnectionId);
if (connectionDisconnectToken.CanBeCanceled)
{
@ -117,7 +119,7 @@ namespace Microsoft.Net.Http.Server
{
get
{
return _server.RequestQueueHandle;
return _server.RequestQueue.Handle;
}
}
@ -167,17 +169,25 @@ namespace Microsoft.Net.Http.Server
Response.SendOpaqueUpgrade(); // TODO: Async
Request.SwitchToOpaqueMode();
Response.SwitchToOpaqueMode();
Stream opaqueStream = new OpaqueStream(Request.Body, Response.Body);
return Task.FromResult(opaqueStream);
var opaqueStream = new OpaqueStream(Request.Body, Response.Body);
return Task.FromResult<Stream>(opaqueStream);
}
/*
public bool TryGetChannelBinding(ref ChannelBinding value)
// TODO: Public when needed
internal bool TryGetChannelBinding(ref ChannelBinding value)
{
value = Server.GetChannelBinding(Request.ConnectionId, Request.IsSecureConnection);
if (!Request.IsSecureConnection)
{
LogHelper.LogDebug(Logger, "TryGetChannelBinding", "Channel binding requires HTTPS.");
return false;
}
value = ClientCertLoader.GetChannelBindingFromTls(Server.RequestQueue, Request.ConnectionId, Logger);
Debug.Assert(value != null, "GetChannelBindingFromTls returned null even though OS supposedly supports Extended Protection");
LogHelper.LogInfo(Logger, "Channel binding retrieved.");
return value != null;
}
*/
public void Dispose()
{

Просмотреть файл

@ -64,7 +64,7 @@ namespace Microsoft.Net.Http.Server
: this(requestStream, userState, callback)
{
_dataAlreadyRead = dataAlreadyRead;
var boundHandle = requestStream.RequestContext.Server.BoundHandle;
var boundHandle = requestStream.RequestContext.Server.RequestQueue.BoundHandle;
_overlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, buffer));
_pinnedBuffer = (Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset));

Просмотреть файл

@ -54,7 +54,7 @@ namespace Microsoft.Net.Http.Server
CancellationTokenRegistration cancellationRegistration)
: this(responseStream, cancellationRegistration)
{
var boundHandle = _responseStream.RequestContext.Server.BoundHandle;
var boundHandle = _responseStream.RequestContext.Server.RequestQueue.BoundHandle;
object[] objectsToPin;
if (buffer.TotalBytes == 0)
@ -117,7 +117,7 @@ namespace Microsoft.Net.Http.Server
long? count, bool chunked, CancellationTokenRegistration cancellationRegistration)
: this(responseStream, cancellationRegistration)
{
var boundHandle = responseStream.RequestContext.Server.BoundHandle;
var boundHandle = responseStream.RequestContext.Server.RequestQueue.BoundHandle;
int bufferSize = 1024 * 64; // TODO: Validate buffer size choice.
#if NETSTANDARD1_3

Просмотреть файл

@ -270,7 +270,7 @@ namespace Microsoft.Net.Http.Server
IntPtr infoptr = new IntPtr(&timeoutinfo);
_server.SetUrlGroupProperty(
_server.UrlGroup.SetProperty(
UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY.HttpServerTimeoutsProperty,
infoptr, (uint)TimeoutLimitSize);
}

Просмотреть файл

@ -151,7 +151,7 @@ namespace Microsoft.Net.Http.Server
else
{
// This means a port was specified but was invalid or empty.
throw new FormatException("Invalid prefix, invalid port speficification: " + prefix);
throw new FormatException("Invalid prefix, invalid port specified: " + prefix);
}
}
else

Просмотреть файл

@ -49,7 +49,7 @@ namespace Microsoft.Net.Http.Server
var id = _nextId++;
if (_webListener.IsListening)
{
RegisterPrefix(item.Whole, id);
_webListener.UrlGroup.RegisterPrefix(item.Whole, id);
}
_prefixes.Add(id, item);
}
@ -108,7 +108,7 @@ namespace Microsoft.Net.Http.Server
id = pair.Key;
if (_webListener.IsListening)
{
UnregisterPrefix(pair.Value.Whole);
_webListener.UrlGroup.UnregisterPrefix(pair.Value.Whole);
}
}
}
@ -142,7 +142,7 @@ namespace Microsoft.Net.Http.Server
foreach (var pair in _prefixes)
{
// We'll get this index back on each request and use it to look up the prefix to calculate PathBase.
RegisterPrefix(pair.Value.Whole, pair.Key);
_webListener.UrlGroup.RegisterPrefix(pair.Value.Whole, pair.Key);
}
}
}
@ -155,51 +155,9 @@ namespace Microsoft.Net.Http.Server
foreach (var prefix in _prefixes.Values)
{
// ignore possible failures
UnregisterPrefix(prefix.Whole);
_webListener.UrlGroup.UnregisterPrefix(prefix.Whole);
}
}
}
private void RegisterPrefix(string uriPrefix, int contextId)
{
LogHelper.LogInfo(_webListener.Logger, "Listening on prefix: " + uriPrefix);
uint statusCode =
UnsafeNclNativeMethods.HttpApi.HttpAddUrlToUrlGroup(
_webListener.UrlGroupId,
uriPrefix,
(ulong)contextId,
0);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS)
{
throw new WebListenerException((int)statusCode, String.Format(Resources.Exception_PrefixAlreadyRegistered, uriPrefix));
}
else
{
throw new WebListenerException((int)statusCode);
}
}
}
private bool UnregisterPrefix(string uriPrefix)
{
uint statusCode = 0;
LogHelper.LogInfo(_webListener.Logger, "Stop listening on prefix: " + uriPrefix);
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpRemoveUrlFromUrlGroup(
_webListener.UrlGroupId,
uriPrefix,
0);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND)
{
return false;
}
return true;
}
}
}

Просмотреть файл

@ -22,14 +22,10 @@
//------------------------------------------------------------------------------
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security.Authentication.ExtendedProtection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@ -41,10 +37,6 @@ namespace Microsoft.Net.Http.Server
public sealed class WebListener : IDisposable
{
private const long DefaultRequestQueueLength = 1000; // Http.sys default.
private static readonly int RequestChannelBindStatusSize =
Marshal.SizeOf<UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_CHANNEL_BIND_STATUS>();
private static readonly int BindingInfoSize =
Marshal.SizeOf<UnsafeNclNativeMethods.HttpApi.HTTP_BINDING_INFO>();
// Win8# 559317 fixed a bug in Http.sys's HttpReceiveClientCertificate method.
// Without this fix IOCP callbacks were not being called although ERROR_IO_PENDING was
@ -61,20 +53,17 @@ namespace Microsoft.Net.Http.Server
// 0.5 seconds per request. Respond with a 400 Bad Request.
private const int UnknownHeaderLimit = 1000;
private readonly ConcurrentDictionary<ulong, ConnectionCancellation> _connectionCancellationTokens;
private ILogger _logger;
private SafeHandle _requestQueueHandle;
private ThreadPoolBoundHandle _boundHandle;
private volatile State _state; // m_State is set only within lock blocks, but often read outside locks.
private bool _ignoreWriteExceptions;
private HttpServerSessionHandle _serverSessionHandle;
private ulong _urlGroupId;
private ServerSession _serverSession;
private UrlGroup _urlGroup;
private RequestQueue _requestQueue;
private TimeoutManager _timeoutManager;
private AuthenticationManager _authManager;
private bool _v2Initialized;
private DisconnectListener _disconnectListener;
private object _internalLock;
@ -108,7 +97,33 @@ namespace Microsoft.Net.Http.Server
_urlPrefixes = new UrlPrefixCollection(this);
_timeoutManager = new TimeoutManager(this);
_authManager = new AuthenticationManager(this);
_connectionCancellationTokens = new ConcurrentDictionary<ulong, ConnectionCancellation>();
// V2 initialization sequence:
// 1. Create server session
// 2. Create url group
// 3. Create request queue - Done in Start()
// 4. Add urls to url group - Done in Start()
// 5. Attach request queue to url group - Done in Start()
try
{
_serverSession = new ServerSession();
_urlGroup = new UrlGroup(_serverSession, _logger);
_requestQueue = new RequestQueue(_urlGroup, _logger);
_disconnectListener = new DisconnectListener(_requestQueue, _logger);
}
catch (Exception exception)
{
// If Url group or request queue creation failed, close server session before throwing.
_requestQueue?.Dispose();
_urlGroup?.Dispose();
_serverSession?.Dispose();
LogHelper.LogException(_logger, ".Ctor", exception);
throw;
}
}
internal enum State
@ -134,22 +149,19 @@ namespace Microsoft.Net.Http.Server
set { _bufferResponses = value; }
}
internal SafeHandle RequestQueueHandle
internal UrlGroup UrlGroup
{
get
{
return _requestQueueHandle;
}
get { return _urlGroup; }
}
internal ThreadPoolBoundHandle BoundHandle
internal RequestQueue RequestQueue
{
get { return _boundHandle; }
get { return _requestQueue; }
}
internal ulong UrlGroupId
internal DisconnectListener DisconnectListener
{
get { return _urlGroupId; }
get { return _disconnectListener; }
}
/// <summary>
@ -157,46 +169,27 @@ namespace Microsoft.Net.Http.Server
/// </summary>
public TimeoutManager TimeoutManager
{
get
{
ValidateV2Property();
Debug.Assert(_timeoutManager != null, "Timeout manager is not assigned");
return _timeoutManager;
}
get { return _timeoutManager; }
}
public AuthenticationManager AuthenticationManager
{
get
{
ValidateV2Property();
Debug.Assert(_authManager != null, "Auth manager is not assigned");
return _authManager;
}
get { return _authManager; }
}
internal static bool IsSupported
{
get
{
return UnsafeNclNativeMethods.HttpApi.Supported;
}
get { return UnsafeNclNativeMethods.HttpApi.Supported; }
}
public bool IsListening
{
get
{
return _state == State.Started;
}
get { return _state == State.Started; }
}
internal bool IgnoreWriteExceptions
{
get
{
return _ignoreWriteExceptions;
}
get { return _ignoreWriteExceptions; }
set
{
CheckDisposed();
@ -210,6 +203,7 @@ namespace Microsoft.Net.Http.Server
/// <param name="limit"></param>
public void SetRequestQueueLimit(long limit)
{
CheckDisposed();
if (limit <= 0)
{
throw new ArgumentOutOfRangeException("limit", limit, string.Empty);
@ -221,140 +215,18 @@ namespace Microsoft.Net.Http.Server
}
_requestQueueLength = limit;
SetRequestQueueLimit();
}
private unsafe void SetRequestQueueLimit()
{
// The listener must be active for this to work. Call from Start after activating.
if (!IsListening || !_requestQueueLength.HasValue)
{
return;
}
long length = _requestQueueLength.Value;
uint result = UnsafeNclNativeMethods.HttpApi.HttpSetRequestQueueProperty(_requestQueueHandle,
UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY.HttpServerQueueLengthProperty,
new IntPtr((void*)&length), (uint)Marshal.SizeOf<long>(), 0, IntPtr.Zero);
if (result != 0)
{
throw new WebListenerException((int)result);
}
}
private void ValidateV2Property()
{
// Make sure that calling CheckDisposed and SetupV2Config is an atomic operation. This
// avoids race conditions if the listener is aborted/closed after CheckDisposed(), but
// before SetupV2Config().
lock (_internalLock)
{
CheckDisposed();
SetupV2Config();
}
}
internal void SetUrlGroupProperty(UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize)
{
ValidateV2Property();
uint statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
Debug.Assert(_urlGroupId != 0, "SetUrlGroupProperty called with invalid url group id");
Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer");
// Set the url group property using Http Api.
statusCode = UnsafeNclNativeMethods.HttpApi.HttpSetUrlGroupProperty(
_urlGroupId, property, info, infosize);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
WebListenerException exception = new WebListenerException((int)statusCode);
LogHelper.LogException(_logger, "SetUrlGroupProperty", exception);
throw exception;
}
}
private IntPtr DangerousGetHandle()
{
return _requestQueueHandle.DangerousGetHandle();
}
private unsafe void SetupV2Config()
{
uint statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
ulong id = 0;
// If we have already initialized V2 config, then nothing to do.
if (_v2Initialized)
{
return;
}
// V2 initialization sequence:
// 1. Create server session
// 2. Create url group
// 3. Create request queue - Done in Start()
// 4. Add urls to url group - Done in Start()
// 5. Attach request queue to url group - Done in Start()
try
{
statusCode = UnsafeNclNativeMethods.HttpApi.HttpCreateServerSession(
UnsafeNclNativeMethods.HttpApi.Version, &id, 0);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
throw new WebListenerException((int)statusCode);
}
Debug.Assert(id != 0, "Invalid id returned by HttpCreateServerSession");
_serverSessionHandle = new HttpServerSessionHandle(id);
id = 0;
statusCode = UnsafeNclNativeMethods.HttpApi.HttpCreateUrlGroup(
_serverSessionHandle.DangerousGetServerSessionId(), &id, 0);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
throw new WebListenerException((int)statusCode);
}
Debug.Assert(id != 0, "Invalid id returned by HttpCreateUrlGroup");
_urlGroupId = id;
_v2Initialized = true;
}
catch (Exception exception)
{
// If V2 initialization fails, we mark object as unusable.
_state = State.Disposed;
// If Url group or request queue creation failed, close server session before throwing.
if (_serverSessionHandle != null)
{
_serverSessionHandle.Dispose();
}
LogHelper.LogException(_logger, "SetupV2Config", exception);
throw;
}
_requestQueue.SetLengthLimit(_requestQueueLength.Value);
}
public void Start()
{
CheckDisposed();
// TODO: _logger = LogHelper.CreateLogger(loggerFactory, typeof(OwinWebListener));
LogHelper.LogInfo(_logger, "Start");
// Make sure there are no race conditions between Start/Stop/Abort/Close/Dispose and
// calls to SetupV2Config: Start needs to setup all resources (esp. in V2 where besides
// the request handle, there is also a server session and a Url group. Abort/Stop must
// not interfere while Start is allocating those resources. The lock also makes sure
// all methods changing state can read and change the state in an atomic way.
// Make sure there are no race conditions between Start/Stop/Abort/Close/Dispose.
// Start needs to setup all resources. Abort/Stop must not interfere while Start is
// allocating those resources.
lock (_internalLock)
{
try
@ -365,12 +237,7 @@ namespace Microsoft.Net.Http.Server
return;
}
// SetupV2Config() is not called in the ctor, because it may throw. This would
// be a regression since in v1 the ctor never threw. Besides, ctors should do
// minimal work according to the framework design guidelines.
SetupV2Config();
CreateRequestQueueHandle();
AttachRequestQueueToUrlGroup();
_requestQueue.AttachToUrlGroup();
// All resources are set up correctly. Now add all prefixes.
try
@ -380,99 +247,24 @@ namespace Microsoft.Net.Http.Server
catch (WebListenerException)
{
// If an error occurred while adding prefixes, free all resources allocated by previous steps.
DetachRequestQueueFromUrlGroup();
_requestQueue.DetachFromUrlGroup();
throw;
}
_state = State.Started;
SetRequestQueueLimit();
}
catch (Exception exception)
{
// Make sure the HttpListener instance can't be used if Start() failed.
_state = State.Disposed;
CloseRequestQueueHandle();
CleanupV2Config();
DisposeInternal();
LogHelper.LogException(_logger, "Start", exception);
throw;
}
}
}
private void CleanupV2Config()
{
// If we never setup V2, just return.
if (!_v2Initialized)
{
return;
}
// V2 stopping sequence:
// 1. Detach request queue from url group - Done in Stop()/Abort()
// 2. Remove urls from url group - Done in Stop()
// 3. Close request queue - Done in Stop()/Abort()
// 4. Close Url group.
// 5. Close server session.
Debug.Assert(_urlGroupId != 0, "HttpCloseUrlGroup called with invalid url group id");
uint statusCode = UnsafeNclNativeMethods.HttpApi.HttpCloseUrlGroup(_urlGroupId);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
LogHelper.LogError(_logger, "CleanupV2Config", "Result: " + statusCode);
}
_urlGroupId = 0;
Debug.Assert(_serverSessionHandle != null, "ServerSessionHandle is null in CloseV2Config");
Debug.Assert(!_serverSessionHandle.IsInvalid, "ServerSessionHandle is invalid in CloseV2Config");
_serverSessionHandle.Dispose();
}
private unsafe void AttachRequestQueueToUrlGroup()
{
// Set the association between request queue and url group. After this, requests for registered urls will
// get delivered to this request queue.
UnsafeNclNativeMethods.HttpApi.HTTP_BINDING_INFO info = new UnsafeNclNativeMethods.HttpApi.HTTP_BINDING_INFO();
info.Flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
info.RequestQueueHandle = DangerousGetHandle();
IntPtr infoptr = new IntPtr(&info);
SetUrlGroupProperty(UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
infoptr, (uint)BindingInfoSize);
}
private unsafe void DetachRequestQueueFromUrlGroup()
{
Debug.Assert(_urlGroupId != 0, "DetachRequestQueueFromUrlGroup can't detach using Url group id 0.");
// Break the association between request queue and url group. After this, requests for registered urls
// will get 503s.
// Note that this method may be called multiple times (Stop() and then Abort()). This
// is fine since http.sys allows to set HttpServerBindingProperty multiple times for valid
// Url groups.
UnsafeNclNativeMethods.HttpApi.HTTP_BINDING_INFO info = new UnsafeNclNativeMethods.HttpApi.HTTP_BINDING_INFO();
info.Flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE;
info.RequestQueueHandle = IntPtr.Zero;
IntPtr infoptr = new IntPtr(&info);
uint statusCode = UnsafeNclNativeMethods.HttpApi.HttpSetUrlGroupProperty(_urlGroupId,
UnsafeNclNativeMethods.HttpApi.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
infoptr, (uint)BindingInfoSize);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
LogHelper.LogError(_logger, "DetachRequestQueueFromUrlGroup", "Result: " + statusCode);
}
}
internal void Stop()
private void Stop()
{
try
{
@ -488,13 +280,7 @@ namespace Microsoft.Net.Http.Server
_state = State.Stopped;
DetachRequestQueueFromUrlGroup();
// Even though it would be enough to just detach the request queue in v2, in order to
// keep app compat with earlier versions of the framework, we need to close the request queue.
// This will make sure that pending GetContext() calls will complete and throw an exception. Just
// detaching the url group from the request queue would not cause GetContext() to return.
CloseRequestQueueHandle();
_requestQueue.DetachFromUrlGroup();
}
}
catch (Exception exception)
@ -504,46 +290,6 @@ namespace Microsoft.Net.Http.Server
}
}
private unsafe void CreateRequestQueueHandle()
{
uint statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
HttpRequestQueueV2Handle requestQueueHandle = null;
statusCode =
UnsafeNclNativeMethods.SafeNetHandles.HttpCreateRequestQueue(
UnsafeNclNativeMethods.HttpApi.Version, null, null, 0, out requestQueueHandle);
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
throw new WebListenerException((int)statusCode);
}
// Disabling callbacks when IO operation completes synchronously (returns ErrorCodes.ERROR_SUCCESS)
if (SkipIOCPCallbackOnSuccess &&
!UnsafeNclNativeMethods.SetFileCompletionNotificationModes(
requestQueueHandle,
UnsafeNclNativeMethods.FileCompletionNotificationModes.SkipCompletionPortOnSuccess |
UnsafeNclNativeMethods.FileCompletionNotificationModes.SkipSetEventOnHandle))
{
throw new WebListenerException(Marshal.GetLastWin32Error());
}
_requestQueueHandle = requestQueueHandle;
_boundHandle = ThreadPoolBoundHandle.BindHandle(_requestQueueHandle);
}
private unsafe void CloseRequestQueueHandle()
{
if ((_requestQueueHandle != null) && (!_requestQueueHandle.IsInvalid))
{
_requestQueueHandle.Dispose();
}
if (_boundHandle != null)
{
_boundHandle.Dispose();
}
}
/// <summary>
/// Stop the server and clean up.
/// </summary>
@ -571,7 +317,7 @@ namespace Microsoft.Net.Http.Server
LogHelper.LogInfo(_logger, "Dispose");
Stop();
CleanupV2Config();
DisposeInternal();
}
catch (Exception exception)
{
@ -585,6 +331,25 @@ namespace Microsoft.Net.Http.Server
}
}
private void DisposeInternal()
{
// V2 stopping sequence:
// 1. Detach request queue from url group - Done in Stop()/Abort()
// 2. Remove urls from url group - Done in Stop()
// 3. Close request queue - Done in Stop()/Abort()
// 4. Close Url group.
// 5. Close server session.
_requestQueue.Dispose();
_urlGroup.Dispose();
Debug.Assert(_serverSession != null, "ServerSessionHandle is null in CloseV2Config");
Debug.Assert(!_serverSession.Id.IsInvalid, "ServerSessionHandle is invalid in CloseV2Config");
_serverSession.Dispose();
}
internal unsafe bool ValidateRequest(NativeRequestContext requestMemory)
{
// Block potential DOS attacks
@ -624,7 +389,7 @@ namespace Microsoft.Net.Http.Server
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
// someother bad error, possible(?) return values are:
// some other bad error, possible(?) return values are:
// ERROR_INVALID_HANDLE, ERROR_INSUFFICIENT_BUFFER, ERROR_OPERATION_ABORTED
asyncResult.Dispose();
throw new WebListenerException((int)statusCode);
@ -639,101 +404,6 @@ namespace Microsoft.Net.Http.Server
return asyncResult.Task;
}
internal CancellationToken RegisterForDisconnectNotification(RequestContext requestContext)
{
try
{
// Create exactly one CancellationToken per connection.
ulong connectionId = requestContext.Request.ConnectionId;
return GetConnectionCancellation(connectionId);
}
catch (Win32Exception exception)
{
LogHelper.LogException(_logger, "RegisterForDisconnectNotification", exception);
return CancellationToken.None;
}
}
private CancellationToken GetConnectionCancellation(ulong connectionId)
{
// Read case is performance sensitive
ConnectionCancellation cancellation;
if (!_connectionCancellationTokens.TryGetValue(connectionId, out cancellation))
{
cancellation = GetCreatedConnectionCancellation(connectionId);
}
return cancellation.GetCancellationToken(connectionId);
}
private ConnectionCancellation GetCreatedConnectionCancellation(ulong connectionId)
{
// Race condition on creation has no side effects
ConnectionCancellation cancellation = new ConnectionCancellation(this);
return _connectionCancellationTokens.GetOrAdd(connectionId, cancellation);
}
private unsafe CancellationToken CreateDisconnectToken(ulong connectionId)
{
// Debug.WriteLine("Server: Registering connection for disconnect for connection ID: " + connectionId);
// Create a nativeOverlapped callback so we can register for disconnect callback
var cts = new CancellationTokenSource();
SafeNativeOverlapped nativeOverlapped = null;
nativeOverlapped = new SafeNativeOverlapped(_boundHandle, _boundHandle.AllocateNativeOverlapped(
(errorCode, numBytes, overlappedPtr) =>
{
// Debug.WriteLine("Server: http.sys disconnect callback fired for connection ID: " + connectionId);
// Free the overlapped
nativeOverlapped.Dispose();
// Pull the token out of the list and Cancel it.
ConnectionCancellation token;
_connectionCancellationTokens.TryRemove(connectionId, out token);
try
{
cts.Cancel();
}
catch (AggregateException exception)
{
LogHelper.LogException(_logger, "CreateDisconnectToken::Disconnected", exception);
}
cts.Dispose();
},
null, null));
uint statusCode;
try
{
statusCode = UnsafeNclNativeMethods.HttpApi.HttpWaitForDisconnectEx(requestQueueHandle: _requestQueueHandle,
connectionId: connectionId, reserved: 0, overlapped: nativeOverlapped);
}
catch (Win32Exception exception)
{
statusCode = (uint)exception.NativeErrorCode;
LogHelper.LogException(_logger, "CreateDisconnectToken", exception);
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING &&
statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
// We got an unknown result so return a None
// TODO: return a canceled token?
return CancellationToken.None;
}
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
// TODO: return a canceled token?
return CancellationToken.None;
}
return cts.Token;
}
private unsafe void SendError(ulong requestId, HttpStatusCode httpStatusCode, IList<string> authChallenges)
{
UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_V2 httpResponse = new UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_V2();
@ -809,7 +479,7 @@ namespace Microsoft.Net.Http.Server
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendHttpResponse(
_requestQueueHandle,
_requestQueue.Handle,
requestId,
0,
&httpResponse,
@ -824,7 +494,7 @@ namespace Microsoft.Net.Http.Server
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
// if we fail to send a 401 something's seriously wrong, abort the request
RequestContext.CancelRequest(_requestQueueHandle, requestId);
RequestContext.CancelRequest(_requestQueue.Handle, requestId);
}
}
finally
@ -842,100 +512,6 @@ namespace Microsoft.Net.Http.Server
}
}
private static int GetTokenOffsetFromBlob(IntPtr blob)
{
Debug.Assert(blob != IntPtr.Zero);
IntPtr tokenPointer = Marshal.ReadIntPtr(blob, (int)Marshal.OffsetOf<UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_CHANNEL_BIND_STATUS>("ChannelToken"));
Debug.Assert(tokenPointer != IntPtr.Zero);
return (int)IntPtrHelper.Subtract(tokenPointer, blob);
}
private static int GetTokenSizeFromBlob(IntPtr blob)
{
Debug.Assert(blob != IntPtr.Zero);
return Marshal.ReadInt32(blob, (int)Marshal.OffsetOf<UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_CHANNEL_BIND_STATUS>("ChannelTokenSize"));
}
internal ChannelBinding GetChannelBinding(ulong connectionId, bool isSecureConnection)
{
if (!isSecureConnection)
{
LogHelper.LogInfo(_logger, "Channel binding is not supported for HTTP.");
return null;
}
ChannelBinding result = GetChannelBindingFromTls(connectionId);
Debug.Assert(result != null, "GetChannelBindingFromTls returned null even though OS supposedly supports Extended Protection");
LogHelper.LogInfo(_logger, "Channel binding retrieved.");
return result;
}
private unsafe ChannelBinding GetChannelBindingFromTls(ulong connectionId)
{
// +128 since a CBT is usually <128 thus we need to call HRCC just once. If the CBT
// is >128 we will get ERROR_MORE_DATA and call again
int size = RequestChannelBindStatusSize + 128;
Debug.Assert(size >= 0);
byte[] blob = null;
SafeLocalFreeChannelBinding token = null;
uint bytesReceived = 0;
uint statusCode;
do
{
blob = new byte[size];
fixed (byte* blobPtr = blob)
{
// Http.sys team: ServiceName will always be null if
// HTTP_RECEIVE_SECURE_CHANNEL_TOKEN flag is set.
statusCode = UnsafeNclNativeMethods.HttpApi.HttpReceiveClientCertificate(
RequestQueueHandle,
connectionId,
(uint)UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_RECEIVE_SECURE_CHANNEL_TOKEN,
blobPtr,
(uint)size,
&bytesReceived,
SafeNativeOverlapped.Zero);
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
{
int tokenOffset = GetTokenOffsetFromBlob((IntPtr)blobPtr);
int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr);
Debug.Assert(tokenSize < Int32.MaxValue);
token = SafeLocalFreeChannelBinding.LocalAlloc(tokenSize);
Marshal.Copy(blob, tokenOffset, token.DangerousGetHandle(), tokenSize);
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA)
{
int tokenSize = GetTokenSizeFromBlob((IntPtr)blobPtr);
Debug.Assert(tokenSize < Int32.MaxValue);
size = RequestChannelBindStatusSize + tokenSize;
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER)
{
LogHelper.LogError(_logger, "GetChannelBindingFromTls", "Channel binding is not supported.");
return null; // old schannel library which doesn't support CBT
}
else
{
// It's up to the consumer to fail if the missing ChannelBinding matters to them.
LogHelper.LogException(_logger, "GetChannelBindingFromTls", new WebListenerException((int)statusCode));
break;
}
}
}
while (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS);
return token;
}
internal void CheckDisposed()
{
if (_state == State.Disposed)
@ -943,35 +519,5 @@ namespace Microsoft.Net.Http.Server
throw new ObjectDisposedException(this.GetType().FullName);
}
}
private class ConnectionCancellation
{
private readonly WebListener _parent;
private volatile bool _initialized; // Must be volatile because initialization is synchronized
private CancellationToken _cancellationToken;
public ConnectionCancellation(WebListener parent)
{
_parent = parent;
}
internal CancellationToken GetCancellationToken(ulong connectionId)
{
// Initialized case is performance sensitive
if (_initialized)
{
return _cancellationToken;
}
return InitializeCancellationToken(connectionId);
}
private CancellationToken InitializeCancellationToken(ulong connectionId)
{
object syncObject = this;
#pragma warning disable 420 // Disable warning about volatile by reference since EnsureInitialized does volatile operations
return LazyInitializer.EnsureInitialized(ref _cancellationToken, ref _initialized, ref syncObject, () => _parent.CreateDisconnectToken(connectionId));
#pragma warning restore 420
}
}
}
}

Просмотреть файл

@ -787,7 +787,7 @@ namespace Microsoft.Net.Http.Server
// Responses can be cached for requests with cache-control: no-cache.
// http://tools.ietf.org/html/rfc7234#section-5.2.1.4
[Fact]
[Fact(Skip = "https://github.com/aspnet/WebListener/issues/210")]
public async Task Caching_RequestCacheControlNoCache_Cached()
{
string address;