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;