#39 - Hot add/remove prefixes. #13 - Support Add/Remove(string).

This commit is contained in:
Chris Ross 2014-10-21 12:20:17 -07:00
parent 15344d8d7a
commit 46a11be2ed
5 changed files with 247 additions and 86 deletions

View File

@ -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.

View File

@ -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()

View File

@ -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
{
/// <summary>
/// A collection or URL prefixes
/// </summary>
public class UrlPrefixCollection : ICollection<UrlPrefix>
{
private readonly WebListener _webListener;
private readonly IDictionary<int, UrlPrefix> _prefixes = new Dictionary<int, UrlPrefix>(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<UrlPrefix> 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;
}
}
}

View File

@ -85,7 +85,7 @@ namespace Microsoft.Net.Http.Server
private object _internalLock;
private List<UrlPrefix> _urlPrefixes = new List<UrlPrefix>();
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<ulong, ConnectionCancellation>();
@ -120,7 +121,7 @@ namespace Microsoft.Net.Http.Server
get { return _logger; }
}
public List<UrlPrefix> UrlPrefixes
public UrlPrefixCollection UrlPrefixes
{
get { return _urlPrefixes; }
}
@ -133,6 +134,11 @@ namespace Microsoft.Net.Http.Server
}
}
internal ulong UrlGroupId
{
get { return _urlGroupId; }
}
/// <summary>
/// Exposes the Http.Sys timeout configurations. These may also be configured in the registry.
/// </summary>
@ -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))
{

View File

@ -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<string> SendRequestAsync(string uri)
{
ServicePointManager.DefaultConnectionLimit = 100;