This commit is contained in:
parent
47b592cd34
commit
d59868b2d4
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http
|
namespace Microsoft.AspNetCore.Http
|
||||||
{
|
{
|
||||||
|
|
@ -32,10 +34,17 @@ namespace Microsoft.AspNetCore.Http
|
||||||
throw new InvalidOperationException("Binding address is not a unix pipe.");
|
throw new InvalidOperationException("Binding address is not a unix pipe.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Host.Substring(UnixPipeHostPrefix.Length - 1);
|
var unixPipeHostPrefixLength = UnixPipeHostPrefix.Length;
|
||||||
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
// "/" character in unix refers to root. Windows has drive letters and volume separator (c:)
|
||||||
|
unixPipeHostPrefixLength--;
|
||||||
|
}
|
||||||
|
return Host.Substring(unixPipeHostPrefixLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (IsUnixPipe)
|
if (IsUnixPipe)
|
||||||
|
|
@ -88,7 +97,18 @@ namespace Microsoft.AspNetCore.Http
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pathDelimiterStart = address.IndexOf(":", schemeDelimiterEnd + UnixPipeHostPrefix.Length, StringComparison.Ordinal);
|
var unixPipeHostPrefixLength = UnixPipeHostPrefix.Length;
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
// Windows has drive letters and volume separator (c:)
|
||||||
|
unixPipeHostPrefixLength += 2;
|
||||||
|
if (schemeDelimiterEnd + unixPipeHostPrefixLength > address.Length)
|
||||||
|
{
|
||||||
|
throw new FormatException($"Invalid url: '{address}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pathDelimiterStart = address.IndexOf(":", schemeDelimiterEnd + unixPipeHostPrefixLength, StringComparison.Ordinal);
|
||||||
pathDelimiterEnd = pathDelimiterStart + ":".Length;
|
pathDelimiterEnd = pathDelimiterStart + ":".Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,6 +161,11 @@ namespace Microsoft.AspNetCore.Http
|
||||||
throw new FormatException($"Invalid url: '{address}'");
|
throw new FormatException($"Invalid url: '{address}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isUnixPipe && !Path.IsPathRooted(serverAddress.UnixPipePath))
|
||||||
|
{
|
||||||
|
throw new FormatException($"Invalid url, unix socket path must be absolute: '{address}'");
|
||||||
|
}
|
||||||
|
|
||||||
if (address[address.Length - 1] == '/')
|
if (address[address.Length - 1] == '/')
|
||||||
{
|
{
|
||||||
serverAddress.PathBase = address.Substring(pathDelimiterEnd, address.Length - pathDelimiterEnd - 1);
|
serverAddress.PathBase = address.Substring(pathDelimiterEnd, address.Length - pathDelimiterEnd - 1);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Microsoft.AspNetCore.Testing.xunit;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
namespace Microsoft.AspNetCore.Http.Tests
|
namespace Microsoft.AspNetCore.Http.Tests
|
||||||
{
|
{
|
||||||
|
|
@ -32,6 +34,16 @@ namespace Microsoft.AspNetCore.Http.Tests
|
||||||
Assert.Throws<FormatException>(() => BindingAddress.Parse(url));
|
Assert.Throws<FormatException>(() => BindingAddress.Parse(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ConditionalTheory]
|
||||||
|
[InlineData("http://unix:/")]
|
||||||
|
[InlineData("http://unix:/c")]
|
||||||
|
[InlineData("http://unix:/wrong.path")]
|
||||||
|
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows has drive letters and volume separator (c:), testing this url on unix or osx provides completely different output.")]
|
||||||
|
public void FromUriThrowsForUrlsWithWrongFilePathOnWindows(string url)
|
||||||
|
{
|
||||||
|
Assert.Throws<FormatException>(() => BindingAddress.Parse(url));
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("://emptyscheme", "", "emptyscheme", 0, "", "://emptyscheme:0")]
|
[InlineData("://emptyscheme", "", "emptyscheme", 0, "", "://emptyscheme:0")]
|
||||||
[InlineData("http://+", "http", "+", 80, "", "http://+:80")]
|
[InlineData("http://+", "http", "+", 80, "", "http://+:80")]
|
||||||
|
|
@ -50,11 +62,6 @@ namespace Microsoft.AspNetCore.Http.Tests
|
||||||
[InlineData("http://foo:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "foo:", 80, "/tmp/kestrel-test.sock:5000/doesn't/matter", "http://foo::80/tmp/kestrel-test.sock:5000/doesn't/matter")]
|
[InlineData("http://foo:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "foo:", 80, "/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", "http://unix:foo:80/tmp/kestrel-test.sock")]
|
[InlineData("http://unix:foo/tmp/kestrel-test.sock", "http", "unix:foo", 80, "/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", "http://unix:5000/tmp/kestrel-test.sock")]
|
[InlineData("http://unix:5000/tmp/kestrel-test.sock", "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, "", null)]
|
|
||||||
[InlineData("https://unix:/tmp/kestrel-test.sock", "https", "unix:/tmp/kestrel-test.sock", 0, "", null)]
|
|
||||||
[InlineData("http://unix:/tmp/kestrel-test.sock:", "http", "unix:/tmp/kestrel-test.sock", 0, "", "http://unix:/tmp/kestrel-test.sock")]
|
|
||||||
[InlineData("http://unix:/tmp/kestrel-test.sock:/", "http", "unix:/tmp/kestrel-test.sock", 0, "", "http://unix:/tmp/kestrel-test.sock")]
|
|
||||||
[InlineData("http://unix:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "unix:/tmp/kestrel-test.sock", 0, "5000/doesn't/matter", "http://unix:/tmp/kestrel-test.sock")]
|
|
||||||
public void UrlsAreParsedCorrectly(string url, string scheme, string host, int port, string pathBase, string toString)
|
public void UrlsAreParsedCorrectly(string url, string scheme, string host, int port, string pathBase, string toString)
|
||||||
{
|
{
|
||||||
var serverAddress = BindingAddress.Parse(url);
|
var serverAddress = BindingAddress.Parse(url);
|
||||||
|
|
@ -66,5 +73,43 @@ namespace Microsoft.AspNetCore.Http.Tests
|
||||||
|
|
||||||
Assert.Equal(toString ?? url, serverAddress.ToString());
|
Assert.Equal(toString ?? url, serverAddress.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ConditionalTheory]
|
||||||
|
[InlineData("http://unix:/tmp/kestrel-test.sock", "http", "unix:/tmp/kestrel-test.sock", 0, "", null)]
|
||||||
|
[InlineData("https://unix:/tmp/kestrel-test.sock", "https", "unix:/tmp/kestrel-test.sock", 0, "", null)]
|
||||||
|
[InlineData("http://unix:/tmp/kestrel-test.sock:", "http", "unix:/tmp/kestrel-test.sock", 0, "", "http://unix:/tmp/kestrel-test.sock")]
|
||||||
|
[InlineData("http://unix:/tmp/kestrel-test.sock:/", "http", "unix:/tmp/kestrel-test.sock", 0, "", "http://unix:/tmp/kestrel-test.sock")]
|
||||||
|
[InlineData("http://unix:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "unix:/tmp/kestrel-test.sock", 0, "5000/doesn't/matter", "http://unix:/tmp/kestrel-test.sock")]
|
||||||
|
[OSSkipCondition(OperatingSystems.Windows)]
|
||||||
|
public void UnixSocketUrlsAreParsedCorrectlyOnUnix(string url, string scheme, string host, int port, string pathBase, string toString)
|
||||||
|
{
|
||||||
|
var serverAddress = BindingAddress.Parse(url);
|
||||||
|
|
||||||
|
Assert.Equal(scheme, serverAddress.Scheme);
|
||||||
|
Assert.Equal(host, serverAddress.Host);
|
||||||
|
Assert.Equal(port, serverAddress.Port);
|
||||||
|
Assert.Equal(pathBase, serverAddress.PathBase);
|
||||||
|
|
||||||
|
Assert.Equal(toString ?? url, serverAddress.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalTheory]
|
||||||
|
[InlineData("http://unix:/c:/foo/bar/pipe.socket", "http", "unix:/c:/foo/bar/pipe.socket", 0, "", null)]
|
||||||
|
[InlineData("http://unix:/c:/foo/bar/pipe.socket:", "http", "unix:/c:/foo/bar/pipe.socket", 0, "", "http://unix:/c:/foo/bar/pipe.socket")]
|
||||||
|
[InlineData("http://unix:/c:/foo/bar/pipe.socket:/", "http", "unix:/c:/foo/bar/pipe.socket", 0, "", "http://unix:/c:/foo/bar/pipe.socket")]
|
||||||
|
[InlineData("http://unix:/c:/foo/bar/pipe.socket:5000/doesn't/matter", "http", "unix:/c:/foo/bar/pipe.socket", 0, "5000/doesn't/matter", "http://unix:/c:/foo/bar/pipe.socket")]
|
||||||
|
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows has drive letters and volume separator (c:), testing this url on unix or osx provides completely different output.")]
|
||||||
|
public void UnixSocketUrlsAreParsedCorrectlyOnWindows(string url, string scheme, string host, int port, string pathBase, string toString)
|
||||||
|
{
|
||||||
|
var serverAddress = BindingAddress.Parse(url);
|
||||||
|
|
||||||
|
Assert.Equal(scheme, serverAddress.Scheme);
|
||||||
|
Assert.Equal(host, serverAddress.Host);
|
||||||
|
Assert.Equal(port, serverAddress.Port);
|
||||||
|
Assert.Equal(pathBase, serverAddress.PathBase);
|
||||||
|
|
||||||
|
Assert.Equal(toString ?? url, serverAddress.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[ConditionalFact]
|
[ConditionalFact]
|
||||||
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_RS4)]
|
[OSSkipCondition(OperatingSystems.Windows, SkipReason = "tmp/kestrel-test.sock is not valid for windows. Unix socket path must be absolute.")]
|
||||||
public void ParseAddressUnixPipe()
|
public void ParseAddressUnixPipe()
|
||||||
{
|
{
|
||||||
var listenOptions = AddressBinder.ParseAddress("http://unix:/tmp/kestrel-test.sock", out var https);
|
var listenOptions = AddressBinder.ParseAddress("http://unix:/tmp/kestrel-test.sock", out var https);
|
||||||
|
|
@ -86,6 +86,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
Assert.False(https);
|
Assert.False(https);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows has drive letters and volume separator (c:), testing this url on unix or osx provides completely different output.")]
|
||||||
|
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_RS4)]
|
||||||
|
public void ParseAddressUnixPipeOnWindows()
|
||||||
|
{
|
||||||
|
var listenOptions = AddressBinder.ParseAddress(@"http://unix:/c:/foo/bar/pipe.socket", out var https);
|
||||||
|
Assert.IsType<UnixDomainSocketEndPoint>(listenOptions.EndPoint);
|
||||||
|
Assert.Equal("c:/foo/bar/pipe.socket", listenOptions.SocketPath);
|
||||||
|
Assert.False(https);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("http://10.10.10.10:5000/", "10.10.10.10", 5000, false)]
|
[InlineData("http://10.10.10.10:5000/", "10.10.10.10", 5000, false)]
|
||||||
[InlineData("http://[::1]:5000", "::1", 5000, false)]
|
[InlineData("http://[::1]:5000", "::1", 5000, false)]
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Testing;
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
|
|
@ -96,7 +98,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
var read = 0;
|
var read = 0;
|
||||||
while (read < data.Length)
|
while (read < data.Length)
|
||||||
{
|
{
|
||||||
read += await socket.ReceiveAsync(buffer.AsMemory(read, buffer.Length - read), SocketFlags.None).DefaultTimeout();
|
var bytesReceived = await socket.ReceiveAsync(buffer.AsMemory(read, buffer.Length - read), SocketFlags.None).DefaultTimeout();
|
||||||
|
read += bytesReceived;
|
||||||
|
if (bytesReceived <= 0) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Equal(data, buffer);
|
Assert.Equal(data, buffer);
|
||||||
|
|
@ -114,6 +118,73 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if LIBUV
|
||||||
|
[OSSkipCondition(OperatingSystems.Windows, SkipReason = "Libuv does not support unix domain sockets on Windows.")]
|
||||||
|
#else
|
||||||
|
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10_RS4)]
|
||||||
|
#endif
|
||||||
|
[ConditionalFact]
|
||||||
|
[CollectDump]
|
||||||
|
public async Task TestUnixDomainSocketWithUrl()
|
||||||
|
{
|
||||||
|
var path = Path.GetTempFileName();
|
||||||
|
var url = $"http://unix:/{path}";
|
||||||
|
|
||||||
|
Delete(path);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var hostBuilder = TransportSelector.GetWebHostBuilder()
|
||||||
|
.UseUrls(url)
|
||||||
|
.UseKestrel()
|
||||||
|
.ConfigureServices(AddTestLogging)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("Hello World");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var host = hostBuilder.Build())
|
||||||
|
{
|
||||||
|
await host.StartAsync().DefaultTimeout();
|
||||||
|
|
||||||
|
// https://github.com/dotnet/corefx/issues/5999
|
||||||
|
// .NET Core HttpClient does not support unix sockets, it's difficult to parse raw response data. below is a little hacky way.
|
||||||
|
using (var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified))
|
||||||
|
{
|
||||||
|
await socket.ConnectAsync(new UnixDomainSocketEndPoint(path)).DefaultTimeout();
|
||||||
|
|
||||||
|
var httpRequest = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\nConnection: close\r\n\r\n");
|
||||||
|
await socket.SendAsync(httpRequest, SocketFlags.None).DefaultTimeout();
|
||||||
|
|
||||||
|
var readBuffer = new byte[512];
|
||||||
|
var read = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var bytesReceived = await socket.ReceiveAsync(readBuffer.AsMemory(read), SocketFlags.None).DefaultTimeout();
|
||||||
|
read += bytesReceived;
|
||||||
|
if (bytesReceived <= 0) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpResponse = Encoding.ASCII.GetString(readBuffer, 0, read);
|
||||||
|
int httpStatusStart = httpResponse.IndexOf(' ') + 1;
|
||||||
|
int httpStatusEnd = httpResponse.IndexOf(' ', httpStatusStart);
|
||||||
|
|
||||||
|
var httpStatus = int.Parse(httpResponse.Substring(httpStatusStart, httpStatusEnd - httpStatusStart));
|
||||||
|
Assert.Equal(httpStatus, StatusCodes.Status200OK);
|
||||||
|
|
||||||
|
}
|
||||||
|
await host.StopAsync().DefaultTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void Delete(string path)
|
private static void Delete(string path)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue