| File: NativeInterop\RequestQueue.cs | Web Access |
| Project: src\aspnetcore\src\Servers\HttpSys\src\Microsoft.AspNetCore.Server.HttpSys.csproj (Microsoft.AspNetCore.Server.HttpSys) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.AccessControl; using Microsoft.Extensions.Logging; using Windows.Win32; using Windows.Win32.Networking.HttpServer; using Windows.Win32.Security; namespace Microsoft.AspNetCore.Server.HttpSys; internal sealed partial class RequestQueue { private readonly RequestQueueMode _mode; private readonly ILogger _logger; private bool _disposed; internal RequestQueue(string requestQueueName, ILogger logger) : this(requestQueueName, RequestQueueMode.Attach, securityDescriptor: null, logger, receiver: true) { } internal RequestQueue(string? requestQueueName, RequestQueueMode mode, GenericSecurityDescriptor? securityDescriptor, ILogger logger) : this(requestQueueName, mode, securityDescriptor, logger, false) { } private RequestQueue(string? requestQueueName, RequestQueueMode mode, GenericSecurityDescriptor? securityDescriptor, ILogger logger, bool receiver) { _mode = mode; _logger = logger; var flags = 0u; Created = true; SECURITY_ATTRIBUTES? securityAttributes = null; nint? pSecurityDescriptor = null; try { if (_mode == RequestQueueMode.Attach) { flags = PInvoke.HTTP_CREATE_REQUEST_QUEUE_FLAG_OPEN_EXISTING; Created = false; if (receiver) { flags |= PInvoke.HTTP_CREATE_REQUEST_QUEUE_FLAG_DELEGATION; } } else if (securityDescriptor is not null) // Create or CreateOrAttach { // Convert the security descriptor to a byte array byte[] securityDescriptorBytes = new byte[securityDescriptor.BinaryLength]; securityDescriptor.GetBinaryForm(securityDescriptorBytes, 0); // Allocate native memory for the security descriptor pSecurityDescriptor = Marshal.AllocHGlobal(securityDescriptorBytes.Length); Marshal.Copy(securityDescriptorBytes, 0, pSecurityDescriptor.Value, securityDescriptorBytes.Length); unsafe { securityAttributes = new SECURITY_ATTRIBUTES { nLength = (uint)Marshal.SizeOf<SECURITY_ATTRIBUTES>(), lpSecurityDescriptor = pSecurityDescriptor.Value.ToPointer(), bInheritHandle = false }; } } var statusCode = PInvoke.HttpCreateRequestQueue( HttpApi.Version, requestQueueName, securityAttributes, flags, out var requestQueueHandle); if (_mode == RequestQueueMode.CreateOrAttach && statusCode == ErrorCodes.ERROR_ALREADY_EXISTS) { // Tried to create, but it already exists so attach to it instead. Created = false; flags = PInvoke.HTTP_CREATE_REQUEST_QUEUE_FLAG_OPEN_EXISTING; statusCode = PInvoke.HttpCreateRequestQueue( HttpApi.Version, requestQueueName, SecurityAttributes: default, // Attaching should not pass any security attributes flags, out requestQueueHandle); } if ((flags & PInvoke.HTTP_CREATE_REQUEST_QUEUE_FLAG_OPEN_EXISTING) != 0 && statusCode == ErrorCodes.ERROR_FILE_NOT_FOUND) { throw new HttpSysException((int)statusCode, $"Failed to attach to the given request queue '{requestQueueName}', the queue could not be found."); } else if (statusCode == ErrorCodes.ERROR_INVALID_NAME) { throw new HttpSysException((int)statusCode, $"The given request queue name '{requestQueueName}' is invalid."); } else if (statusCode != ErrorCodes.ERROR_SUCCESS) { throw new HttpSysException((int)statusCode); } // Disabling callbacks when IO operation completes synchronously (returns ErrorCodes.ERROR_SUCCESS) if (HttpSysListener.SkipIOCPCallbackOnSuccess && !PInvoke.SetFileCompletionNotificationModes( requestQueueHandle, (byte)(PInvoke.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS | PInvoke.FILE_SKIP_SET_EVENT_ON_HANDLE))) { requestQueueHandle.Dispose(); throw new HttpSysException(Marshal.GetLastWin32Error()); } Handle = requestQueueHandle; BoundHandle = ThreadPoolBoundHandle.BindHandle(Handle); if (!Created) { Log.AttachedToQueue(_logger, requestQueueName); } } finally { if (pSecurityDescriptor is not null) { // Free the allocated memory for the security descriptor Marshal.FreeHGlobal(pSecurityDescriptor.Value); } } } /// <summary> /// True if this instance created the queue instead of attaching to an existing one. /// </summary> internal bool Created { get; } internal SafeHandle Handle { get; } internal ThreadPoolBoundHandle BoundHandle { get; } // The listener must be active for this to work. internal unsafe void SetLengthLimit(long length) { Debug.Assert(Created); CheckDisposed(); var result = PInvoke.HttpSetRequestQueueProperty(Handle, HTTP_SERVER_PROPERTY.HttpServerQueueLengthProperty, new ReadOnlySpan<byte>(&length, sizeof(long))); if (result != 0) { throw new HttpSysException((int)result); } } // The listener must be active for this to work. internal unsafe void SetRejectionVerbosity(Http503VerbosityLevel verbosity) { Debug.Assert(Created); CheckDisposed(); var verbosityLong = (long)verbosity; var result = PInvoke.HttpSetRequestQueueProperty(Handle, HTTP_SERVER_PROPERTY.HttpServer503VerbosityProperty, new ReadOnlySpan<byte>(&verbosityLong, sizeof(long))); if (result != 0) { throw new HttpSysException((int)result); } } public void Dispose() { if (_disposed) { return; } _disposed = true; // Close the request queue handle first to cancel any pending IO operations. // This must happen before disposing BoundHandle, as pending operations need // to be cancelled and cleaned up using the BoundHandle. Handle.Dispose(); BoundHandle.Dispose(); } /// <summary> /// Shuts down the request queue to cancel any pending IO operations, /// but does not close the handle or dispose the BoundHandle. This allows pending operations /// to be properly cleaned up before disposing the BoundHandle. /// </summary> internal void StopProcessingRequests() { PInvoke.HttpShutdownRequestQueue(Handle); } private void CheckDisposed() { ObjectDisposedException.ThrowIf(_disposed, this); } private static partial class Log { [LoggerMessage(LoggerEventIds.AttachedToQueue, LogLevel.Information, "Attached to an existing request queue '{RequestQueueName}', some options do not apply.", EventName = "AttachedToQueue")] public static partial void AttachedToQueue(ILogger logger, string? requestQueueName); } }