Initial work to support HTTPS using SslStream

- Add extension method "UseKestrelHttps" to IApplicationBuilder
This commit is contained in:
Stephen Halter 2015-09-30 11:15:09 -07:00
parent 49451fb11e
commit 2f3a00625a
39 changed files with 667 additions and 124 deletions

View File

@ -33,6 +33,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.StandardsPolice",
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Server.Kestrel.LibuvCopier", "tools\Microsoft.AspNet.Server.Kestrel.LibuvCopier\Microsoft.AspNet.Server.Kestrel.LibuvCopier.xproj", "{8CBA6FE3-3CC9-4420-8AA3-123E983734C2}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Server.Kestrel.Https", "src\Microsoft.AspNet.Server.Kestrel.Https\Microsoft.AspNet.Server.Kestrel.Https.xproj", "{5F64B3C3-0C2E-431A-B820-A81BBFC863DA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -67,6 +69,10 @@ Global
{8CBA6FE3-3CC9-4420-8AA3-123E983734C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CBA6FE3-3CC9-4420-8AA3-123E983734C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CBA6FE3-3CC9-4420-8AA3-123E983734C2}.Release|Any CPU.Build.0 = Release|Any CPU
{5F64B3C3-0C2E-431A-B820-A81BBFC863DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5F64B3C3-0C2E-431A-B820-A81BBFC863DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F64B3C3-0C2E-431A-B820-A81BBFC863DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F64B3C3-0C2E-431A-B820-A81BBFC863DA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -79,5 +85,6 @@ Global
{BD2D4D29-1BD9-40D0-BB31-337D5416B63C} = {327F7880-D9AF-46BD-B45C-3B7E34A01DFD}
{82295647-7C1C-4671-BAB6-0FEF58F949EC} = {327F7880-D9AF-46BD-B45C-3B7E34A01DFD}
{8CBA6FE3-3CC9-4420-8AA3-123E983734C2} = {327F7880-D9AF-46BD-B45C-3B7E34A01DFD}
{5F64B3C3-0C2E-431A-B820-A81BBFC863DA} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
EndGlobalSection
EndGlobal

View File

@ -5,7 +5,11 @@
"Microsoft.Extensions.Logging.Console": "1.0.0-*"
},
"frameworks": {
"dnx451": { },
"dnx451": {
"dependencies": {
"Microsoft.AspNet.Server.Kestrel.Https": "1.0.0-*"
}
},
"dnxcore50": {
"dependencies": {
"System.Console": "4.0.0-beta-*"
@ -16,6 +20,6 @@
"web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.Kestrel",
"run": "Microsoft.AspNet.Server.Kestrel",
"run-socket": "Microsoft.AspNet.Server.Kestrel --server.urls http://unix:/tmp/kestrel-test.sock",
"kestrel": "Microsoft.AspNet.Server.Kestrel"
"kestrel": "Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:5000;https://localhost:5001"
}
}

View File

@ -0,0 +1,29 @@
// 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.Security.Cryptography.X509Certificates;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Server.Kestrel.Filter;
namespace Microsoft.AspNet.Server.Kestrel.Https
{
public static class HttpsApplicationBuilderExtensions
{
public static IApplicationBuilder UseKestrelHttps(this IApplicationBuilder app, X509Certificate2 cert)
{
var serverInfo = app.ServerFeatures.Get<IKestrelServerInformation>();
if (serverInfo == null)
{
return app;
}
var prevFilter = serverInfo.ConnectionFilter ?? new NoOpConnectionFilter();
serverInfo.ConnectionFilter = new HttpsConnectionFilter(cert, prevFilter);
return app;
}
}
}

View File

@ -0,0 +1,44 @@
// 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;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNet.Server.Kestrel.Filter;
namespace Microsoft.AspNet.Server.Kestrel.Https
{
public class HttpsConnectionFilter : IConnectionFilter
{
private readonly X509Certificate2 _cert;
private readonly IConnectionFilter _previous;
public HttpsConnectionFilter(X509Certificate2 cert, IConnectionFilter previous)
{
if (cert == null)
{
throw new ArgumentNullException(nameof(cert));
}
if (previous == null)
{
throw new ArgumentNullException(nameof(previous));
}
_cert = cert;
_previous = previous;
}
public async Task OnConnection(ConnectionFilterContext context)
{
await _previous.OnConnection(context);
if (string.Equals(context.Address.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
var sslStream = new SslStream(context.Connection);
await sslStream.AuthenticateAsServerAsync(_cert);
context.Connection = sslStream;
}
}
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>5f64b3c3-0c2e-431a-b820-a81bbfc863da</ProjectGuid>
<RootNamespace>Microsoft.AspNet.Server.Kestrel.Https</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,14 @@
{
"version": "1.0.0-*",
"description": "Adds HTTPS support to Kestrel",
"repository": {
"type": "git",
"url": "git://github.com/aspnet/kestrelhttpserver"
},
"dependencies": {
"Microsoft.AspNet.Server.Kestrel": "1.0.0-*"
},
"frameworks": {
"dnx451": { }
}
}

View File

@ -0,0 +1,13 @@
// 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.IO;
namespace Microsoft.AspNet.Server.Kestrel.Filter
{
public class ConnectionFilterContext
{
public ServerAddress Address { get; set; }
public Stream Connection { get; set; }
}
}

View File

@ -0,0 +1,63 @@
// 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;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Server.Kestrel.Http;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNet.Server.Kestrel.Filter
{
public class FilteredStreamAdapter
{
private readonly Stream _filteredStream;
private readonly Stream _socketInputStream;
private readonly IKestrelTrace _log;
public FilteredStreamAdapter(
Stream filteredStream,
MemoryPool2 memory,
IKestrelTrace logger)
{
SocketInput = new SocketInput(memory);
SocketOutput = new StreamSocketOutput(filteredStream);
_log = logger;
_filteredStream = filteredStream;
_socketInputStream = new SocketInputStream(SocketInput);
_filteredStream.CopyToAsync(_socketInputStream).ContinueWith((task, state) =>
{
((FilteredStreamAdapter)state).OnStreamClose(task);
}, this);
}
public SocketInput SocketInput { get; private set; }
public ISocketOutput SocketOutput { get; private set; }
private void OnStreamClose(Task copyAsyncTask)
{
if (copyAsyncTask.IsFaulted)
{
_log.LogError("FilteredStreamAdapter.CopyToAsync", copyAsyncTask.Exception);
}
else if (copyAsyncTask.IsCanceled)
{
_log.LogError("FilteredStreamAdapter.CopyToAsync canceled.");
}
try
{
_filteredStream.Dispose();
_socketInputStream.Dispose();
}
catch (Exception ex)
{
_log.LogError("FilteredStreamAdapter.OnStreamClose", ex);
}
}
}
}

View File

@ -0,0 +1,12 @@
// 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.Threading.Tasks;
namespace Microsoft.AspNet.Server.Kestrel.Filter
{
public interface IConnectionFilter
{
Task OnConnection(ConnectionFilterContext context);
}
}

View File

@ -0,0 +1,91 @@
// 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;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Server.Kestrel.Http;
namespace Microsoft.AspNet.Server.Kestrel.Filter
{
public class LibuvStream : Stream
{
private readonly SocketInput _input;
private readonly ISocketOutput _output;
public LibuvStream(SocketInput input, ISocketOutput output)
{
_input = input;
_output = output;
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length
{
get
{
throw new NotSupportedException();
}
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
return ReadAsync(new ArraySegment<byte>(buffer, offset, count)).GetAwaiter().GetResult();
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return ReadAsync(new ArraySegment<byte>(buffer, offset, count));
}
public override void Write(byte[] buffer, int offset, int count)
{
var segment = new ArraySegment<byte>(buffer, offset, count);
_output.Write(segment);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token)
{
var segment = new ArraySegment<byte>(buffer, offset, count);
return _output.WriteAsync(segment);
}
public override void Flush()
{
// No-op since writes are immediate.
}
private Task<int> ReadAsync(ArraySegment<byte> buffer)
{
return _input.ReadAsync(buffer);
}
}
}

View File

@ -0,0 +1,17 @@
// 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.Threading.Tasks;
namespace Microsoft.AspNet.Server.Kestrel.Filter
{
public class NoOpConnectionFilter : IConnectionFilter
{
private static Task _empty = Task.FromResult<object>(null);
public Task OnConnection(ConnectionFilterContext context)
{
return _empty;
}
}
}

View File

@ -0,0 +1,99 @@
// 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;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Server.Kestrel.Http;
namespace Microsoft.AspNet.Server.Kestrel.Filter
{
/// <summary>
/// This is a write-only stream that copies what is written into a
/// <see cref="SocketInput"/> object. This is used as an argument to
/// <see cref="Stream.CopyToAsync(Stream)" /> so input filtered by a
/// ConnectionFilter (e.g. SslStream) can be consumed by <see cref="Frame"/>.
/// </summary>
public class SocketInputStream : Stream
{
private static Task _emptyTask = Task.FromResult<object>(null);
private static byte[] _emptyBuffer = new byte[0];
private readonly SocketInput _socketInput;
public SocketInputStream(SocketInput socketInput)
{
_socketInput = socketInput;
}
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length
{
get
{
throw new NotSupportedException();
}
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override void Flush()
{
// No-op
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
var inputBuffer = _socketInput.IncomingStart(count);
Buffer.BlockCopy(buffer, offset, inputBuffer.Data.Array, inputBuffer.Data.Offset, count);
_socketInput.IncomingComplete(count, error: null);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token)
{
Write(buffer, offset, count);
return _emptyTask;
}
protected override void Dispose(bool disposing)
{
// Close _socketInput with a 0-length write.
Write(_emptyBuffer, 0, 0);
base.Dispose(disposing);
}
}
}

View File

@ -0,0 +1,35 @@
// 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;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Server.Kestrel.Http;
namespace Microsoft.AspNet.Server.Kestrel.Filter
{
public class StreamSocketOutput : ISocketOutput
{
private static readonly Task _emptyTask = Task.FromResult<object>(null);
private readonly Stream _outputStream;
public StreamSocketOutput(Stream outputStream)
{
_outputStream = outputStream;
}
void ISocketOutput.Write(ArraySegment<byte> buffer, bool immediate)
{
_outputStream.Write(buffer.Array, buffer.Offset, buffer.Count);
}
Task ISocketOutput.WriteAsync(ArraySegment<byte> buffer, bool immediate, CancellationToken cancellationToken)
{
// TODO: Use _outputStream.WriteAsync
_outputStream.Write(buffer.Array, buffer.Offset, buffer.Count);
return _emptyTask;
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Server.Kestrel.Filter;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
using Microsoft.AspNet.Server.Kestrel.Networking;
using Microsoft.Extensions.Logging;
@ -18,9 +19,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
private static long _lastConnectionId;
private readonly UvStreamHandle _socket;
private readonly Frame _frame;
private Frame _frame;
private ConnectionFilterContext _filterContext;
private readonly long _connectionId;
private readonly SocketInput _rawSocketInput;
private readonly SocketOutput _rawSocketOutput;
private readonly object _stateLock = new object();
private ConnectionState _connectionState;
@ -31,16 +36,67 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
_connectionId = Interlocked.Increment(ref _lastConnectionId);
SocketInput = new SocketInput(Memory2);
SocketOutput = new SocketOutput(Thread, _socket, _connectionId, Log);
_frame = new Frame(this);
_rawSocketInput = new SocketInput(Memory2);
_rawSocketOutput = new SocketOutput(Thread, _socket, _connectionId, Log);
}
public void Start()
{
Log.ConnectionStart(_connectionId);
_frame.Start();
// Start socket prior to applying the ConnectionFilter
_socket.ReadStart(_allocCallback, _readCallback, this);
// Don't initialize _frame until SocketInput and SocketOutput are set to their final values.
if (ConnectionFilter == null)
{
SocketInput = _rawSocketInput;
SocketOutput = _rawSocketOutput;
_frame = new Frame(this);
_frame.Start();
}
else
{
var libuvStream = new LibuvStream(_rawSocketInput, _rawSocketOutput);
_filterContext = new ConnectionFilterContext
{
Connection = libuvStream,
Address = ServerAddress
};
ConnectionFilter.OnConnection(_filterContext).ContinueWith((task, state) =>
{
var connection = (Connection)state;
if (task.IsFaulted)
{
connection.Log.LogError("ConnectionFilter.OnConnection", task.Exception);
ConnectionControl.End(ProduceEndType.SocketDisconnect);
}
else if (task.IsCanceled)
{
connection.Log.LogError("ConnectionFilter.OnConnection Canceled");
ConnectionControl.End(ProduceEndType.SocketDisconnect);
}
else
{
connection.ApplyConnectionFilter();
}
}, this);
}
}
private void ApplyConnectionFilter()
{
var filteredStreamAdapter = new FilteredStreamAdapter(_filterContext.Connection, Memory2, Log);
SocketInput = filteredStreamAdapter.SocketInput;
SocketOutput = filteredStreamAdapter.SocketOutput;
_frame = new Frame(this);
_frame.Start();
}
private static Libuv.uv_buf_t AllocCallback(UvStreamHandle handle, int suggestedSize, object state)
@ -50,7 +106,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
private Libuv.uv_buf_t OnAlloc(UvStreamHandle handle, int suggestedSize)
{
var result = SocketInput.IncomingStart(2048);
var result = _rawSocketInput.IncomingStart(2048);
return handle.Libuv.buf_init(
result.DataPtr,
@ -78,7 +134,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
Log.ConnectionReadFin(_connectionId);
}
SocketInput.IncomingComplete(readCount, errorDone ? error : null);
_rawSocketInput.IncomingComplete(readCount, errorDone ? error : null);
}
void IConnectionControl.Pause()
@ -107,7 +163,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
_connectionState = ConnectionState.Shutdown;
Log.ConnectionWriteFin(_connectionId);
SocketOutput.End(endType);
_rawSocketOutput.End(endType);
break;
case ProduceEndType.ConnectionKeepAlive:
if (_connectionState != ConnectionState.Open)
@ -125,7 +181,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
_connectionState = ConnectionState.Disconnected;
Log.ConnectionDisconnect(_connectionId);
SocketOutput.End(endType);
_rawSocketOutput.End(endType);
break;
}
}

View File

@ -14,6 +14,5 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
void Write(ArraySegment<byte> buffer, bool immediate = true);
Task WriteAsync(ArraySegment<byte> buffer, bool immediate = true, CancellationToken cancellationToken = default(CancellationToken));
void End(ProduceEndType endType);
}
}

View File

@ -21,12 +21,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
protected UvStreamHandle ListenSocket { get; private set; }
public Task StartAsync(
string scheme,
string host,
int port,
ServerAddress address,
KestrelThread thread,
Func<Frame, Task> application)
{
ServerAddress = address;
Thread = thread;
Application = application;
@ -35,7 +34,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
try
{
ListenSocket = CreateListenSocket(host, port);
ListenSocket = CreateListenSocket();
tcs.SetResult(0);
}
catch (Exception ex)
@ -49,7 +48,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
/// <summary>
/// Creates the socket used to listen for incoming connections
/// </summary>
protected abstract UvStreamHandle CreateListenSocket(string host, int port);
protected abstract UvStreamHandle CreateListenSocket();
protected static void ConnectionCallback(UvStreamHandle stream, int status, Exception error, object state)
{

View File

@ -3,6 +3,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Server.Kestrel.Filter;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
namespace Microsoft.AspNet.Server.Kestrel.Http
@ -23,13 +24,15 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
public ListenerContext(ListenerContext listenerContext)
: base(listenerContext)
{
ServerAddress = listenerContext.ServerAddress;
Thread = listenerContext.Thread;
Application = listenerContext.Application;
Memory = listenerContext.Memory;
Memory2 = listenerContext.Memory2;
Log = listenerContext.Log;
}
public ServerAddress ServerAddress { get; set; }
public KestrelThread Thread { get; set; }
public Func<Frame, Task> Application { get; set; }

View File

@ -31,13 +31,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
public async Task StartAsync(
string pipeName,
string scheme,
string host,
int port,
ServerAddress address,
KestrelThread thread,
Func<Frame, Task> application)
{
await StartAsync(scheme, host, port, thread, application).ConfigureAwait(false);
await StartAsync(address, thread, application).ConfigureAwait(false);
await Thread.PostAsync(_ =>
{

View File

@ -24,9 +24,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
public Task StartAsync(
string pipeName,
ServerAddress address,
KestrelThread thread,
Func<Frame, Task> application)
{
ServerAddress = address;
Thread = thread;
Application = application;

View File

@ -106,27 +106,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
}
public override async Task<int> ReadAsyncImplementation(ArraySegment<byte> buffer, CancellationToken cancellationToken)
public override Task<int> ReadAsyncImplementation(ArraySegment<byte> buffer, CancellationToken cancellationToken)
{
var input = _context.SocketInput;
while (true)
{
await input;
var begin = input.ConsumingStart();
int actual;
var end = begin.CopyTo(buffer.Array, buffer.Offset, buffer.Count, out actual);
input.ConsumingComplete(end, end);
if (actual != 0)
{
return actual;
}
if (input.RemoteIntakeFin)
{
return 0;
}
}
return _context.SocketInput.ReadAsync(buffer);
}
}
@ -147,30 +129,22 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
var input = _context.SocketInput;
while (true)
var limit = Math.Min(buffer.Count, _inputLength);
if (limit == 0)
{
var limit = Math.Min(buffer.Count, _inputLength);
if (limit == 0)
{
return 0;
}
await input;
var begin = input.ConsumingStart();
int actual;
var end = begin.CopyTo(buffer.Array, buffer.Offset, limit, out actual);
_inputLength -= actual;
input.ConsumingComplete(end, end);
if (actual != 0)
{
return actual;
}
if (input.RemoteIntakeFin)
{
throw new InvalidDataException("Unexpected end of request content");
}
return 0;
}
var limitedBuffer = new ArraySegment<byte>(buffer.Array, buffer.Offset, limit);
var actual = await _context.SocketInput.ReadAsync(limitedBuffer);
_inputLength -= actual;
if (actual == 0)
{
throw new InvalidDataException("Unexpected end of request content");
}
return actual;
}
}

View File

@ -20,11 +20,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
/// <summary>
/// Creates the socket used to listen for incoming connections
/// </summary>
protected override UvStreamHandle CreateListenSocket(string host, int port)
protected override UvStreamHandle CreateListenSocket()
{
var socket = new UvPipeHandle(Log);
socket.Init(Thread.Loop, false);
socket.Bind(host);
socket.Bind(ServerAddress.UnixPipePath);
socket.Listen(Constants.ListenBacklog, ConnectionCallback, this);
return socket;
}

View File

@ -20,11 +20,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
/// <summary>
/// Creates the socket used to listen for incoming connections
/// </summary>
protected override UvStreamHandle CreateListenSocket(string host, int port)
protected override UvStreamHandle CreateListenSocket()
{
var socket = new UvPipeHandle(Log);
socket.Init(Thread.Loop, false);
socket.Bind(host);
socket.Bind(ServerAddress.UnixPipePath);
socket.Listen(Constants.ListenBacklog, ConnectionCallback, this);
return socket;
}

View File

@ -0,0 +1,33 @@
// 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;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Server.Kestrel.Http
{
public static class SocketInputExtensions
{
public static async Task<int> ReadAsync(this SocketInput input, ArraySegment<byte> buffer)
{
while (true)
{
await input;
var begin = input.ConsumingStart();
int actual;
var end = begin.CopyTo(buffer.Array, buffer.Offset, buffer.Count, out actual);
input.ConsumingComplete(end, end);
if (actual != 0)
{
return actual;
}
if (input.RemoteIntakeFin)
{
return 0;
}
}
}
}
}

View File

@ -114,6 +114,25 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
}
}
public void End(ProduceEndType endType)
{
switch (endType)
{
case ProduceEndType.SocketShutdownSend:
Write(default(ArraySegment<byte>), (error, state, calledInline) => { }, null,
immediate: true,
socketShutdownSend: true,
socketDisconnect: false);
break;
case ProduceEndType.SocketDisconnect:
Write(default(ArraySegment<byte>), (error, state, calledInline) => { }, null,
immediate: true,
socketShutdownSend: false,
socketDisconnect: true);
break;
}
}
private void ScheduleWrite()
{
_thread.Post(_this => _this.WriteAllPending(), this);
@ -295,25 +314,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
return tcs.Task;
}
void ISocketOutput.End(ProduceEndType endType)
{
switch (endType)
{
case ProduceEndType.SocketShutdownSend:
Write(default(ArraySegment<byte>), (error, state, calledInline) => { }, null,
immediate: true,
socketShutdownSend: true,
socketDisconnect: false);
break;
case ProduceEndType.SocketDisconnect:
Write(default(ArraySegment<byte>), (error, state, calledInline) => { }, null,
immediate: true,
socketShutdownSend: false,
socketDisconnect: true);
break;
}
}
private class CallbackContext
{
// callback(error, state, calledInline)

View File

@ -21,11 +21,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
/// <summary>
/// Creates the socket used to listen for incoming connections
/// </summary>
protected override UvStreamHandle CreateListenSocket(string host, int port)
protected override UvStreamHandle CreateListenSocket()
{
var socket = new UvTcpHandle(Log);
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
socket.Bind(host, port);
socket.Bind(ServerAddress);
socket.Listen(Constants.ListenBacklog, ConnectionCallback, this);
return socket;
}

View File

@ -21,11 +21,11 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
/// <summary>
/// Creates the socket used to listen for incoming connections
/// </summary>
protected override UvStreamHandle CreateListenSocket(string host, int port)
protected override UvStreamHandle CreateListenSocket()
{
var socket = new UvTcpHandle(Log);
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
socket.Bind(host, port);
socket.Bind(ServerAddress);
socket.Listen(Constants.ListenBacklog, ConnectionCallback, this);
return socket;
}

View File

@ -1,10 +1,14 @@
// 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 Microsoft.AspNet.Server.Kestrel.Filter;
namespace Microsoft.AspNet.Server.Kestrel
{
public interface IKestrelServerInformation
{
int ThreadCount { get; set; }
IConnectionFilter ConnectionFilter { get; set; }
}
}

View File

@ -98,15 +98,11 @@ namespace Microsoft.AspNet.Server.Kestrel
Threads.Clear();
}
public IDisposable CreateServer(string scheme, string host, int port, Func<Frame, Task> application)
public IDisposable CreateServer(ServerAddress address, Func<Frame, Task> application)
{
var listeners = new List<IDisposable>();
var usingPipes = host.StartsWith(Constants.UnixPipeHostPrefix);
if (usingPipes)
{
// Subtract one because we want to include the '/' character that starts the path.
host = host.Substring(Constants.UnixPipeHostPrefix.Length - 1);
}
var usingPipes = address.IsUnixPipe;
try
{
@ -123,7 +119,7 @@ namespace Microsoft.AspNet.Server.Kestrel
(Listener) new PipeListener(this) :
new TcpListener(this);
listeners.Add(listener);
listener.StartAsync(scheme, host, port, thread, application).Wait();
listener.StartAsync(address, thread, application).Wait();
}
else if (first)
{
@ -132,7 +128,7 @@ namespace Microsoft.AspNet.Server.Kestrel
: new TcpListenerPrimary(this);
listeners.Add(listener);
listener.StartAsync(pipeName, scheme, host, port, thread, application).Wait();
listener.StartAsync(pipeName, address, thread, application).Wait();
}
else
{
@ -140,7 +136,7 @@ namespace Microsoft.AspNet.Server.Kestrel
? (ListenerSecondary) new PipeListenerSecondary(this)
: new TcpListenerSecondary(this);
listeners.Add(listener);
listener.StartAsync(pipeName, thread, application).Wait();
listener.StartAsync(pipeName, address, thread, application).Wait();
}
first = false;

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Server.Features;
using Microsoft.AspNet.Server.Kestrel.Filter;
using Microsoft.Extensions.Configuration;
namespace Microsoft.AspNet.Server.Kestrel
@ -14,6 +15,8 @@ namespace Microsoft.AspNet.Server.Kestrel
public int ThreadCount { get; set; }
public IConnectionFilter ConnectionFilter { get; set; }
public void Initialize(IConfiguration configuration)
{
var urls = configuration["server.urls"] ?? string.Empty;

View File

@ -33,9 +33,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
_uv.tcp_init(loop, this);
}
public void Bind(string host, int port)
public void Bind(ServerAddress address)
{
var endpoint = CreateIPEndpoint(host, port);
var endpoint = CreateIPEndpoint(address);
Libuv.sockaddr addr;
var addressText = endpoint.Address.ToString();
@ -65,14 +65,14 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
/// Returns an <see cref="IPEndPoint"/> for the given host an port.
/// If the host parameter isn't "localhost" or an IP address, use IPAddress.Any.
/// </summary>
public static IPEndPoint CreateIPEndpoint(string host, int port)
public static IPEndPoint CreateIPEndpoint(ServerAddress address)
{
// TODO: IPv6 support
IPAddress ip;
if (!IPAddress.TryParse(host, out ip))
if (!IPAddress.TryParse(address.Host, out ip))
{
if (string.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase))
if (string.Equals(address.Host, "localhost", StringComparison.OrdinalIgnoreCase))
{
ip = IPAddress.Loopback;
}
@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
}
}
return new IPEndPoint(ip, port);
return new IPEndPoint(ip, address.Port);
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Globalization;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
@ -14,6 +15,24 @@ namespace Microsoft.AspNet.Server.Kestrel
public int Port { get; private set; }
public string Scheme { get; private set; }
public bool IsUnixPipe
{
get
{
return Host.StartsWith(Constants.UnixPipeHostPrefix);
}
}
public string UnixPipePath
{
get
{
Debug.Assert(IsUnixPipe);
return Host.Substring(Constants.UnixPipeHostPrefix.Length - 1);
}
}
public override string ToString()
{
return Scheme.ToLowerInvariant() + "://" + Host.ToLowerInvariant() + ":" + Port.ToString(CultureInfo.InvariantCulture) + Path.ToLowerInvariant();

View File

@ -59,7 +59,8 @@ namespace Microsoft.AspNet.Server.Kestrel
{
AppShutdown = _appShutdownService,
Log = new KestrelTrace(_logger),
DateHeaderValueManager = dateHeaderValueManager
DateHeaderValueManager = dateHeaderValueManager,
ConnectionFilter = information.ConnectionFilter
});
disposables.Push(engine);
@ -86,9 +87,7 @@ namespace Microsoft.AspNet.Server.Kestrel
{
atLeastOneListener = true;
disposables.Push(engine.CreateServer(
parsedAddress.Scheme,
parsedAddress.Host,
parsedAddress.Port,
parsedAddress,
async frame =>
{
var request = new ServerRequest(frame);

View File

@ -1,6 +1,7 @@
// 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 Microsoft.AspNet.Server.Kestrel.Filter;
using Microsoft.AspNet.Server.Kestrel.Http;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
using Microsoft.Dnx.Runtime;
@ -20,6 +21,7 @@ namespace Microsoft.AspNet.Server.Kestrel
Memory = context.Memory;
Log = context.Log;
DateHeaderValueManager = context.DateHeaderValueManager;
ConnectionFilter = context.ConnectionFilter;
}
public IApplicationShutdown AppShutdown { get; set; }
@ -29,5 +31,7 @@ namespace Microsoft.AspNet.Server.Kestrel
public IKestrelTrace Log { get; set; }
public DateHeaderValueManager DateHeaderValueManager { get; set; }
public IConnectionFilter ConnectionFilter { get; set; }
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net;
using Microsoft.AspNet.Server.Kestrel;
using Microsoft.AspNet.Server.Kestrel.Networking;
using Xunit;
@ -16,7 +17,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
public void CorrectIPEndpointsAreCreated(string host, string expectedAddress)
{
// "0.0.0.0" is IPAddress.Any
var endpoint = UvTcpHandle.CreateIPEndpoint(host, 5000);
var endpoint = UvTcpHandle.CreateIPEndpoint(ServerAddress.FromUrl($"http://{host}:5000/"));
Assert.NotNull(endpoint);
Assert.Equal(IPAddress.Parse(expectedAddress), endpoint.Address);
Assert.Equal(5000, endpoint.Port);

View File

@ -88,7 +88,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
{
var engine = new KestrelEngine(LibraryManager, new TestServiceContext());
engine.Start(1);
var started = engine.CreateServer("http", "localhost", 54321, App);
var address = ServerAddress.FromUrl("http://localhost:54321/");
var started = engine.CreateServer(address, App);
started.Dispose();
engine.Dispose();
}
@ -99,7 +100,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
{
var engine = new KestrelEngine(LibraryManager, new TestServiceContext());
engine.Start(1);
var started = engine.CreateServer("http", "localhost", 54321, App);
var address = ServerAddress.FromUrl("http://localhost:54321/");
var started = engine.CreateServer(address, App);
Console.WriteLine("Started");
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

View File

@ -166,7 +166,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
var serverListenTcp = new UvTcpHandle(_logger);
serverListenTcp.Init(loop);
serverListenTcp.Bind("0.0.0.0", 54321);
var address = ServerAddress.FromUrl("http://localhost:54321/");
serverListenTcp.Bind(address);
serverListenTcp.Listen(128, (_1, status, error, _2) =>
{
var serverConnectionTcp = new UvTcpHandle(_logger);

View File

@ -81,7 +81,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
loop.Init(_uv);
var tcp = new UvTcpHandle(_logger);
tcp.Init(loop);
tcp.Bind("localhost", 0);
var address = ServerAddress.FromUrl("http://localhost:0/");
tcp.Bind(address);
tcp.Dispose();
loop.Run();
loop.Dispose();
@ -95,7 +96,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
loop.Init(_uv);
var tcp = new UvTcpHandle(_logger);
tcp.Init(loop);
tcp.Bind("localhost", 54321);
var address = ServerAddress.FromUrl("http://localhost:54321/");
tcp.Bind(address);
tcp.Listen(10, (stream, status, error, state) =>
{
var tcp2 = new UvTcpHandle(_logger);
@ -132,7 +134,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
loop.Init(_uv);
var tcp = new UvTcpHandle(_logger);
tcp.Init(loop);
tcp.Bind("localhost", 54321);
var address = ServerAddress.FromUrl("http://localhost:54321/");
tcp.Bind(address);
tcp.Listen(10, (_, status, error, state) =>
{
Console.WriteLine("Connected");
@ -188,7 +191,8 @@ namespace Microsoft.AspNet.Server.KestrelTests
loop.Init(_uv);
var tcp = new UvTcpHandle(_logger);
tcp.Init(loop);
tcp.Bind("localhost", 54321);
var address = ServerAddress.FromUrl("http://localhost:54321/");
tcp.Bind(address);
tcp.Listen(10, (_, status, error, state) =>
{
Console.WriteLine("Connected");

View File

@ -37,7 +37,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new KestrelTrace(new TestKestrelTrace()));
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace());
var socketOutput = new SocketOutput(kestrelThread, socket, 0, trace);
@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new KestrelTrace(new TestKestrelTrace()));
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace());
var socketOutput = new SocketOutput(kestrelThread, socket, 0, trace);

View File

@ -52,9 +52,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
new TestServiceContext());
_engine.Start(1);
_server = _engine.CreateServer(
"http",
"localhost",
54321,
ServerAddress.FromUrl("http://localhost:54321/"),
app);
}