Perf
This commit is contained in:
parent
a91238d8d2
commit
dca31cc6f6
|
|
@ -0,0 +1,51 @@
|
|||
// 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.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Benchmarks.Middleware
|
||||
{
|
||||
public class PlaintextMiddleware
|
||||
{
|
||||
private static readonly PathString _path = new PathString("/plaintext");
|
||||
private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!");
|
||||
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public PlaintextMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal))
|
||||
{
|
||||
return WriteResponse(httpContext.Response);
|
||||
}
|
||||
|
||||
return _next(httpContext);
|
||||
}
|
||||
|
||||
public static Task WriteResponse(HttpResponse response)
|
||||
{
|
||||
var payloadLength = _helloWorldPayload.Length;
|
||||
response.StatusCode = 200;
|
||||
response.ContentType = "text/plain";
|
||||
response.ContentLength = payloadLength;
|
||||
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlaintextMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UsePlainText(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<PlaintextMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Benchmarks.Middleware;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
|
@ -18,6 +19,7 @@ namespace NativeIISSample
|
|||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAuthenticationSchemeProvider authSchemeProvider)
|
||||
{
|
||||
app.UsePlainText();
|
||||
app.Run(async (context) =>
|
||||
{
|
||||
context.Response.ContentType = "text/plain";
|
||||
|
|
|
|||
|
|
@ -60,4 +60,11 @@ SAFEIsDigit(UCHAR c)
|
|||
return isdigit( c );
|
||||
}
|
||||
|
||||
#define __RETURN_GLE_FAIL(str) return HRESULT_FROM_WIN32(GetLastError());
|
||||
#define __RETURN_HR_FAIL(hr, str) do { HRESULT __hr = (hr); return __hr; } while (0, 0)
|
||||
|
||||
#define RETURN_IF_FAILED(hr) do { HRESULT __hrRet = hr; if (FAILED(__hrRet)) { __RETURN_HR_FAIL(__hrRet, #hr); }} while (0, 0)
|
||||
#define RETURN_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { __RETURN_GLE_FAIL(#ptr); }} while (0, 0)
|
||||
#define RETURN_IF_HANDLE_INVALID(handle) do { HANDLE __hRet = (handle); if (__hRet == INVALID_HANDLE_VALUE) { __RETURN_GLE_FAIL(#handle); }} while (0, 0)
|
||||
|
||||
#endif // _MACROS_H
|
||||
|
|
|
|||
|
|
@ -20,9 +20,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
IHttpRequestFeature,
|
||||
IHttpResponseFeature,
|
||||
IHttpUpgradeFeature,
|
||||
IHttpConnectionFeature,
|
||||
IHttpRequestLifetimeFeature,
|
||||
IHttpRequestIdentifierFeature,
|
||||
IHttpAuthenticationFeature,
|
||||
IServerVariablesFeature
|
||||
{
|
||||
|
|
@ -183,49 +181,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
|
||||
bool IHttpResponseFeature.HasStarted => HasResponseStarted;
|
||||
|
||||
// The UpgradeAvailable Feature is set on the first request to the server.
|
||||
bool IHttpUpgradeFeature.IsUpgradableRequest => _websocketAvailability == WebsocketAvailabilityStatus.Available;
|
||||
bool IHttpUpgradeFeature.IsUpgradableRequest => _server.IsWebSocketAvailible(_pInProcessHandler);
|
||||
|
||||
bool IFeatureCollection.IsReadOnly => false;
|
||||
|
||||
int IFeatureCollection.Revision => _featureRevision;
|
||||
|
||||
IPAddress IHttpConnectionFeature.RemoteIpAddress
|
||||
{
|
||||
get => RemoteIpAddress;
|
||||
set => RemoteIpAddress = value;
|
||||
}
|
||||
|
||||
IPAddress IHttpConnectionFeature.LocalIpAddress
|
||||
{
|
||||
get => LocalIpAddress;
|
||||
set => LocalIpAddress = value;
|
||||
}
|
||||
|
||||
int IHttpConnectionFeature.RemotePort
|
||||
{
|
||||
get => RemotePort;
|
||||
set => RemotePort = value;
|
||||
}
|
||||
|
||||
int IHttpConnectionFeature.LocalPort
|
||||
{
|
||||
get => LocalPort;
|
||||
set => LocalPort = value;
|
||||
}
|
||||
|
||||
string IHttpConnectionFeature.ConnectionId
|
||||
{
|
||||
get => RequestConnectionId;
|
||||
set => RequestConnectionId = value;
|
||||
}
|
||||
|
||||
string IHttpRequestIdentifierFeature.TraceIdentifier
|
||||
{
|
||||
get => TraceIdentifier;
|
||||
set => TraceIdentifier = value;
|
||||
}
|
||||
|
||||
ClaimsPrincipal IHttpAuthenticationFeature.User
|
||||
{
|
||||
get => User;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
// 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.Globalization;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||
{
|
||||
internal partial class IISHttpContext : IHttpConnectionFeature
|
||||
{
|
||||
IPAddress IHttpConnectionFeature.RemoteIpAddress
|
||||
{
|
||||
get
|
||||
{
|
||||
if (RemoteIpAddress == null)
|
||||
{
|
||||
InitializeRemoteEndpoint();
|
||||
}
|
||||
|
||||
return RemoteIpAddress;
|
||||
}
|
||||
set => RemoteIpAddress = value;
|
||||
}
|
||||
|
||||
IPAddress IHttpConnectionFeature.LocalIpAddress
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LocalIpAddress == null)
|
||||
{
|
||||
InitializeRemoteEndpoint();
|
||||
}
|
||||
return LocalIpAddress;
|
||||
}
|
||||
set => LocalIpAddress = value;
|
||||
}
|
||||
|
||||
int IHttpConnectionFeature.RemotePort
|
||||
{
|
||||
get
|
||||
{
|
||||
if (RemoteIpAddress == null)
|
||||
{
|
||||
InitializeRemoteEndpoint();
|
||||
}
|
||||
|
||||
return RemotePort;
|
||||
}
|
||||
set => RemotePort = value;
|
||||
}
|
||||
|
||||
int IHttpConnectionFeature.LocalPort
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LocalIpAddress == null)
|
||||
{
|
||||
InitializeRemoteEndpoint();
|
||||
}
|
||||
|
||||
return LocalPort;
|
||||
}
|
||||
set => LocalPort = value;
|
||||
}
|
||||
|
||||
string IHttpConnectionFeature.ConnectionId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (RequestConnectionId == null)
|
||||
{
|
||||
InitializeConnectionId();
|
||||
}
|
||||
|
||||
return RequestConnectionId;
|
||||
}
|
||||
set => RequestConnectionId = value;
|
||||
}
|
||||
|
||||
private void InitializeLocalEndpoint()
|
||||
{
|
||||
var localEndPoint = GetLocalEndPoint();
|
||||
LocalIpAddress = localEndPoint.GetIPAddress();
|
||||
LocalPort = localEndPoint.GetPort();
|
||||
}
|
||||
|
||||
private void InitializeRemoteEndpoint()
|
||||
{
|
||||
var remoteEndPoint = GetRemoteEndPoint();
|
||||
RemoteIpAddress = remoteEndPoint.GetIPAddress();
|
||||
RemotePort = remoteEndPoint.GetPort();
|
||||
}
|
||||
|
||||
private void InitializeConnectionId()
|
||||
{
|
||||
RequestConnectionId = ConnectionId.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// 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 Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||
{
|
||||
internal partial class IISHttpContext : IHttpRequestIdentifierFeature
|
||||
{
|
||||
string IHttpRequestIdentifierFeature.TraceIdentifier
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TraceIdentifier == null)
|
||||
{
|
||||
InitializeHttpRequestIdentifierFeature();
|
||||
}
|
||||
|
||||
return TraceIdentifier;
|
||||
}
|
||||
set => TraceIdentifier = value;
|
||||
}
|
||||
|
||||
private unsafe void InitializeHttpRequestIdentifierFeature()
|
||||
{
|
||||
// Copied from WebListener
|
||||
// This is the base GUID used by HTTP.SYS for generating the activity ID.
|
||||
// HTTP.SYS overwrites the first 8 bytes of the base GUID with RequestId to generate ETW activity ID.
|
||||
// The requestId should be set by the NativeRequestContext
|
||||
var guid = new Guid(0xffcb4c93, 0xa57f, 0x453c, 0xb6, 0x3f, 0x84, 0x71, 0xc, 0x79, 0x67, 0xbb);
|
||||
*((ulong*)&guid) = RequestId;
|
||||
|
||||
// TODO: Also make this not slow
|
||||
TraceIdentifier = guid.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,8 +28,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
private const int PauseWriterThreshold = 65536;
|
||||
private const int ResumeWriterTheshold = PauseWriterThreshold / 2;
|
||||
|
||||
// TODO make this static again.
|
||||
private static WebsocketAvailabilityStatus _websocketAvailability = WebsocketAvailabilityStatus.Uninitialized;
|
||||
|
||||
protected readonly IntPtr _pInProcessHandler;
|
||||
|
||||
|
|
@ -60,7 +58,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
private const string NtlmString = "NTLM";
|
||||
private const string NegotiateString = "Negotiate";
|
||||
private const string BasicString = "Basic";
|
||||
private const string WebSocketVersionString = "WEBSOCKET_VERSION";
|
||||
|
||||
internal unsafe IISHttpContext(MemoryPool<byte> memoryPool, IntPtr pInProcessHandler, IISOptions options, IISHttpServer server)
|
||||
: base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.HttpGetRawRequest(pInProcessHandler))
|
||||
|
|
@ -72,103 +69,64 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
_server = server;
|
||||
|
||||
NativeMethods.HttpSetManagedContext(pInProcessHandler, (IntPtr)_thisHandle);
|
||||
unsafe
|
||||
Method = GetVerb();
|
||||
|
||||
RawTarget = GetRawUrl();
|
||||
// TODO version is slow.
|
||||
HttpVersion = GetVersion();
|
||||
Scheme = SslStatus != SslStatus.Insecure ? Constants.HttpsScheme : Constants.HttpScheme;
|
||||
KnownMethod = VerbId;
|
||||
StatusCode = 200;
|
||||
|
||||
var originalPath = RequestUriBuilder.DecodeAndUnescapePath(GetRawUrlInBytes());
|
||||
|
||||
if (KnownMethod == HttpApiTypes.HTTP_VERB.HttpVerbOPTIONS && string.Equals(RawTarget, "*", StringComparison.Ordinal))
|
||||
{
|
||||
Method = GetVerb();
|
||||
PathBase = string.Empty;
|
||||
Path = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Path and pathbase are unescaped by RequestUriBuilder
|
||||
// The UsePathBase middleware will modify the pathbase and path correctly
|
||||
PathBase = string.Empty;
|
||||
Path = originalPath;
|
||||
}
|
||||
|
||||
RawTarget = GetRawUrl();
|
||||
// TODO version is slow.
|
||||
HttpVersion = GetVersion();
|
||||
Scheme = SslStatus != SslStatus.Insecure ? Constants.HttpsScheme : Constants.HttpScheme;
|
||||
KnownMethod = VerbId;
|
||||
var cookedUrl = GetCookedUrl();
|
||||
QueryString = cookedUrl.GetQueryString() ?? string.Empty;
|
||||
|
||||
var originalPath = RequestUriBuilder.DecodeAndUnescapePath(GetRawUrlInBytes());
|
||||
RequestHeaders = new RequestHeaders(this);
|
||||
HttpResponseHeaders = new HeaderCollection();
|
||||
ResponseHeaders = HttpResponseHeaders;
|
||||
|
||||
if (KnownMethod == HttpApiTypes.HTTP_VERB.HttpVerbOPTIONS && string.Equals(RawTarget, "*", StringComparison.Ordinal))
|
||||
if (options.ForwardWindowsAuthentication)
|
||||
{
|
||||
WindowsUser = GetWindowsPrincipal();
|
||||
if (options.AutomaticAuthentication)
|
||||
{
|
||||
PathBase = string.Empty;
|
||||
Path = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Path and pathbase are unescaped by RequestUriBuilder
|
||||
// The UsePathBase middleware will modify the pathbase and path correctly
|
||||
PathBase = string.Empty;
|
||||
Path = originalPath;
|
||||
User = WindowsUser;
|
||||
}
|
||||
}
|
||||
|
||||
var cookedUrl = GetCookedUrl();
|
||||
QueryString = cookedUrl.GetQueryString() ?? string.Empty;
|
||||
ResetFeatureCollection();
|
||||
|
||||
// TODO: Avoid using long.ToString, it's pretty slow
|
||||
RequestConnectionId = ConnectionId.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
// Copied from WebListener
|
||||
// This is the base GUID used by HTTP.SYS for generating the activity ID.
|
||||
// HTTP.SYS overwrites the first 8 bytes of the base GUID with RequestId to generate ETW activity ID.
|
||||
// The requestId should be set by the NativeRequestContext
|
||||
var guid = new Guid(0xffcb4c93, 0xa57f, 0x453c, 0xb6, 0x3f, 0x84, 0x71, 0xc, 0x79, 0x67, 0xbb);
|
||||
*((ulong*)&guid) = RequestId;
|
||||
|
||||
// TODO: Also make this not slow
|
||||
TraceIdentifier = guid.ToString();
|
||||
|
||||
var localEndPoint = GetLocalEndPoint();
|
||||
LocalIpAddress = localEndPoint.GetIPAddress();
|
||||
LocalPort = localEndPoint.GetPort();
|
||||
|
||||
var remoteEndPoint = GetRemoteEndPoint();
|
||||
RemoteIpAddress = remoteEndPoint.GetIPAddress();
|
||||
RemotePort = remoteEndPoint.GetPort();
|
||||
StatusCode = 200;
|
||||
|
||||
RequestHeaders = new RequestHeaders(this);
|
||||
HttpResponseHeaders = new HeaderCollection();
|
||||
ResponseHeaders = HttpResponseHeaders;
|
||||
|
||||
if (options.ForwardWindowsAuthentication)
|
||||
{
|
||||
WindowsUser = GetWindowsPrincipal();
|
||||
if (options.AutomaticAuthentication)
|
||||
{
|
||||
User = WindowsUser;
|
||||
}
|
||||
}
|
||||
|
||||
ResetFeatureCollection();
|
||||
|
||||
// Check if the Http upgrade feature is available in IIS.
|
||||
// To check this, we can look at the server variable WEBSOCKET_VERSION
|
||||
// And see if there is a version. Same check that Katana did:
|
||||
// https://github.com/aspnet/AspNetKatana/blob/9f6e09af6bf203744feb5347121fe25f6eec06d8/src/Microsoft.Owin.Host.SystemWeb/OwinAppContext.cs#L125
|
||||
// Actively not locking here as acquiring a lock on every request will hurt perf more than checking the
|
||||
// server variables a few extra times if a bunch of requests hit the server at the same time.
|
||||
if (_websocketAvailability == WebsocketAvailabilityStatus.Uninitialized)
|
||||
{
|
||||
var webSocketsAvailable = NativeMethods.HttpTryGetServerVariable(pInProcessHandler, WebSocketVersionString, out var webSocketsSupported)
|
||||
&& !string.IsNullOrEmpty(webSocketsSupported);
|
||||
|
||||
_websocketAvailability = webSocketsAvailable ?
|
||||
WebsocketAvailabilityStatus.Available :
|
||||
WebsocketAvailabilityStatus.NotAvailable;
|
||||
}
|
||||
|
||||
if (_websocketAvailability == WebsocketAvailabilityStatus.NotAvailable)
|
||||
{
|
||||
_currentIHttpUpgradeFeature = null;
|
||||
}
|
||||
if (_server.IsWebSocketAvailible(pInProcessHandler))
|
||||
{
|
||||
_currentIHttpUpgradeFeature = null;
|
||||
}
|
||||
|
||||
RequestBody = new IISHttpRequestBody(this);
|
||||
ResponseBody = new IISHttpResponseBody(this);
|
||||
|
||||
Input = new Pipe(new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.ThreadPool, minimumSegmentSize: MinAllocBufferSize));
|
||||
var pipe = new Pipe(new PipeOptions(
|
||||
_memoryPool,
|
||||
readerScheduler: PipeScheduler.ThreadPool,
|
||||
pauseWriterThreshold: PauseWriterThreshold,
|
||||
resumeWriterThreshold: ResumeWriterTheshold,
|
||||
minimumSegmentSize: MinAllocBufferSize));
|
||||
var pipe = new Pipe(
|
||||
new PipeOptions(
|
||||
_memoryPool,
|
||||
readerScheduler: PipeScheduler.ThreadPool,
|
||||
pauseWriterThreshold: PauseWriterThreshold,
|
||||
resumeWriterThreshold: ResumeWriterTheshold,
|
||||
minimumSegmentSize: MinAllocBufferSize));
|
||||
Output = new OutputProducer(pipe);
|
||||
}
|
||||
|
||||
|
|
@ -545,12 +503,5 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private enum WebsocketAvailabilityStatus
|
||||
{
|
||||
Uninitialized = 1,
|
||||
Available,
|
||||
NotAvailable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
{
|
||||
internal class IISHttpServer : IServer
|
||||
{
|
||||
private const string WebSocketVersionString = "WEBSOCKET_VERSION";
|
||||
|
||||
private static NativeMethods.PFN_REQUEST_HANDLER _requestHandler = HandleRequest;
|
||||
private static NativeMethods.PFN_SHUTDOWN_HANDLER _shutdownHandler = HandleShutdown;
|
||||
private static NativeMethods.PFN_ASYNC_COMPLETION _onAsyncCompletion = OnAsyncCompletion;
|
||||
|
|
@ -32,8 +34,28 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
private bool Stopping => _stopping == 1;
|
||||
private int _outstandingRequests;
|
||||
private readonly TaskCompletionSource<object> _shutdownSignal = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private bool? _websocketAvailable;
|
||||
|
||||
public IFeatureCollection Features { get; } = new FeatureCollection();
|
||||
|
||||
// TODO: Remove pInProcessHandler argument
|
||||
public bool IsWebSocketAvailible(IntPtr pInProcessHandler)
|
||||
{
|
||||
// Check if the Http upgrade feature is available in IIS.
|
||||
// To check this, we can look at the server variable WEBSOCKET_VERSION
|
||||
// And see if there is a version. Same check that Katana did:
|
||||
// https://github.com/aspnet/AspNetKatana/blob/9f6e09af6bf203744feb5347121fe25f6eec06d8/src/Microsoft.Owin.Host.SystemWeb/OwinAppContext.cs#L125
|
||||
// Actively not locking here as acquiring a lock on every request will hurt perf more than checking the
|
||||
// server variables a few extra times if a bunch of requests hit the server at the same time.
|
||||
if (!_websocketAvailable.HasValue)
|
||||
{
|
||||
_websocketAvailable = NativeMethods.HttpTryGetServerVariable(pInProcessHandler, WebSocketVersionString, out var webSocketsSupported)
|
||||
&& !string.IsNullOrEmpty(webSocketsSupported);
|
||||
}
|
||||
|
||||
return _websocketAvailable.Value;
|
||||
}
|
||||
|
||||
public IISHttpServer(IApplicationLifetime applicationLifetime, IAuthenticationSchemeProvider authentication, IOptions<IISOptions> options)
|
||||
{
|
||||
_applicationLifetime = applicationLifetime;
|
||||
|
|
@ -122,6 +144,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
|||
var server = (IISHttpServer)GCHandle.FromIntPtr(pvRequestContext).Target;
|
||||
Interlocked.Increment(ref server._outstandingRequests);
|
||||
|
||||
// TODO: Add try/catch and logging here
|
||||
var context = server._iisContextFactory.CreateHttpContext(pInProcessHandler);
|
||||
|
||||
var task = Task.Run(() => context.ProcessRequestAsync());
|
||||
|
|
|
|||
Loading…
Reference in New Issue