Specialized struct generics rather than interface (#1640)

Changed the IHttpParser interface to be generic. This lets use a struct to 
get better code generation and also should allow us to inline calls back into
the handler from the parser.
This commit is contained in:
Ben Adams 2017-04-11 03:30:18 +01:00 committed by David Fowler
parent 1faaefef30
commit d29e4d4cf0
21 changed files with 78 additions and 62 deletions

View File

@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
return WriteAsync(default(ArraySegment<byte>), chunk: false, cancellationToken: cancellationToken);
}
public void Write<T>(Action<WritableBuffer, T> callback, T state)
public void Write<T>(Action<WritableBuffer, T> callback, T state) where T : struct
{
lock (_sync)
{

View File

@ -26,7 +26,7 @@ using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public abstract partial class Frame : IFrameControl, IHttpRequestLineHandler, IHttpHeadersHandler
public abstract partial class Frame : IFrameControl
{
private const byte ByteAsterisk = (byte)'*';
private const byte ByteForwardSlash = (byte)'/';
@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private static readonly ArraySegment<byte> _endChunkedResponseBytes = CreateAsciiByteArraySegment("0\r\n\r\n");
private static readonly ArraySegment<byte> _continueBytes = CreateAsciiByteArraySegment("HTTP/1.1 100 Continue\r\n\r\n");
private static readonly Action<WritableBuffer, Frame> _writeHeaders = WriteResponseHeaders;
private static readonly Action<WritableBuffer, FrameAdapter> _writeHeaders = WriteResponseHeaders;
private static readonly byte[] _bytesConnectionClose = Encoding.ASCII.GetBytes("\r\nConnection: close");
private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive");
@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
protected long _responseBytesWritten;
private readonly FrameContext _frameContext;
private readonly IHttpParser _parser;
private readonly IHttpParser<FrameAdapter> _parser;
public Frame(FrameContext frameContext)
{
@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
ServerOptions = ServiceContext.ServerOptions;
_parser = ServiceContext.HttpParserFactory(this);
_parser = ServiceContext.HttpParserFactory(new FrameAdapter(this));
FrameControl = this;
_keepAliveMilliseconds = (long)ServerOptions.Limits.KeepAliveTimeout.TotalMilliseconds;
@ -988,11 +988,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
responseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
}
Output.Write(_writeHeaders, this);
Output.Write(_writeHeaders, new FrameAdapter(this));
}
private static void WriteResponseHeaders(WritableBuffer writableBuffer, Frame frame)
private static void WriteResponseHeaders(WritableBuffer writableBuffer, FrameAdapter frameAdapter)
{
var frame = frameAdapter.Frame;
var writer = new WritableBufferWriter(writableBuffer);
var responseHeaders = frame.FrameResponseHeaders;
@ -1050,7 +1051,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
overLength = true;
}
var result = _parser.ParseRequestLine(this, buffer, out consumed, out examined);
var result = _parser.ParseRequestLine(new FrameAdapter(this), buffer, out consumed, out examined);
if (!result && overLength)
{
RejectRequest(RequestRejectionReason.RequestLineTooLong);
@ -1072,7 +1073,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
overLength = true;
}
var result = _parser.ParseHeaders(this, buffer, out consumed, out examined, out var consumedBytes);
var result = _parser.ParseHeaders(new FrameAdapter(this), buffer, out consumed, out examined, out var consumedBytes);
_remainingRequestHeadersBytesAllowed -= consumedBytes;
if (!result && overLength)

View File

@ -0,0 +1,23 @@
// 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.AspNetCore.Server.Kestrel.Internal.System;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public struct FrameAdapter : IHttpRequestLineHandler, IHttpHeadersHandler
{
public Frame Frame;
public FrameAdapter(Frame frame)
{
Frame = frame;
}
public void OnHeader(Span<byte> name, Span<byte> value)
=> Frame.OnHeader(name, value);
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
=> Frame.OnStartLine(method, version, target, path, query, customMethod, pathEncoded);
}
}

View File

@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public class HttpParser : IHttpParser
public class HttpParser<TRequestHandler> : IHttpParser<TRequestHandler> where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler
{
public HttpParser(IKestrelTrace log)
{
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private const byte ByteQuestionMark = (byte)'?';
private const byte BytePercentage = (byte)'%';
public unsafe bool ParseRequestLine(IHttpRequestLineHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
public unsafe bool ParseRequestLine(TRequestHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
{
consumed = buffer.Start;
examined = buffer.End;
@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return true;
}
private unsafe void ParseRequestLine(IHttpRequestLineHandler handler, byte* data, int length)
private unsafe void ParseRequestLine(TRequestHandler handler, byte* data, int length)
{
int offset;
Span<byte> customMethod = default(Span<byte>);
@ -183,7 +183,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
handler.OnStartLine(method, httpVersion, targetBuffer, pathBuffer, query, customMethod, pathEncoded);
}
public unsafe bool ParseHeaders(IHttpHeadersHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out int consumedBytes)
public unsafe bool ParseHeaders(TRequestHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out int consumedBytes)
{
consumed = buffer.Start;
examined = buffer.End;
@ -346,7 +346,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void TakeSingleHeader(byte* headerLine, int length, IHttpHeadersHandler handler)
private unsafe void TakeSingleHeader(byte* headerLine, int length, TRequestHandler handler)
{
// Skip CR, LF from end position
var valueEnd = length - 3;

View File

@ -5,11 +5,11 @@ using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public interface IHttpParser
public interface IHttpParser<TRequestHandler> where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler
{
bool ParseRequestLine(IHttpRequestLineHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined);
bool ParseRequestLine(TRequestHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined);
bool ParseHeaders(IHttpHeadersHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out int consumedBytes);
bool ParseHeaders(TRequestHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out int consumedBytes);
void Reset();
}

View File

@ -17,6 +17,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
Task WriteAsync(ArraySegment<byte> buffer, bool chunk = false, CancellationToken cancellationToken = default(CancellationToken));
void Flush();
Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken));
void Write<T>(Action<WritableBuffer, T> write, T state);
void Write<T>(Action<WritableBuffer, T> write, T state) where T : struct;
}
}

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public IThreadPool ThreadPool { get; set; }
public Func<Frame, IHttpParser> HttpParserFactory { get; set; }
public Func<FrameAdapter, IHttpParser<FrameAdapter>> HttpParserFactory { get; set; }
public DateHeaderValueManager DateHeaderValueManager { get; set; }

View File

@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
var serviceContext = new ServiceContext
{
Log = trace,
HttpParserFactory = frame => new HttpParser(frame.ServiceContext.Log),
HttpParserFactory = frameParser => new HttpParser<FrameAdapter>(frameParser.Frame.ServiceContext.Log),
ThreadPool = threadPool,
DateHeaderValueManager = _dateHeaderValueManager,
ServerOptions = Options

View File

@ -256,28 +256,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
"42,000",
"42.000",
};
private class NoopHttpParser : IHttpParser
{
public bool ParseHeaders(IHttpHeadersHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out int consumedBytes)
{
consumed = buffer.Start;
examined = buffer.End;
consumedBytes = 0;
return false;
}
public bool ParseRequestLine(IHttpRequestLineHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
{
consumed = buffer.Start;
examined = buffer.End;
return false;
}
public void Reset()
{
}
}
}
}

View File

@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System;
@ -418,7 +417,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(buffer.End, examined);
}
private IHttpParser CreateParser(IKestrelTrace log) => new HttpParser(log);
private IHttpParser<RequestHandler> CreateParser(IKestrelTrace log) => new HttpParser<RequestHandler>(log);
public static IEnumerable<string[]> RequestLineValidData => HttpParsingData.RequestLineValidData;

View File

@ -38,11 +38,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var called = false;
((ISocketOutput)socketOutput).Write<object>((buffer, state) =>
((ISocketOutput)socketOutput).Write((buffer, state) =>
{
called = true;
},
null);
0);
Assert.False(called);
}

View File

@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
var serviceContext = new ServiceContext
{
HttpParserFactory = _ => NullParser.Instance,
HttpParserFactory = _ => NullParser<FrameAdapter>.Instance,
ServerOptions = new KestrelServerOptions()
};
var frameContext = new FrameContext

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
var serviceContext = new ServiceContext
{
HttpParserFactory = _ => NullParser.Instance,
HttpParserFactory = _ => NullParser<FrameAdapter>.Instance,
ServerOptions = new KestrelServerOptions()
};
var frameContext = new FrameContext

View File

@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
DateHeaderValueManager = new DateHeaderValueManager(),
ServerOptions = new KestrelServerOptions(),
Log = new MockTrace(),
HttpParserFactory = f => new HttpParser(log: null)
HttpParserFactory = f => new HttpParser<FrameAdapter>(log: null)
};
var frameContext = new FrameContext
{

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
public class KestrelHttpParserBenchmark : IHttpRequestLineHandler, IHttpHeadersHandler
{
private readonly HttpParser _parser = new HttpParser(log: null);
private readonly HttpParser<Adapter> _parser = new HttpParser<Adapter>(log: null);
private ReadableBuffer _buffer;
@ -53,14 +53,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
private void ParseData()
{
if (!_parser.ParseRequestLine(this, _buffer, out var consumed, out var examined))
if (!_parser.ParseRequestLine(new Adapter(this), _buffer, out var consumed, out var examined))
{
ErrorUtilities.ThrowInvalidRequestHeaders();
}
_buffer = _buffer.Slice(consumed, _buffer.End);
if (!_parser.ParseHeaders(this, _buffer, out consumed, out examined, out var consumedBytes))
if (!_parser.ParseHeaders(new Adapter(this), _buffer, out consumed, out examined, out var consumedBytes))
{
ErrorUtilities.ThrowInvalidRequestHeaders();
}
@ -73,5 +73,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
public void OnHeader(Span<byte> name, Span<byte> value)
{
}
private struct Adapter : IHttpRequestLineHandler, IHttpHeadersHandler
{
public KestrelHttpParserBenchmark RequestHandler;
public Adapter(KestrelHttpParserBenchmark requestHandler)
{
RequestHandler = requestHandler;
}
public void OnHeader(Span<byte> name, Span<byte> value)
=> RequestHandler.OnHeader(name, value);
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
=> RequestHandler.OnStartLine(method, version, target, path, query, customMethod, pathEncoded);
}
}
}

View File

@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
public class NullParser : IHttpParser
public class NullParser<TRequestHandler> : IHttpParser<TRequestHandler> where TRequestHandler : struct, IHttpHeadersHandler, IHttpRequestLineHandler
{
private readonly byte[] _startLine = Encoding.ASCII.GetBytes("GET /plaintext HTTP/1.1\r\n");
private readonly byte[] _target = Encoding.ASCII.GetBytes("/plaintext");
@ -19,9 +19,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
private readonly byte[] _connectionHeaderName = Encoding.ASCII.GetBytes("Connection");
private readonly byte[] _connectionHeaderValue = Encoding.ASCII.GetBytes("keep-alive");
public static readonly NullParser Instance = new NullParser();
public static readonly NullParser<FrameAdapter> Instance = new NullParser<FrameAdapter>();
public bool ParseHeaders(IHttpHeadersHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out int consumedBytes)
public bool ParseHeaders(TRequestHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined, out int consumedBytes)
{
handler.OnHeader(new Span<byte>(_hostHeaderName), new Span<byte>(_hostHeaderValue));
handler.OnHeader(new Span<byte>(_acceptHeaderName), new Span<byte>(_acceptHeaderValue));
@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
return true;
}
public bool ParseRequestLine(IHttpRequestLineHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
public bool ParseRequestLine(TRequestHandler handler, ReadableBuffer buffer, out ReadCursor consumed, out ReadCursor examined)
{
handler.OnStartLine(HttpMethod.Get,
HttpVersion.Http11,

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
var serviceContext = new ServiceContext
{
HttpParserFactory = f => new HttpParser(f.ServiceContext.Log),
HttpParserFactory = f => new HttpParser<FrameAdapter>(f.Frame.ServiceContext.Log),
ServerOptions = new KestrelServerOptions()
};
var frameContext = new FrameContext

View File

@ -170,7 +170,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
var serviceContext = new ServiceContext
{
HttpParserFactory = f => new HttpParser(f.ServiceContext.Log),
HttpParserFactory = f => new HttpParser<FrameAdapter>(f.Frame.ServiceContext.Log),
ServerOptions = new KestrelServerOptions()
};
var frameContext = new FrameContext

View File

@ -120,7 +120,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
DateHeaderValueManager = new DateHeaderValueManager(),
ServerOptions = new KestrelServerOptions(),
Log = new MockTrace(),
HttpParserFactory = f => new HttpParser(log: null)
HttpParserFactory = f => new HttpParser<FrameAdapter>(log: null)
};
var frameContext = new FrameContext

View File

@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Testing
return TaskCache.CompletedTask;
}
public void Write<T>(Action<WritableBuffer, T> write, T state)
public void Write<T>(Action<WritableBuffer, T> write, T state) where T : struct
{
}

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Testing
ThreadPool = new LoggingThreadPool(Log);
DateHeaderValueManager = new DateHeaderValueManager(systemClock: new MockSystemClock());
DateHeaderValue = DateHeaderValueManager.GetDateHeaderValues().String;
HttpParserFactory = frame => new HttpParser(frame.ServiceContext.Log);
HttpParserFactory = frameAdapter => new HttpParser<FrameAdapter>(frameAdapter.Frame.ServiceContext.Log);
ServerOptions = new KestrelServerOptions
{
AddServerHeader = false