diff --git a/src/Servers/HttpSys/src/HttpSysListener.cs b/src/Servers/HttpSys/src/HttpSysListener.cs index dcb9023007e5d3025fdcb1281d2fa2d195ead483..42b63fea7137d097a5ebbdc3dfa18bc328b3d41d 100644 --- a/src/Servers/HttpSys/src/HttpSysListener.cs +++ b/src/Servers/HttpSys/src/HttpSysListener.cs @@ -77,9 +77,9 @@ internal partial class HttpSysListener : IDisposable { _serverSession = new ServerSession(); - _urlGroup = new UrlGroup(_serverSession, Logger); + _requestQueue = new RequestQueue(options.RequestQueueName, options.RequestQueueMode, Logger); - _requestQueue = new RequestQueue(_urlGroup, options.RequestQueueName, options.RequestQueueMode, Logger); + _urlGroup = new UrlGroup(_serverSession, _requestQueue, Logger); _disconnectListener = new DisconnectListener(_requestQueue, Logger); } @@ -147,12 +147,12 @@ internal partial class HttpSysListener : IDisposable return; } - // If this instance created the queue then configure it. - if (_requestQueue.Created) + // Always configure the UrlGroup if the intent was to create, only configure the queue if we actually created it + if (Options.RequestQueueMode == RequestQueueMode.Create || Options.RequestQueueMode == RequestQueueMode.CreateOrAttach) { - Options.Apply(UrlGroup, RequestQueue); + Options.Apply(UrlGroup, _requestQueue.Created ? RequestQueue : null); - _requestQueue.AttachToUrlGroup(); + UrlGroup.AttachToQueue(); // All resources are set up correctly. Now add all prefixes. try @@ -162,7 +162,7 @@ internal partial class HttpSysListener : IDisposable catch (HttpSysException) { // If an error occurred while adding prefixes, free all resources allocated by previous steps. - _requestQueue.DetachFromUrlGroup(); + UrlGroup.DetachFromQueue(); throw; } } @@ -194,11 +194,11 @@ internal partial class HttpSysListener : IDisposable Log.ListenerStopping(Logger); - // If this instance created the queue then remove the URL prefixes before shutting down. - if (_requestQueue.Created) + // If this instance registered URL prefixes then remove them before shutting down. + if (Options.RequestQueueMode == RequestQueueMode.Create || Options.RequestQueueMode == RequestQueueMode.CreateOrAttach) { Options.UrlPrefixes.UnregisterAllPrefixes(); - _requestQueue.DetachFromUrlGroup(); + UrlGroup.DetachFromQueue(); } _state = State.Stopped; diff --git a/src/Servers/HttpSys/src/HttpSysOptions.cs b/src/Servers/HttpSys/src/HttpSysOptions.cs index 8db5f9f03982e67eae2872dbe5c51d28720cc7b2..f5eded3da02b417b8b754e0f675c34d0e9b328f8 100644 --- a/src/Servers/HttpSys/src/HttpSysOptions.cs +++ b/src/Servers/HttpSys/src/HttpSysOptions.cs @@ -238,7 +238,7 @@ public class HttpSysOptions public bool UseLatin1RequestHeaders { get; set; } // Not called when attaching to an existing queue. - internal void Apply(UrlGroup urlGroup, RequestQueue requestQueue) + internal void Apply(UrlGroup urlGroup, RequestQueue? requestQueue) { _urlGroup = urlGroup; _requestQueue = requestQueue; @@ -248,14 +248,17 @@ public class HttpSysOptions _urlGroup.SetMaxConnections(_maxConnections.Value); } - if (_requestQueueLength != DefaultRequestQueueLength) + if (_requestQueue is not null) { - _requestQueue.SetLengthLimit(_requestQueueLength); - } + if (_requestQueueLength != DefaultRequestQueueLength) + { + _requestQueue.SetLengthLimit(_requestQueueLength); + } - if (_rejectionVebosityLevel != DefaultRejectionVerbosityLevel) - { - _requestQueue.SetRejectionVerbosity(_rejectionVebosityLevel); + if (_rejectionVebosityLevel != DefaultRejectionVerbosityLevel) + { + _requestQueue.SetRejectionVerbosity(_rejectionVebosityLevel); + } } Authentication.SetUrlGroupSecurity(urlGroup); diff --git a/src/Servers/HttpSys/src/MessagePump.cs b/src/Servers/HttpSys/src/MessagePump.cs index 8420716c267a997635bad90c6d898025d4dbd6d0..61217208faf0ea749bdc437e805128d9937c5b1c 100644 --- a/src/Servers/HttpSys/src/MessagePump.cs +++ b/src/Servers/HttpSys/src/MessagePump.cs @@ -53,7 +53,7 @@ internal partial class MessagePump : IServer if (HttpApi.SupportsDelegation) { - var delegationProperty = new ServerDelegationPropertyFeature(Listener.RequestQueue, _logger); + var delegationProperty = new ServerDelegationPropertyFeature(Listener.UrlGroup, _logger); Features.Set<IServerDelegationFeature>(delegationProperty); } diff --git a/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs b/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs index eb235706b44be40e67fe3f6e6c7e02b0d9a4783e..7b99862f05e39768569c32d8620699347f395f32 100644 --- a/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs +++ b/src/Servers/HttpSys/src/NativeInterop/RequestQueue.cs @@ -10,26 +10,22 @@ namespace Microsoft.AspNetCore.Server.HttpSys; internal sealed partial class RequestQueue { - private static readonly int BindingInfoSize = - Marshal.SizeOf<HttpApiTypes.HTTP_BINDING_INFO>(); - private readonly RequestQueueMode _mode; private readonly ILogger _logger; private bool _disposed; internal RequestQueue(string requestQueueName, ILogger logger) - : this(urlGroup: null, requestQueueName, RequestQueueMode.Attach, logger, receiver: true) + : this(requestQueueName, RequestQueueMode.Attach, logger, receiver: true) { } - internal RequestQueue(UrlGroup urlGroup, string? requestQueueName, RequestQueueMode mode, ILogger logger) - : this(urlGroup, requestQueueName, mode, logger, false) + internal RequestQueue(string? requestQueueName, RequestQueueMode mode, ILogger logger) + : this(requestQueueName, mode, logger, false) { } - private RequestQueue(UrlGroup? urlGroup, string? requestQueueName, RequestQueueMode mode, ILogger logger, bool receiver) + private RequestQueue(string? requestQueueName, RequestQueueMode mode, ILogger logger, bool receiver) { _mode = mode; - UrlGroup = urlGroup; _logger = logger; var flags = HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.None; @@ -106,55 +102,6 @@ internal sealed partial class RequestQueue internal SafeHandle Handle { get; } internal ThreadPoolBoundHandle BoundHandle { get; } - internal UrlGroup? UrlGroup { get; } - - internal unsafe void AttachToUrlGroup() - { - if (UrlGroup == null) - { - throw new NotSupportedException("Can't attach when UrlGroup is null"); - } - - Debug.Assert(Created); - CheckDisposed(); - // 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 HttpApiTypes.HTTP_BINDING_INFO(); - info.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT; - info.RequestQueueHandle = Handle.DangerousGetHandle(); - - var infoptr = new IntPtr(&info); - - UrlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty, - infoptr, (uint)BindingInfoSize); - } - - internal unsafe void DetachFromUrlGroup() - { - if (UrlGroup == null) - { - throw new NotSupportedException("Can't detach when UrlGroup is null"); - } - - Debug.Assert(Created); - CheckDisposed(); - // 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 HttpApiTypes.HTTP_BINDING_INFO(); - info.Flags = HttpApiTypes.HTTP_FLAGS.NONE; - info.RequestQueueHandle = IntPtr.Zero; - - var infoptr = new IntPtr(&info); - - UrlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty, - infoptr, (uint)BindingInfoSize, throwOnError: false); - } - // The listener must be active for this to work. internal unsafe void SetLengthLimit(long length) { diff --git a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs index 06ebd23a8bbc783e1db6e889bb80c1f5ee81aab3..0a446a4523ec24a7bb884dd1f54dbaebb5ae19f3 100644 --- a/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs +++ b/src/Servers/HttpSys/src/NativeInterop/UrlGroup.cs @@ -10,6 +10,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys; internal partial class UrlGroup : IDisposable { + private static readonly int BindingInfoSize = + Marshal.SizeOf<HttpApiTypes.HTTP_BINDING_INFO>(); private static readonly int QosInfoSize = Marshal.SizeOf<HttpApiTypes.HTTP_QOS_SETTING_INFO>(); private static readonly int RequestPropertyInfoSize = @@ -18,12 +20,14 @@ internal partial class UrlGroup : IDisposable private readonly ILogger _logger; private readonly ServerSession? _serverSession; + private readonly RequestQueue _requestQueue; private bool _disposed; private readonly bool _created; - internal unsafe UrlGroup(ServerSession serverSession, ILogger logger) + internal unsafe UrlGroup(ServerSession serverSession, RequestQueue requestQueue, ILogger logger) { _serverSession = serverSession; + _requestQueue = requestQueue; _logger = logger; ulong urlGroupId = 0; @@ -91,6 +95,41 @@ internal partial class UrlGroup : IDisposable } } + internal unsafe void AttachToQueue() + { + CheckDisposed(); + // 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 HttpApiTypes.HTTP_BINDING_INFO(); + info.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT; + info.RequestQueueHandle = _requestQueue.Handle.DangerousGetHandle(); + + var infoptr = new IntPtr(&info); + + SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty, + infoptr, (uint)BindingInfoSize); + } + + internal unsafe void DetachFromQueue() + { + CheckDisposed(); + // 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 HttpApiTypes.HTTP_BINDING_INFO(); + info.Flags = HttpApiTypes.HTTP_FLAGS.NONE; + info.RequestQueueHandle = IntPtr.Zero; + + var infoptr = new IntPtr(&info); + + SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty, + infoptr, (uint)BindingInfoSize, throwOnError: false); + } + internal void RegisterPrefix(string uriPrefix, int contextId) { Log.RegisteringPrefix(_logger, uriPrefix); @@ -101,6 +140,22 @@ internal partial class UrlGroup : IDisposable { if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS) { + // If we didn't create the queue and the uriPrefix already exists, confirm it exists for the + // queue we attached to, if so we are all good, otherwise throw an already registered error. + if (!_requestQueue.Created) + { + unsafe + { + ulong urlGroupId; + var findUrlStatusCode = HttpApi.HttpFindUrlGroupId(uriPrefix, _requestQueue.Handle, &urlGroupId); + if (findUrlStatusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) + { + // Already registered for the desired queue, all good + return; + } + } + } + throw new HttpSysException((int)statusCode, Resources.FormatException_PrefixAlreadyRegistered(uriPrefix)); } if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_ACCESS_DENIED) @@ -111,18 +166,12 @@ internal partial class UrlGroup : IDisposable } } - internal bool UnregisterPrefix(string uriPrefix) + internal void UnregisterPrefix(string uriPrefix) { Log.UnregisteringPrefix(_logger, uriPrefix); CheckDisposed(); - var statusCode = HttpApi.HttpRemoveUrlFromUrlGroup(Id, uriPrefix, 0); - - if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND) - { - return false; - } - return true; + HttpApi.HttpRemoveUrlFromUrlGroup(Id, uriPrefix, 0); } public void Dispose() diff --git a/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs b/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs index ccdb71c1742fcaf16d109987c5d9deeb771cc258..143f0c06fe6929a42a5d0716d5cb4724db078ec4 100644 --- a/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs +++ b/src/Servers/HttpSys/src/ServerDelegationPropertyFeature.cs @@ -10,14 +10,9 @@ internal class ServerDelegationPropertyFeature : IServerDelegationFeature private readonly ILogger _logger; private readonly UrlGroup _urlGroup; - public ServerDelegationPropertyFeature(RequestQueue queue, ILogger logger) + public ServerDelegationPropertyFeature(UrlGroup urlGroup, ILogger logger) { - if (queue.UrlGroup == null) - { - throw new ArgumentException($"{nameof(queue)}.UrlGroup can't be null"); - } - - _urlGroup = queue.UrlGroup; + _urlGroup = urlGroup ?? throw new ArgumentNullException(nameof(urlGroup)); _logger = logger; } diff --git a/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs b/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs index b3db093023533e11f7a6d3d4f0b525646ad829ac..f1792fb824b0f0a236d0f2b65e7030257f217d8e 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/DelegateTests.cs @@ -227,9 +227,7 @@ public class DelegateTests // Stop the receiver receiver?.Dispose(); - // Start the receiver again but this time we need to attach to the existing queue. - // Due to https://github.com/dotnet/aspnetcore/issues/40359, we have to manually - // register URL prefixes and attach the server's queue to them. + // Start the receiver again but this time we need to use CreateOrAttach to attach to the existing queue and setup the UrlPrefixes using var receiverRestarted = (MessagePump)Utilities.CreateHttpServer(out receiverAddress, async httpContext => { await httpContext.Response.WriteAsync(_expectedResponseString); @@ -237,12 +235,10 @@ public class DelegateTests options => { options.RequestQueueName = queueName; - options.RequestQueueMode = RequestQueueMode.Attach; + options.RequestQueueMode = RequestQueueMode.CreateOrAttach; options.UrlPrefixes.Clear(); options.UrlPrefixes.Add(receiverAddress); }); - AttachToUrlGroup(receiverRestarted.Listener.RequestQueue); - receiverRestarted.Listener.Options.UrlPrefixes.RegisterAllPrefixes(receiverRestarted.Listener.UrlGroup); responseString = await SendRequestAsync(delegatorAddress); Assert.Equal(_expectedResponseString, responseString); @@ -250,18 +246,6 @@ public class DelegateTests destination?.Dispose(); } - private unsafe void AttachToUrlGroup(RequestQueue requestQueue) - { - var info = new HttpApiTypes.HTTP_BINDING_INFO(); - info.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT; - info.RequestQueueHandle = requestQueue.Handle.DangerousGetHandle(); - - var infoptr = new IntPtr(&info); - - requestQueue.UrlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty, - infoptr, (uint)Marshal.SizeOf<HttpApiTypes.HTTP_BINDING_INFO>()); - } - private async Task<string> SendRequestAsync(string uri) { using var client = new HttpClient(); diff --git a/src/Servers/HttpSys/test/FunctionalTests/Listener/AuthenticationOnExistingQueueTests.cs b/src/Servers/HttpSys/test/FunctionalTests/Listener/AuthenticationOnExistingQueueTests.cs index aa161858623826be3fd947cdd72f023b8561fd6c..8e1d611801b83ad77ceb7805cee1934d4bd58a11 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Listener/AuthenticationOnExistingQueueTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/Listener/AuthenticationOnExistingQueueTests.cs @@ -10,7 +10,37 @@ using Xunit; namespace Microsoft.AspNetCore.Server.HttpSys.Listener; -public class AuthenticationOnExistingQueueTests +public class AuthenticationOnExistingQueueTests_Attach : AuthenticationOnExistingQueueTests +{ + protected override string ConfigureServer(HttpSysOptions options, string baseServerAddress) + { + options.RequestQueueMode = RequestQueueMode.Attach; + return baseServerAddress; + } +} + +public class AuthenticationOnExistingQueueTests_CreateOrAttach_UseExistingUrlPrefix : AuthenticationOnExistingQueueTests +{ + protected override string ConfigureServer(HttpSysOptions options, string baseServerAddress) + { + options.RequestQueueMode = RequestQueueMode.CreateOrAttach; + return baseServerAddress; + } +} + +public class AuthenticationOnExistingQueueTests_CreateOrAttach_UseNewUrlPrefix : AuthenticationOnExistingQueueTests +{ + protected override string ConfigureServer(HttpSysOptions options, string baseServerAddress) + { + options.RequestQueueMode = RequestQueueMode.CreateOrAttach; + var basePrefix = UrlPrefix.Create(baseServerAddress); + var prefix = UrlPrefix.Create(basePrefix.Scheme, basePrefix.Host, basePrefix.Port, "/server"); + options.UrlPrefixes.Add(prefix); + return prefix.ToString(); + } +} + +public abstract class AuthenticationOnExistingQueueTests { private static readonly bool AllowAnoymous = true; private static readonly bool DenyAnoymous = false; @@ -24,8 +54,8 @@ public class AuthenticationOnExistingQueueTests [InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationSchemes.Digest |*/ AuthenticationSchemes.Basic)] public async Task AuthTypes_AllowAnonymous_NoChallenge(AuthenticationSchemes authType) { - using var baseServer = Utilities.CreateHttpAuthServer(authType, AllowAnoymous, out var address); - using var server = Utilities.CreateServerOnExistingQueue(authType, AllowAnoymous, baseServer.Options.RequestQueueName); + using var baseServer = CreateHttpAuthServer(authType, AllowAnoymous); + using var server = CreateServerOnExistingQueue(authType, AllowAnoymous, baseServer, out var address); Task<HttpResponseMessage> responseTask = SendRequestAsync(address); @@ -47,8 +77,8 @@ public class AuthenticationOnExistingQueueTests [InlineData(AuthenticationSchemes.Basic)] public async Task AuthType_RequireAuth_ChallengesAdded(AuthenticationSchemes authType) { - using var baseServer = Utilities.CreateHttpAuthServer(authType, DenyAnoymous, out var address); - using var server = Utilities.CreateServerOnExistingQueue(authType, DenyAnoymous, baseServer.Options.RequestQueueName); + using var baseServer = CreateHttpAuthServer(authType, DenyAnoymous); + using var server = CreateServerOnExistingQueue(authType, DenyAnoymous, baseServer, out var address); Task<HttpResponseMessage> responseTask = SendRequestAsync(address); @@ -65,8 +95,8 @@ public class AuthenticationOnExistingQueueTests [InlineData(AuthenticationSchemes.Basic)] public async Task AuthType_AllowAnonymousButSpecify401_ChallengesAdded(AuthenticationSchemes authType) { - using var baseServer = Utilities.CreateHttpAuthServer(authType, AllowAnoymous, out var address); - using var server = Utilities.CreateServerOnExistingQueue(authType, AllowAnoymous, baseServer.Options.RequestQueueName); + using var baseServer = CreateHttpAuthServer(authType, AllowAnoymous); + using var server = CreateServerOnExistingQueue(authType, AllowAnoymous, baseServer, out var address); Task<HttpResponseMessage> responseTask = SendRequestAsync(address); @@ -90,8 +120,8 @@ public class AuthenticationOnExistingQueueTests | AuthenticationSchemes.NTLM /* | AuthenticationSchemes.Digest TODO: Not implemented */ | AuthenticationSchemes.Basic; - using var baseServer = Utilities.CreateHttpAuthServer(authType, AllowAnoymous, out var address); - using var server = Utilities.CreateServerOnExistingQueue(authType, AllowAnoymous, baseServer.Options.RequestQueueName); + using var baseServer = CreateHttpAuthServer(authType, AllowAnoymous); + using var server = CreateServerOnExistingQueue(authType, AllowAnoymous, baseServer, out var address); Task<HttpResponseMessage> responseTask = SendRequestAsync(address); @@ -115,8 +145,8 @@ public class AuthenticationOnExistingQueueTests [InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationType.Digest |*/ AuthenticationSchemes.Basic)] public async Task AuthTypes_AllowAnonymousButSpecify401_Success(AuthenticationSchemes authType) { - using var baseServer = Utilities.CreateHttpAuthServer(authType, AllowAnoymous, out var address); - using var server = Utilities.CreateServerOnExistingQueue(authType, AllowAnoymous, baseServer.Options.RequestQueueName); + using var baseServer = CreateHttpAuthServer(authType, AllowAnoymous); + using var server = CreateServerOnExistingQueue(authType, AllowAnoymous, baseServer, out var address); Task<HttpResponseMessage> responseTask = SendRequestAsync(address, useDefaultCredentials: true); @@ -145,8 +175,8 @@ public class AuthenticationOnExistingQueueTests [InlineData(AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM | /*AuthenticationType.Digest |*/ AuthenticationSchemes.Basic)] public async Task AuthTypes_RequireAuth_Success(AuthenticationSchemes authType) { - using var baseServer = Utilities.CreateHttpAuthServer(authType, DenyAnoymous, out var address); - using var server = Utilities.CreateServerOnExistingQueue(authType, DenyAnoymous, baseServer.Options.RequestQueueName); + using var baseServer = CreateHttpAuthServer(authType, DenyAnoymous); + using var server = CreateServerOnExistingQueue(authType, DenyAnoymous, baseServer, out var address); Task<HttpResponseMessage> responseTask = SendRequestAsync(address, useDefaultCredentials: true); @@ -160,6 +190,32 @@ public class AuthenticationOnExistingQueueTests Assert.Equal(HttpStatusCode.OK, response.StatusCode); } + protected abstract string ConfigureServer(HttpSysOptions options, string baseServerAddress); + + private HttpSysListener CreateHttpAuthServer(AuthenticationSchemes authType, bool allowAnonymous) + { + var server = Utilities.CreateDynamicHttpServer("/baseServer", out var root, out var baseAddress); + server.Options.Authentication.Schemes = authType; + server.Options.Authentication.AllowAnonymous = allowAnonymous; + return server; + } + + private HttpSysListener CreateServerOnExistingQueue(AuthenticationSchemes authScheme, bool allowAnonymos, HttpSysListener baseServer, out string address) + { + string serverAddress = null; + var baseServerAddress = baseServer.Options.UrlPrefixes.First().ToString(); + var server = Utilities.CreateServer(options => + { + options.RequestQueueName = baseServer.Options.RequestQueueName; + options.Authentication.Schemes = authScheme; + options.Authentication.AllowAnonymous = allowAnonymos; + serverAddress = ConfigureServer(options, baseServerAddress); + }); + + address = serverAddress; + return server; + } + private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool useDefaultCredentials = false) { HttpClientHandler handler = new HttpClientHandler(); diff --git a/src/Servers/HttpSys/test/FunctionalTests/Listener/ServerOnExistingQueueTests.cs b/src/Servers/HttpSys/test/FunctionalTests/Listener/ServerOnExistingQueueTests.cs index 0d62957ee31ef8746e4bfe3d5d125f001d09715a..c4ae575303fed4d185541912b40491dd1f600e2c 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Listener/ServerOnExistingQueueTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/Listener/ServerOnExistingQueueTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.HttpSys.Internal; using Microsoft.AspNetCore.Testing; using Xunit; @@ -232,6 +233,88 @@ public class ServerOnExistingQueueTests Assert.Equal(string.Empty, response); } + [ConditionalFact] + public async Task Server_CreateOrAttach_NoUrlPrefix_NewUrlPrefixWorks() + { + var queueName = Guid.NewGuid().ToString(); + + // Create a queue without a UrlGroup or any UrlPrefixes + HttpRequestQueueV2Handle requestQueueHandle = null; + var statusCode = HttpApi.HttpCreateRequestQueue( + HttpApi.Version, + queueName, + null, + 0, + out requestQueueHandle); + + Assert.True(statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS); + + using var server = Utilities.CreateServer(options => + { + options.RequestQueueName = queueName; + options.RequestQueueMode = RequestQueueMode.CreateOrAttach; + options.UrlPrefixes.Add("http://localhost:0"); + }); + + var address = server.Options.UrlPrefixes.First().FullPrefix; + + var responseTask = SendRequestAsync(address); + + var context = await server.AcceptAsync(Utilities.DefaultTimeout); + context.Dispose(); + + var response = await responseTask; + Assert.Equal(string.Empty, response); + } + + [ConditionalFact] + public async Task Server_CreateOrAttach_UrlPrefixExist_ExistingUrlPrefixWorks() + { + using var baseServer = Utilities.CreateHttpServer(out var address); + using var server = Utilities.CreateServer(options => + { + options.RequestQueueName = baseServer.Options.RequestQueueName; + options.RequestQueueMode = RequestQueueMode.CreateOrAttach; + options.UrlPrefixes.Add(address); + }); + + var responseTask = SendRequestAsync(address); + + var context = await server.AcceptAsync(Utilities.DefaultTimeout); + context.Dispose(); + + var response = await responseTask; + Assert.Equal(string.Empty, response); + } + + [ConditionalFact] + public async Task Server_CreateOrAttach_UrlPrefixExist_NewAndExistingUrlPrefixsWork() + { + using var baseServer = Utilities.CreateHttpServerReturnRoot("/baseServer", out string rootAddress); + using var server = Utilities.CreateServer(options => + { + options.RequestQueueName = baseServer.Options.RequestQueueName; + options.RequestQueueMode = RequestQueueMode.CreateOrAttach; + options.UrlPrefixes.Add(rootAddress + "/server"); + }); + + var responseTask = SendRequestAsync(rootAddress + "/baseServer"); + + var context = await server.AcceptAsync(Utilities.DefaultTimeout); + context.Dispose(); + + var response = await responseTask; + Assert.Equal(string.Empty, response); + + responseTask = SendRequestAsync(rootAddress + "/server"); + + context = await server.AcceptAsync(Utilities.DefaultTimeout); + context.Dispose(); + + response = await responseTask; + Assert.Equal(string.Empty, response); + } + private async Task<string> SendRequestAsync(string uri) { using HttpClient client = new HttpClient(); diff --git a/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs b/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs index 696fce6f826e83f1ae78293f619fa9cba24665c3..15664bf82119d4e770d65b025a0165004cf0790d 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs @@ -22,14 +22,6 @@ internal static class Utilities internal static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); - internal static HttpSysListener CreateHttpAuthServer(AuthenticationSchemes authType, bool allowAnonymous, out string baseAddress) - { - var listener = CreateHttpServer(out baseAddress); - listener.Options.Authentication.Schemes = authType; - listener.Options.Authentication.AllowAnonymous = allowAnonymous; - return listener; - } - internal static HttpSysListener CreateHttpServer(out string baseAddress) { string root; @@ -90,23 +82,24 @@ internal static class Utilities return listener; } - internal static HttpSysListener CreateServerOnExistingQueue(string requestQueueName) - { - return CreateServerOnExistingQueue(AuthenticationSchemes.None, true, requestQueueName); - } - - internal static HttpSysListener CreateServerOnExistingQueue(AuthenticationSchemes authScheme, bool allowAnonymos, string requestQueueName) + internal static HttpSysListener CreateServer(Action<HttpSysOptions> configureOptions) { var options = new HttpSysOptions(); - options.RequestQueueMode = RequestQueueMode.Attach; - options.RequestQueueName = requestQueueName; - options.Authentication.Schemes = authScheme; - options.Authentication.AllowAnonymous = allowAnonymos; + configureOptions(options); var listener = new HttpSysListener(options, new LoggerFactory()); listener.Start(); return listener; } + internal static HttpSysListener CreateServerOnExistingQueue(string requestQueueName) + { + return CreateServer(options => + { + options.RequestQueueName = requestQueueName; + options.RequestQueueMode = RequestQueueMode.Attach; + }); + } + /// <summary> /// AcceptAsync extension with timeout. This extension should be used in all tests to prevent /// unexpected hangs when a request does not arrive. diff --git a/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs b/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs index 39c1553a70569192ade766313cd49c4f35782e12..ff897a01e564d1ca8a3df252f5b712b856eae0b0 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/ServerTests.cs @@ -36,8 +36,10 @@ public class ServerTests } } - [ConditionalFact] - public async Task Server_ConnectExistingQueueName_Success() + [ConditionalTheory] + [InlineData(RequestQueueMode.Attach)] + [InlineData(RequestQueueMode.CreateOrAttach)] + public async Task Server_ConnectExistingQueueName_Success(RequestQueueMode queueMode) { string address; var queueName = Guid.NewGuid().ToString(); @@ -60,7 +62,7 @@ public class ServerTests }, options => { options.RequestQueueName = queueName; - options.RequestQueueMode = RequestQueueMode.Attach; + options.RequestQueueMode = queueMode; })) { var psi = new ProcessStartInfo("netsh", "http show servicestate view=requestq") @@ -71,6 +73,20 @@ public class ServerTests process.Start(); var netshOutput = await process.StandardOutput.ReadToEndAsync(); Assert.Contains(queueName, netshOutput); + + var prefix = UrlPrefix.Create(address); + switch (queueMode) + { + case RequestQueueMode.Attach: + Assert.Equal("0", prefix.Port); + + break; + case RequestQueueMode.CreateOrAttach: + Assert.NotEqual("0", prefix.Port); + Assert.Contains(address, netshOutput, StringComparison.OrdinalIgnoreCase); + + break; + } } } @@ -587,8 +603,10 @@ public class ServerTests } } - [ConditionalFact] - public async Task Server_AttachToExistingQueue_NoIServerAddresses_NoDefaultAdded() + [ConditionalTheory] + [InlineData(RequestQueueMode.Attach)] + [InlineData(RequestQueueMode.CreateOrAttach)] + public async Task Server_AttachToExistingQueue_NoIServerAddresses_NoDefaultAdded(RequestQueueMode queueMode) { var queueName = Guid.NewGuid().ToString(); using var server = Utilities.CreateHttpServer(out var address, httpContext => Task.CompletedTask, options => @@ -598,7 +616,7 @@ public class ServerTests using var attachedServer = Utilities.CreatePump(options => { options.RequestQueueName = queueName; - options.RequestQueueMode = RequestQueueMode.Attach; + options.RequestQueueMode = queueMode; }); await attachedServer.StartAsync(new DummyApplication(context => Task.CompletedTask), default); var addressesFeature = attachedServer.Features.Get<IServerAddressesFeature>();