Support PathBase (#214).
This commit is contained in:
parent
8b310c4583
commit
921c338a40
|
|
@ -48,6 +48,10 @@ namespace SampleApp
|
|||
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}");
|
||||
|
|
@ -60,4 +64,4 @@ namespace SampleApp
|
|||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +27,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
// 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;
|
||||
|
|
@ -118,12 +117,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
{
|
||||
get
|
||||
{
|
||||
return _pathBase ?? "";
|
||||
return PathBase ?? "";
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_pathBase = value;
|
||||
PathBase = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ using System.Threading;
|
|||
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;
|
||||
|
|
@ -70,6 +69,8 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
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)
|
||||
{
|
||||
|
|
@ -84,6 +85,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
_remoteEndPoint = remoteEndPoint;
|
||||
_localEndPoint = localEndPoint;
|
||||
_prepareRequest = prepareRequest;
|
||||
_pathBase = context.ServerAddress.PathBase;
|
||||
|
||||
FrameControl = this;
|
||||
Reset();
|
||||
|
|
@ -92,6 +94,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
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
|
||||
|
|
@ -198,6 +201,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
Scheme = null;
|
||||
Method = null;
|
||||
RequestUri = null;
|
||||
PathBase = null;
|
||||
Path = null;
|
||||
QueryString = null;
|
||||
_httpVersion = HttpVersionType.Unknown;
|
||||
|
|
@ -809,7 +813,21 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
Path = requestUrlPath;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
|
|
@ -818,6 +836,28 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
|
|||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using System.Text;
|
|||
|
||||
namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
|
||||
{
|
||||
public static class MemoryPoolIterator2Extenstions
|
||||
public static class MemoryPoolIterator2Extensions
|
||||
{
|
||||
private const int _maxStackAllocBytes = 16384;
|
||||
|
||||
|
|
@ -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; }
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Server.Kestrel
|
|||
|
||||
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()
|
||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNet.Server.Kestrel
|
|||
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)
|
||||
|
|
@ -71,7 +71,7 @@ namespace Microsoft.AspNet.Server.Kestrel
|
|||
Scheme = "http",
|
||||
Host = "+",
|
||||
Port = port,
|
||||
Path = "/"
|
||||
PathBase = "/"
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
|
@ -137,7 +137,15 @@ namespace Microsoft.AspNet.Server.Kestrel
|
|||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
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>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -13,7 +14,12 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -14,7 +15,12 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
|||
[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);
|
||||
|
|
@ -37,7 +43,12 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
|||
[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);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,30 +18,30 @@ namespace Microsoft.AspNet.Server.KestrelTests
|
|||
[InlineData("http://localhost", "http", "localhost", 80, "")]
|
||||
[InlineData("http://www.example.com", "http", "www.example.com", 80, "")]
|
||||
[InlineData("https://www.example.com", "https", "www.example.com", 443, "")]
|
||||
[InlineData("http://www.example.com/", "http", "www.example.com", 80, "/")]
|
||||
[InlineData("http://www.example.com/", "http", "www.example.com", 80, "")]
|
||||
[InlineData("http://www.example.com/foo?bar=baz", "http", "www.example.com", 80, "/foo?bar=baz")]
|
||||
[InlineData("http://www.example.com:5000", "http", "www.example.com", 5000, "")]
|
||||
[InlineData("https://www.example.com:5000", "https", "www.example.com", 5000, "")]
|
||||
[InlineData("http://www.example.com:5000/", "http", "www.example.com", 5000, "/")]
|
||||
[InlineData("http://www.example.com:5000/", "http", "www.example.com", 5000, "")]
|
||||
[InlineData("http://www.example.com:NOTAPORT", "http", "www.example.com:NOTAPORT", 80, "")]
|
||||
[InlineData("https://www.example.com:NOTAPORT", "https", "www.example.com:NOTAPORT", 443, "")]
|
||||
[InlineData("http://www.example.com:NOTAPORT/", "http", "www.example.com:NOTAPORT", 80, "/")]
|
||||
[InlineData("http://www.example.com:NOTAPORT/", "http", "www.example.com:NOTAPORT", 80, "")]
|
||||
[InlineData("http://foo:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "foo:", 80, "/tmp/kestrel-test.sock:5000/doesn't/matter")]
|
||||
[InlineData("http://unix:foo/tmp/kestrel-test.sock", "http", "unix:foo", 80, "/tmp/kestrel-test.sock")]
|
||||
[InlineData("http://unix:5000/tmp/kestrel-test.sock", "http", "unix", 5000, "/tmp/kestrel-test.sock")]
|
||||
[InlineData("http://unix:/tmp/kestrel-test.sock", "http", "unix:/tmp/kestrel-test.sock", 0, "")]
|
||||
[InlineData("https://unix:/tmp/kestrel-test.sock", "https", "unix:/tmp/kestrel-test.sock", 0, "")]
|
||||
[InlineData("http://unix:/tmp/kestrel-test.sock:", "http", "unix:/tmp/kestrel-test.sock", 0, "")]
|
||||
[InlineData("http://unix:/tmp/kestrel-test.sock:/", "http", "unix:/tmp/kestrel-test.sock", 0, "/")]
|
||||
[InlineData("http://unix:/tmp/kestrel-test.sock:/", "http", "unix:/tmp/kestrel-test.sock", 0, "")]
|
||||
[InlineData("http://unix:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "unix:/tmp/kestrel-test.sock", 0, "5000/doesn't/matter")]
|
||||
public void UrlsAreParsedCorrectly(string url, string scheme, string host, int port, string path)
|
||||
public void UrlsAreParsedCorrectly(string url, string scheme, string host, int port, string pathBase)
|
||||
{
|
||||
var serverAddress = ServerAddress.FromUrl(url);
|
||||
|
||||
Assert.Equal(scheme, serverAddress.Scheme);
|
||||
Assert.Equal(host, serverAddress.Host);
|
||||
Assert.Equal(port, serverAddress.Port);
|
||||
Assert.Equal(path, serverAddress.Path);
|
||||
Assert.Equal(pathBase, serverAddress.PathBase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue