From 46a11be2edf57a54d6ebec42e0bba156c3eee272 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Tue, 21 Oct 2014 12:20:17 -0700 Subject: [PATCH] #39 - Hot add/remove prefixes. #13 - Support Add/Remove(string). --- .../RequestProcessing/Request.cs | 2 +- src/Microsoft.Net.Http.Server/UrlPrefix.cs | 2 +- .../UrlPrefixCollection.cs | 171 ++++++++++++++++++ src/Microsoft.Net.Http.Server/WebListener.cs | 95 ++-------- .../ServerTests.cs | 63 +++++++ 5 files changed, 247 insertions(+), 86 deletions(-) create mode 100644 src/Microsoft.Net.Http.Server/UrlPrefixCollection.cs diff --git a/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs b/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs index 015cb98e47..750d4425d3 100644 --- a/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs +++ b/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs @@ -106,7 +106,7 @@ namespace Microsoft.Net.Http.Server _cookedUrlQuery = Marshal.PtrToStringUni((IntPtr)cookedUrl.pQueryString, cookedUrl.QueryStringLength / 2); } - UrlPrefix prefix = httpContext.Server.UrlPrefixes[(int)_contextId]; + UrlPrefix prefix = httpContext.Server.UrlPrefixes.GetPrefix((int)_contextId); string orriginalPath = RequestPath; // These paths are both unescaped already. diff --git a/src/Microsoft.Net.Http.Server/UrlPrefix.cs b/src/Microsoft.Net.Http.Server/UrlPrefix.cs index 24cf55fd0e..0c53cc67c9 100644 --- a/src/Microsoft.Net.Http.Server/UrlPrefix.cs +++ b/src/Microsoft.Net.Http.Server/UrlPrefix.cs @@ -166,7 +166,7 @@ namespace Microsoft.Net.Http.Server public override bool Equals(object obj) { - return string.Equals(Whole, obj as string, StringComparison.OrdinalIgnoreCase); + return string.Equals(Whole, Convert.ToString(obj), StringComparison.OrdinalIgnoreCase); } public override int GetHashCode() diff --git a/src/Microsoft.Net.Http.Server/UrlPrefixCollection.cs b/src/Microsoft.Net.Http.Server/UrlPrefixCollection.cs new file mode 100644 index 0000000000..9165960a63 --- /dev/null +++ b/src/Microsoft.Net.Http.Server/UrlPrefixCollection.cs @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Net.Http.Server +{ + /// + /// A collection or URL prefixes + /// + public class UrlPrefixCollection : ICollection + { + private readonly WebListener _webListener; + private readonly IDictionary _prefixes = new Dictionary(1); + private int _nextId = 1; + + internal UrlPrefixCollection(WebListener webListener) + { + _webListener = webListener; + } + + public int Count + { + get { return _prefixes.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + public void Add(string prefix) + { + Add(UrlPrefix.Create(prefix)); + } + + public void Add(UrlPrefix item) + { + var id = _nextId++; + if (_webListener.IsListening) + { + RegisterPrefix(item.Whole, id); + } + _prefixes.Add(id, item); + } + + internal UrlPrefix GetPrefix(int id) + { + return _prefixes[id]; + } + + public void Clear() + { + if (_webListener.IsListening) + { + UnregisterAllPrefixes(); + } + _prefixes.Clear(); + } + + public bool Contains(UrlPrefix item) + { + return _prefixes.Values.Contains(item); + } + + public void CopyTo(UrlPrefix[] array, int arrayIndex) + { + _prefixes.Values.CopyTo(array, arrayIndex); + } + + public bool Remove(string prefix) + { + return Remove(UrlPrefix.Create(prefix)); + } + + public bool Remove(UrlPrefix item) + { + int? id = null; + foreach (var pair in _prefixes) + { + if (pair.Value.Equals(item)) + { + id = pair.Key; + if (_webListener.IsListening) + { + UnregisterPrefix(pair.Value.Whole); + } + } + } + if (id.HasValue) + { + _prefixes.Remove(id.Value); + return true; + } + return false; + } + + public IEnumerator GetEnumerator() + { + return _prefixes.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + internal void RegisterAllPrefixes() + { + // go through the uri list and register for each one of them + 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); + } + } + + internal void UnregisterAllPrefixes() + { + // go through the uri list and unregister for each one of them + foreach (var prefix in _prefixes.Values) + { + // ignore possible failures + UnregisterPrefix(prefix.Whole); + } + } + + private void RegisterPrefix(string uriPrefix, int contextId) + { + uint statusCode = 0; + + 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; + + statusCode = + UnsafeNclNativeMethods.HttpApi.HttpRemoveUrlFromUrlGroup( + _webListener.UrlGroupId, + uriPrefix, + 0); + + if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND) + { + return false; + } + return true; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Net.Http.Server/WebListener.cs b/src/Microsoft.Net.Http.Server/WebListener.cs index 57bafd0c49..54cb36fd20 100644 --- a/src/Microsoft.Net.Http.Server/WebListener.cs +++ b/src/Microsoft.Net.Http.Server/WebListener.cs @@ -85,7 +85,7 @@ namespace Microsoft.Net.Http.Server private object _internalLock; - private List _urlPrefixes = new List(); + private UrlPrefixCollection _urlPrefixes; // The native request queue private long? _requestQueueLength; @@ -103,6 +103,7 @@ namespace Microsoft.Net.Http.Server _state = State.Stopped; _internalLock = new object(); + _urlPrefixes = new UrlPrefixCollection(this); _timeoutManager = new TimeoutManager(this); _authManager = new AuthenticationManager(this); _connectionCancellationTokens = new ConcurrentDictionary(); @@ -120,7 +121,7 @@ namespace Microsoft.Net.Http.Server get { return _logger; } } - public List UrlPrefixes + public UrlPrefixCollection UrlPrefixes { get { return _urlPrefixes; } } @@ -133,6 +134,11 @@ namespace Microsoft.Net.Http.Server } } + internal ulong UrlGroupId + { + get { return _urlGroupId; } + } + /// /// Exposes the Http.Sys timeout configurations. These may also be configured in the registry. /// @@ -258,29 +264,6 @@ namespace Microsoft.Net.Http.Server } } - internal void RemoveAll(bool clear) - { - CheckDisposed(); - // go through the uri list and unregister for each one of them - if (_urlPrefixes.Count > 0) - { - LogHelper.LogInfo(_logger, "RemoveAll"); - if (_state == State.Started) - { - foreach (UrlPrefix registeredPrefix in _urlPrefixes) - { - // ignore possible failures - InternalRemovePrefix(registeredPrefix.Whole); - } - } - - if (clear) - { - _urlPrefixes.Clear(); - } - } - } - private IntPtr DangerousGetHandle() { return _requestQueueHandle.DangerousGetHandle(); @@ -379,7 +362,7 @@ namespace Microsoft.Net.Http.Server // All resources are set up correctly. Now add all prefixes. try { - AddAllPrefixes(); + _urlPrefixes.RegisterAllPrefixes(); } catch (WebListenerException) { @@ -489,7 +472,7 @@ namespace Microsoft.Net.Http.Server } LogHelper.LogInfo(_logger, "Stop"); - RemoveAll(false); + _urlPrefixes.UnregisterAllPrefixes(); _state = State.Stopped; @@ -586,62 +569,6 @@ namespace Microsoft.Net.Http.Server } } - private uint InternalAddPrefix(string uriPrefix, int contextId) - { - uint statusCode = 0; - - statusCode = - UnsafeNclNativeMethods.HttpApi.HttpAddUrlToUrlGroup( - _urlGroupId, - uriPrefix, - (ulong)contextId, - 0); - - return statusCode; - } - - private bool InternalRemovePrefix(string uriPrefix) - { - uint statusCode = 0; - - statusCode = - UnsafeNclNativeMethods.HttpApi.HttpRemoveUrlFromUrlGroup( - _urlGroupId, - uriPrefix, - 0); - - if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_NOT_FOUND) - { - return false; - } - return true; - } - - private void AddAllPrefixes() - { - // go through the uri list and register for each one of them - if (_urlPrefixes.Count > 0) - { - for (int i = 0; i < _urlPrefixes.Count; i++) - { - // We'll get this index back on each request and use it to look up the prefix to calculate PathBase. - UrlPrefix registeredPrefix = _urlPrefixes[i]; - uint statusCode = InternalAddPrefix(registeredPrefix.Whole, i); - if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS) - { - if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS) - { - throw new WebListenerException((int)statusCode, String.Format(Resources.Exception_PrefixAlreadyRegistered, registeredPrefix.Whole)); - } - else - { - throw new WebListenerException((int)statusCode); - } - } - } - } - } - internal unsafe bool ValidateRequest(NativeRequestContext requestMemory) { // Block potential DOS attacks @@ -713,7 +640,7 @@ namespace Microsoft.Net.Http.Server private CancellationToken GetConnectionCancellation(ulong connectionId) { - // Read case is performance senstive + // Read case is performance sensitive ConnectionCancellation cancellation; if (!_connectionCancellationTokens.TryGetValue(connectionId, out cancellation)) { diff --git a/test/Microsoft.Net.Http.Server.FunctionalTests/ServerTests.cs b/test/Microsoft.Net.Http.Server.FunctionalTests/ServerTests.cs index 12e9282073..8b9ed10838 100644 --- a/test/Microsoft.Net.Http.Server.FunctionalTests/ServerTests.cs +++ b/test/Microsoft.Net.Http.Server.FunctionalTests/ServerTests.cs @@ -181,6 +181,69 @@ namespace Microsoft.Net.Http.Server } } + [Fact] + public async Task Server_HotAddPrefix_Success() + { + string address; + using (var server = Utilities.CreateHttpServer(out address)) + { + var responseTask = SendRequestAsync(address); + + var context = await server.GetContextAsync(); + Assert.Equal(string.Empty, context.Request.PathBase); + Assert.Equal("/", context.Request.Path); + context.Dispose(); + + var response = await responseTask; + Assert.Equal(string.Empty, response); + + address += "pathbase/"; + server.UrlPrefixes.Add(address); + + responseTask = SendRequestAsync(address); + + context = await server.GetContextAsync(); + Assert.Equal("/pathbase", context.Request.PathBase); + Assert.Equal("/", context.Request.Path); + context.Dispose(); + + response = await responseTask; + Assert.Equal(string.Empty, response); + } + } + + [Fact] + public async Task Server_HotRemovePrefix_Success() + { + string address; + using (var server = Utilities.CreateHttpServer(out address)) + { + address += "pathbase/"; + server.UrlPrefixes.Add(address); + var responseTask = SendRequestAsync(address); + + var context = await server.GetContextAsync(); + Assert.Equal("/pathbase", context.Request.PathBase); + Assert.Equal("/", context.Request.Path); + context.Dispose(); + + var response = await responseTask; + Assert.Equal(string.Empty, response); + + Assert.True(server.UrlPrefixes.Remove(address)); + + responseTask = SendRequestAsync(address); + + context = await server.GetContextAsync(); + Assert.Equal(string.Empty, context.Request.PathBase); + Assert.Equal("/pathbase/", context.Request.Path); + context.Dispose(); + + response = await responseTask; + Assert.Equal(string.Empty, response); + } + } + private async Task SendRequestAsync(string uri) { ServicePointManager.DefaultConnectionLimit = 100;