Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Support PathBase (#214). #412

Merged
merged 1 commit into from
Dec 1, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion samples/SampleApp/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IAp
context.Request.PathBase,
context.Request.Path,
context.Request.QueryString);
Console.WriteLine($"Method: {context.Request.Method}");
Console.WriteLine($"PathBase: {context.Request.PathBase}");
Console.WriteLine($"Path: {context.Request.Path}");
Console.WriteLine($"QueryString: {context.Request.QueryString}");

var connectionFeature = context.Connection;
Console.WriteLine($"Peer: {connectionFeature.RemoteIpAddress?.ToString()} {connectionFeature.RemotePort}");
Expand All @@ -60,4 +64,4 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IAp
});
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public partial class Frame : IFeatureCollection,
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
// See also: tools/Microsoft.AspNet.Server.Kestrel.GeneratedCode/FrameFeatureCollection.cs

private string _pathBase;
private int _featureRevision;

private List<KeyValuePair<Type, object>> MaybeExtra;
Expand Down Expand Up @@ -118,12 +117,12 @@ string IHttpRequestFeature.PathBase
{
get
{
return _pathBase ?? "";
return PathBase ?? "";
}

set
{
_pathBase = value;
PathBase = value;
}
}

Expand Down
44 changes: 42 additions & 2 deletions src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Server.Kestrel.Filter;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
Expand Down Expand Up @@ -70,6 +69,8 @@ public partial class Frame : FrameContext, IFrameControl
private readonly IPEndPoint _remoteEndPoint;
private readonly Action<IFeatureCollection> _prepareRequest;

private readonly string _pathBase;

public Frame(ConnectionContext context)
: this(context, remoteEndPoint: null, localEndPoint: null, prepareRequest: null)
{
Expand All @@ -84,6 +85,7 @@ public Frame(ConnectionContext context,
_remoteEndPoint = remoteEndPoint;
_localEndPoint = localEndPoint;
_prepareRequest = prepareRequest;
_pathBase = context.ServerAddress.PathBase;

FrameControl = this;
Reset();
Expand All @@ -92,6 +94,7 @@ public Frame(ConnectionContext context,
public string Scheme { get; set; }
public string Method { get; set; }
public string RequestUri { get; set; }
public string PathBase { get; set; }
public string Path { get; set; }
public string QueryString { get; set; }
public string HttpVersion
Expand Down Expand Up @@ -198,6 +201,7 @@ public void Reset()
Scheme = null;
Method = null;
RequestUri = null;
PathBase = null;
Path = null;
QueryString = null;
_httpVersion = HttpVersionType.Unknown;
Expand Down Expand Up @@ -809,7 +813,21 @@ private bool TakeStartLine(SocketInput input)
RequestUri = requestUrlPath;
QueryString = queryString;
HttpVersion = httpVersion;
Path = RequestUri;

bool caseMatches;

if (!string.IsNullOrEmpty(_pathBase) &&
(requestUrlPath.Length == _pathBase.Length || (requestUrlPath.Length > _pathBase.Length && requestUrlPath[_pathBase.Length] == '/')) &&
RequestUrlStartsWithPathBase(requestUrlPath, out caseMatches))
{
PathBase = caseMatches ? _pathBase : requestUrlPath.Substring(0, _pathBase.Length);
Path = requestUrlPath.Substring(_pathBase.Length);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to rewrite this to use iterators and also avoid scanning the string multiple times.

}
else
{
Path = requestUrlPath;
}

return true;
}
finally
Expand All @@ -818,6 +836,28 @@ private bool TakeStartLine(SocketInput input)
}
}

private bool RequestUrlStartsWithPathBase(string requestUrl, out bool caseMatches)
{
caseMatches = true;

for (var i = 0; i < _pathBase.Length; i++)
{
if (requestUrl[i] != _pathBase[i])
{
if (char.ToLowerInvariant(requestUrl[i]) == char.ToLowerInvariant(_pathBase[i]))
{
caseMatches = false;
}
else
{
return false;
}
}
}

return true;
}

public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders)
{
var scan = input.ConsumingStart();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
{
public static class MemoryPoolIterator2Extenstions
public static class MemoryPoolIterator2Extensions
{
private const int _maxStackAllocBytes = 16384;

Expand Down
18 changes: 13 additions & 5 deletions src/Microsoft.AspNet.Server.Kestrel/ServerAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Server.Kestrel
public class ServerAddress
{
public string Host { get; private set; }
public string Path { get; private set; }
public string PathBase { get; private set; }
public int Port { get; private set; }
public string Scheme { get; private set; }

Expand All @@ -35,7 +35,7 @@ public string UnixPipePath

public override string ToString()
{
return Scheme.ToLowerInvariant() + "://" + Host.ToLowerInvariant() + ":" + Port.ToString(CultureInfo.InvariantCulture) + Path.ToLowerInvariant();
return Scheme.ToLowerInvariant() + "://" + Host.ToLowerInvariant() + ":" + Port.ToString(CultureInfo.InvariantCulture) + PathBase.ToLowerInvariant();
}

public override int GetHashCode()
Expand All @@ -53,7 +53,7 @@ public override bool Equals(object obj)
return string.Equals(Scheme, other.Scheme, StringComparison.OrdinalIgnoreCase)
&& string.Equals(Host, other.Host, StringComparison.OrdinalIgnoreCase)
&& Port == other.Port
&& string.Equals(Path, other.Path, StringComparison.OrdinalIgnoreCase);
&& string.Equals(PathBase, other.PathBase, StringComparison.OrdinalIgnoreCase);
}

public static ServerAddress FromUrl(string url)
Expand All @@ -71,7 +71,7 @@ public static ServerAddress FromUrl(string url)
Scheme = "http",
Host = "+",
Port = port,
Path = "/"
PathBase = "/"
};
}
return null;
Expand Down Expand Up @@ -137,7 +137,15 @@ public static ServerAddress FromUrl(string url)
serverAddress.Host = url.Substring(schemeDelimiterEnd, pathDelimiterStart - schemeDelimiterEnd);
}

serverAddress.Path = url.Substring(pathDelimiterEnd);
// Path should not end with a / since it will be used as PathBase later
if (url[url.Length - 1] == '/')
{
serverAddress.PathBase = url.Substring(pathDelimiterEnd, url.Length - pathDelimiterEnd - 1);
}
else
{
serverAddress.PathBase = url.Substring(pathDelimiterEnd);
}

return serverAddress;
}
Expand Down
113 changes: 113 additions & 0 deletions test/Microsoft.AspNet.Server.Kestrel.FunctionalTests/PathBaseTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Testing.xunit;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;

namespace Microsoft.AspNet.Server.Kestrel.FunctionalTests
{
public class PathBaseTests
{
[ConditionalTheory]
[InlineData("http://localhost:8791/base", "http://localhost:8791/base", "/base", "")]
[InlineData("http://localhost:8791/base", "http://localhost:8791/base/", "/base", "/")]
[InlineData("http://localhost:8791/base", "http://localhost:8791/base/something", "/base", "/something")]
[InlineData("http://localhost:8791/base", "http://localhost:8791/base/something/", "/base", "/something/")]
[InlineData("http://localhost:8791/base/more", "http://localhost:8791/base/more", "/base/more", "")]
[InlineData("http://localhost:8791/base/more", "http://localhost:8791/base/more/something", "/base/more", "/something")]
[InlineData("http://localhost:8791/base/more", "http://localhost:8791/base/more/something/", "/base/more", "/something/")]
[FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "Test hangs after execution on Mono.")]
public Task RequestPathBaseIsServerPathBase(string registerAddress, string requestAddress, string expectedPathBase, string expectedPath)
{
return TestPathBase(registerAddress, requestAddress, expectedPathBase, expectedPath);
}

[ConditionalTheory]
[InlineData("http://localhost:8791", "http://localhost:8791/", "", "/")]
[InlineData("http://localhost:8791", "http://localhost:8791/something", "", "/something")]
[InlineData("http://localhost:8791/", "http://localhost:8791/", "", "/")]
[InlineData("http://localhost:8791/base", "http://localhost:8791/", "", "/")]
[InlineData("http://localhost:8791/base", "http://localhost:8791/something", "", "/something")]
[InlineData("http://localhost:8791/base", "http://localhost:8791/baseandsomething", "", "/baseandsomething")]
[InlineData("http://localhost:8791/base", "http://localhost:8791/ba", "", "/ba")]
[InlineData("http://localhost:8791/base", "http://localhost:8791/ba/se", "", "/ba/se")]
[FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "Test hangs after execution on Mono.")]
public Task DefaultPathBaseIsEmpty(string registerAddress, string requestAddress, string expectedPathBase, string expectedPath)
{
return TestPathBase(registerAddress, requestAddress, expectedPathBase, expectedPath);
}

[ConditionalTheory]
[InlineData("http://localhost:8791", "http://localhost:8791/", "", "/")]
[InlineData("http://localhost:8791/", "http://localhost:8791/", "", "/")]
[InlineData("http://localhost:8791/base", "http://localhost:8791/base/", "/base", "/")]
[InlineData("http://localhost:8791/base/", "http://localhost:8791/base", "/base", "")]
[InlineData("http://localhost:8791/base/", "http://localhost:8791/base/", "/base", "/")]
[FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "Test hangs after execution on Mono.")]
public Task PathBaseNeverEndsWithSlash(string registerAddress, string requestAddress, string expectedPathBase, string expectedPath)
{
return TestPathBase(registerAddress, requestAddress, expectedPathBase, expectedPath);
}

[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "Test hangs after execution on Mono.")]
public Task PathBaseAndPathPreserveRequestCasing()
{
return TestPathBase("http://localhost:8791/base", "http://localhost:8791/Base/Something", "/Base", "/Something");
}

[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.Mono, SkipReason = "Test hangs after execution on Mono.")]
public Task PathBaseCanHaveUTF8Characters()
{
return TestPathBase("http://localhost:8791/b♫se", "http://localhost:8791/b♫se/something", "/b♫se", "/something");
}

private async Task TestPathBase(string registerAddress, string requestAddress, string expectedPathBase, string expectedPath)
{
var config = new ConfigurationBuilder().AddInMemoryCollection(
new Dictionary<string, string> {
{ "server.urls", registerAddress }
}).Build();

var builder = new WebHostBuilder(config)
.UseServerFactory("Microsoft.AspNet.Server.Kestrel")
.UseStartup(app =>
{
app.Run(async context =>
{
await context.Response.WriteAsync(JsonConvert.SerializeObject(new
{
PathBase = context.Request.PathBase.Value,
Path = context.Request.Path.Value
}));
});
});

using (var app = builder.Build().Start())
{
using (var client = new HttpClient())
{
var response = await client.GetAsync(requestAddress);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HttpClient will always turn http://localhost to http://localhost/, so you can't test that senario here.

response.EnsureSuccessStatusCode();

var responseText = await response.Content.ReadAsStringAsync();
Assert.NotEmpty(responseText);

var pathFacts = JsonConvert.DeserializeObject<JObject>(responseText);
Assert.Equal(expectedPathBase, pathFacts["PathBase"].Value<string>());
Assert.Equal(expectedPath, pathFacts["Path"].Value<string>());
}
}
}
}
}
8 changes: 7 additions & 1 deletion test/Microsoft.AspNet.Server.KestrelTests/FrameFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Server.Kestrel;
using Microsoft.AspNet.Server.Kestrel.Http;
using Xunit;

Expand All @@ -13,7 +14,12 @@ public class FrameFacts
public void ResetResetsScheme()
{
// Arrange
var frame = new Frame(new ConnectionContext() { DateHeaderValueManager = new DateHeaderValueManager() });
var connectionContext = new ConnectionContext()
{
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
};
var frame = new Frame(connectionContext);
frame.Scheme = "https";

// Act
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using Microsoft.AspNet.Server.Kestrel;
using Microsoft.AspNet.Server.Kestrel.Http;
using Microsoft.Extensions.Primitives;
using Xunit;
Expand All @@ -14,7 +15,12 @@ public class FrameResponseHeadersTests
[Fact]
public void InitialDictionaryContainsServerAndDate()
{
var frame = new Frame(new ConnectionContext { DateHeaderValueManager = new DateHeaderValueManager() });
var connectionContext = new ConnectionContext
{
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
};
var frame = new Frame(connectionContext);
IDictionary<string, StringValues> headers = frame.ResponseHeaders;

Assert.Equal(2, headers.Count);
Expand All @@ -37,7 +43,12 @@ public void InitialDictionaryContainsServerAndDate()
[Fact]
public void InitialEntriesCanBeCleared()
{
var frame = new Frame(new ConnectionContext { DateHeaderValueManager = new DateHeaderValueManager() });
var connectionContext = new ConnectionContext
{
DateHeaderValueManager = new DateHeaderValueManager(),
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
};
var frame = new Frame(connectionContext);

Assert.True(frame.ResponseHeaders.Count > 0);

Expand Down
Loading