Factoring out UrlGroup, ServerSession, RequestQueue, ChannelBinding,
Disconnect Listener.
This commit is contained in:
Родитель
07b078d4e3
Коммит
3d2e1c4d3e
|
@ -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;
|
||||
|
|
Загрузка…
Ссылка в новой задаче