Implementing and testing request-to-shell mapping in RunningShellTable

Trailing host and leading path equality is required (by ordinal-ignore-case)
Longest stated host name is primary quality consideration for selection
Longest stated path prefix is secondary quality consideration, for shells of equal stated host names
Single unqualified shell matches only if all qualified shells fail first statment
'Default' unqualified (no stated host/path) shell takes precidence over other unqualified shells
No match occurs if 'Default' is qualified, multiple other shells are unqualified, and no qualified match occurs

--HG--
branch : dev
This commit is contained in:
Louis DeJardin
2010-04-22 19:35:31 -07:00
parent 6f50f1038e
commit ca222e60f7
5 changed files with 258 additions and 14 deletions

View File

@@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Orchard.Environment;
using Orchard.Environment.Configuration;
using Orchard.Tests.Stubs;
namespace Orchard.Tests.Environment {
[TestFixture]
public class RunningShellTableTests {
[Test]
public void NoShellsGiveNoMatch() {
var table = new RunningShellTable();
var match = table.Match(new StubHttpContext());
Assert.That(match, Is.Null);
}
[Test]
public void DefaultShellMatchesByDefault() {
var table = (IRunningShellTable)new RunningShellTable();
var settings = new ShellSettings { Name = "Default" };
table.Add(settings);
var match = table.Match(new StubHttpContext());
Assert.That(match, Is.SameAs(settings));
}
[Test]
public void AnotherShellMatchesByHostHeader() {
var table = (IRunningShellTable)new RunningShellTable();
var settings = new ShellSettings { Name = "Default" };
var settingsA = new ShellSettings { Name = "Alpha", RequestUrlHost = "a.example.com" };
table.Add(settings);
table.Add(settingsA);
var match = table.Match(new StubHttpContext("~/foo/bar", "a.example.com"));
Assert.That(match, Is.SameAs(settingsA));
}
[Test]
public void DefaultStillCatchesWhenOtherShellsMiss() {
var table = (IRunningShellTable)new RunningShellTable();
var settings = new ShellSettings { Name = "Default" };
var settingsA = new ShellSettings { Name = "Alpha", RequestUrlHost = "a.example.com" };
table.Add(settings);
table.Add(settingsA);
var match = table.Match(new StubHttpContext("~/foo/bar", "b.example.com"));
Assert.That(match, Is.SameAs(settings));
}
[Test]
public void DefaultWontFallbackIfItHasCriteria() {
var table = (IRunningShellTable)new RunningShellTable();
var settings = new ShellSettings { Name = "Default", RequestUrlHost = "www.example.com" };
var settingsA = new ShellSettings { Name = "Alpha", RequestUrlHost = "a.example.com" };
table.Add(settings);
table.Add(settingsA);
var match = table.Match(new StubHttpContext("~/foo/bar", "b.example.com"));
Assert.That(match, Is.Null);
}
[Test]
public void DefaultWillCatchRequestsIfItMatchesCriteria() {
var table = (IRunningShellTable)new RunningShellTable();
var settings = new ShellSettings { Name = "Default", RequestUrlHost = "www.example.com" };
var settingsA = new ShellSettings { Name = "Alpha", RequestUrlHost = "a.example.com" };
table.Add(settings);
table.Add(settingsA);
var match = table.Match(new StubHttpContext("~/foo/bar", "www.example.com"));
Assert.That(match, Is.EqualTo(settings));
}
[Test]
public void NonDefaultCatchallWillFallbackIfNothingElseMatches() {
var table = (IRunningShellTable)new RunningShellTable();
var settings = new ShellSettings { Name = "Default", RequestUrlHost = "www.example.com" };
var settingsA = new ShellSettings { Name = "Alpha" };
table.Add(settings);
table.Add(settingsA);
var match = table.Match(new StubHttpContext("~/foo/bar", "b.example.com"));
Assert.That(match, Is.EqualTo(settingsA));
}
[Test]
public void DefaultCatchallIsFallbackEvenWhenOthersAreUnqualified() {
var table = (IRunningShellTable)new RunningShellTable();
var settings = new ShellSettings { Name = "Default" };
var settingsA = new ShellSettings { Name = "Alpha" };
var settingsB = new ShellSettings { Name = "Beta", RequestUrlHost = "b.example.com" };
var settingsG = new ShellSettings { Name = "Gamma" };
table.Add(settings);
table.Add(settingsA);
table.Add(settingsB);
table.Add(settingsG);
var match = table.Match(new StubHttpContext("~/foo/bar", "a.example.com"));
Assert.That(match, Is.EqualTo(settings));
}
[Test]
public void ThereIsNoFallbackIfMultipleSitesAreUnqualifiedButDefaultIsNotOneOfThem() {
var table = (IRunningShellTable)new RunningShellTable();
var settings = new ShellSettings { Name = "Default", RequestUrlHost = "www.example.com" };
var settingsA = new ShellSettings { Name = "Alpha" };
var settingsB = new ShellSettings { Name = "Beta", RequestUrlHost = "b.example.com" };
var settingsG = new ShellSettings { Name = "Gamma" };
table.Add(settings);
table.Add(settingsA);
table.Add(settingsB);
table.Add(settingsG);
var match = table.Match(new StubHttpContext("~/foo/bar", "a.example.com"));
Assert.That(match, Is.Null);
}
[Test]
public void PathAlsoCausesMatch() {
var table = (IRunningShellTable)new RunningShellTable();
var settings = new ShellSettings { Name = "Default" };
var settingsA = new ShellSettings { Name = "Alpha", RequestUrlPrefix = "~/foo" };
table.Add(settings);
table.Add(settingsA);
var match = table.Match(new StubHttpContext("~/foo/bar", "a.example.com"));
Assert.That(match, Is.SameAs(settingsA));
}
[Test]
public void PathAndHostMustBothMatch() {
var table = (IRunningShellTable)new RunningShellTable();
var settings = new ShellSettings { Name = "Default", RequestUrlHost = "www.example.com", };
var settingsA = new ShellSettings { Name = "Alpha", RequestUrlHost = "wiki.example.com", RequestUrlPrefix = "~/foo" };
var settingsB = new ShellSettings { Name = "Beta", RequestUrlHost = "wiki.example.com", RequestUrlPrefix = "~/bar" };
var settingsG = new ShellSettings { Name = "Gamma", RequestUrlHost = "wiki.example.com" };
var settingsD = new ShellSettings { Name = "Delta", RequestUrlPrefix = "~/Quux" };
table.Add(settings);
table.Add(settingsA);
table.Add(settingsB);
table.Add(settingsG);
table.Add(settingsD);
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "wiki.example.com")), Is.SameAs(settingsA));
Assert.That(table.Match(new StubHttpContext("~/bar/foo", "wiki.example.com")), Is.SameAs(settingsB));
Assert.That(table.Match(new StubHttpContext("~/baaz", "wiki.example.com")), Is.SameAs(settingsG));
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "www.example.com")), Is.SameAs(settings));
Assert.That(table.Match(new StubHttpContext("~/bar/foo", "www.example.com")), Is.SameAs(settings));
Assert.That(table.Match(new StubHttpContext("~/baaz", "www.example.com")), Is.SameAs(settings));
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "a.example.com")), Is.Null);
Assert.That(table.Match(new StubHttpContext("~/quux/quad", "wiki.example.com")), Is.SameAs(settingsG));
Assert.That(table.Match(new StubHttpContext("~/quux/quad", "www.example.com")), Is.SameAs(settings));
Assert.That(table.Match(new StubHttpContext("~/quux/quad", "a.example.com")), Is.SameAs(settingsD));
Assert.That(table.Match(new StubHttpContext("~/yarg", "wiki.example.com")), Is.SameAs(settingsG));
Assert.That(table.Match(new StubHttpContext("~/yarg", "www.example.com")), Is.SameAs(settings));
Assert.That(table.Match(new StubHttpContext("~/yarg", "a.example.com")), Is.Null);
}
[Test]
public void PathAloneWillMatch() {
var table = (IRunningShellTable)new RunningShellTable();
var settingsA = new ShellSettings { Name = "Alpha", RequestUrlPrefix = "~/foo" };
table.Add(settingsA);
Assert.That(table.Match(new StubHttpContext("~/foo/bar", "wiki.example.com")), Is.SameAs(settingsA));
Assert.That(table.Match(new StubHttpContext("~/bar/foo", "wiki.example.com")), Is.Null);
}
}
}

View File

@@ -6,6 +6,7 @@ using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Autofac;
using Autofac.Integration.Web;
using NUnit.Framework;
using Orchard.Environment;
using Orchard.Environment.Configuration;
@@ -109,19 +110,35 @@ namespace Orchard.Tests.Mvc.Routes {
public void MatchingRouteToActiveShellTableWillLimitTheAbilityToMatchRoutes() {
Init();
var routeA = new Route("foo", new MvcRouteHandler());
var routeB = new Route("bar", new MvcRouteHandler());
var routeC = new Route("quux", new MvcRouteHandler());
var routeFoo = new Route("foo", new MvcRouteHandler());
_settingsA.RequestUrlHost = "a.example.com";
_containerA.Resolve<IRoutePublisher>().Publish(
new[] {new RouteDescriptor {Priority = 0, Route = routeA}});
new[] {new RouteDescriptor {Priority = 0, Route = routeFoo}});
_settingsB.RequestUrlHost = "b.example.com";
_containerB.Resolve<IRoutePublisher>().Publish(
new[] {new RouteDescriptor {Priority = 0, Route = routeB}});
new[] {new RouteDescriptor {Priority = 0, Route = routeFoo}});
var httpContext = new StubHttpContext("~/foo");
var routeData = _routes.GetRouteData(httpContext);
Assert.That(routeData, Is.Null);
var httpContextA = new StubHttpContext("~/foo", "a.example.com");
var routeDataA = _routes.GetRouteData(httpContextA);
Assert.That(routeDataA, Is.Not.Null);
Assert.That(routeDataA.DataTokens.ContainsKey("IContainerProvider"), Is.True);
var routeContainerProviderA = (IContainerProvider)routeDataA.DataTokens["IContainerProvider"];
Assert.That(routeContainerProviderA.ApplicationContainer.Resolve<IRouteProvider>(), Is.SameAs(_containerA.Resolve<IRouteProvider>()));
Assert.That(routeContainerProviderA.ApplicationContainer.Resolve<IRouteProvider>(), Is.Not.SameAs(_containerB.Resolve<IRouteProvider>()));
var httpContextB = new StubHttpContext("~/foo", "b.example.com");
var routeDataB = _routes.GetRouteData(httpContextB);
Assert.That(routeDataB, Is.Not.Null);
Assert.That(routeDataB.DataTokens.ContainsKey("IContainerProvider"), Is.True);
var routeContainerProviderB = (IContainerProvider)routeDataA.DataTokens["IContainerProvider"];
Assert.That(routeContainerProviderB.ApplicationContainer.Resolve<IRouteProvider>(), Is.SameAs(_containerB.Resolve<IRouteProvider>()));
Assert.That(routeContainerProviderB.ApplicationContainer.Resolve<IRouteProvider>(), Is.Not.SameAs(_containerA.Resolve<IRouteProvider>()));
}
}

View File

@@ -158,6 +158,7 @@
<Compile Include="Data\StubLocator.cs" />
<Compile Include="Environment\AutofacUtil\AutofacTests.cs" />
<Compile Include="Environment\AutofacUtil\DynamicProxy2\DynamicProxyTests.cs" />
<Compile Include="Environment\RunningShellTableTests.cs" />
<Compile Include="Environment\Utility\Build.cs" />
<Compile Include="Environment\Configuration\AppDataFolderTests.cs" />
<Compile Include="Environment\Configuration\DefaultTenantManagerTests.cs" />

View File

@@ -1,10 +1,12 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web;
namespace Orchard.Tests.Stubs {
public class StubHttpContext : HttpContextBase {
private readonly string _appRelativeCurrentExecutionFilePath;
private readonly string _hostHeader;
private readonly IDictionary _items = new Dictionary<object, object>();
public StubHttpContext() {
@@ -15,23 +17,29 @@ namespace Orchard.Tests.Stubs {
_appRelativeCurrentExecutionFilePath = appRelativeCurrentExecutionFilePath;
}
public StubHttpContext(string appRelativeCurrentExecutionFilePath, string hostHeader) {
_appRelativeCurrentExecutionFilePath = appRelativeCurrentExecutionFilePath;
_hostHeader = hostHeader;
}
public override HttpRequestBase Request {
get { return new StupHttpRequest(_appRelativeCurrentExecutionFilePath); }
get { return new StubHttpRequest(this); }
}
public override IDictionary Items {
get { return _items; }
}
public class StupHttpRequest : HttpRequestBase {
private readonly string _appRelativeCurrentExecutionFilePath;
class StubHttpRequest : HttpRequestBase {
private readonly StubHttpContext _httpContext;
private NameValueCollection _serverVariables;
public StupHttpRequest(string appRelativeCurrentExecutionFilePath) {
_appRelativeCurrentExecutionFilePath = appRelativeCurrentExecutionFilePath;
public StubHttpRequest(StubHttpContext httpContext) {
_httpContext = httpContext;
}
public override string AppRelativeCurrentExecutionFilePath {
get { return _appRelativeCurrentExecutionFilePath; }
get { return _httpContext._appRelativeCurrentExecutionFilePath; }
}
public override string ApplicationPath {
@@ -41,6 +49,14 @@ namespace Orchard.Tests.Stubs {
public override string PathInfo {
get { return ""; }
}
public override NameValueCollection ServerVariables {
get {
return _serverVariables = _serverVariables
?? new NameValueCollection { { "HTTP_HOST", _httpContext._hostHeader } };
}
}
}
}
}

View File

@@ -14,12 +14,40 @@ namespace Orchard.Environment {
public class RunningShellTable : IRunningShellTable {
private IEnumerable<ShellSettings> _shells = Enumerable.Empty<ShellSettings>();
private IEnumerable<IGrouping<string, ShellSettings>> _shellsByHost = Enumerable.Empty<ShellSettings>().GroupBy(x => default(string));
private ShellSettings _fallback;
public void Add(ShellSettings settings) {
_shells = _shells
.Where(s=>s.Name != settings.Name)
.Where(s => s.Name != settings.Name)
.Concat(new[] { settings })
.ToArray();
var qualified =
_shells.Where(x => !string.IsNullOrEmpty(x.RequestUrlHost) || !string.IsNullOrEmpty(x.RequestUrlPrefix));
var unqualified =
_shells.Where(x => string.IsNullOrEmpty(x.RequestUrlHost) && string.IsNullOrEmpty(x.RequestUrlPrefix));
_shellsByHost = qualified
.GroupBy(s => s.RequestUrlHost ?? "")
.OrderByDescending(g => g.Key.Length);
if (unqualified.Count() == 1) {
// only one shell had no request url criteria
_fallback = unqualified.Single();
}
else if (unqualified.Any()) {
// two or more shells had no request criteria.
// this is technically a misconfiguration - so fallback to the default shell
// if it's one which will catch all requests
_fallback = unqualified.SingleOrDefault(x => x.Name == "Default");
}
else {
// no shells are unqualified - a request that does not match a shell's spec
// will not be mapped to routes coming from orchard
_fallback = null;
}
}
public IEnumerable<ShellSettings> List() {
@@ -27,7 +55,23 @@ namespace Orchard.Environment {
}
public ShellSettings Match(HttpContextBase httpContext) {
return _shells.SingleOrDefault(settings => settings.Name == "Default");
var host = httpContext.Request.ServerVariables.Get("HTTP_HOST") ?? "";
var hostLength = host.IndexOf(':');
if (hostLength != -1)
host = host.Substring(0, hostLength);
var appRelativePath = httpContext.Request.AppRelativeCurrentExecutionFilePath;
var mostQualifiedMatch = _shellsByHost
.Where(group => host.EndsWith(group.Key, StringComparison.OrdinalIgnoreCase))
.SelectMany(group => group
.OrderByDescending(settings => (settings.RequestUrlPrefix ?? "").Length))
.Where(settings => appRelativePath.StartsWith(settings.RequestUrlPrefix ?? "", StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
return mostQualifiedMatch ?? _fallback;
}
}
}