Add support delegating requests in HttpSysServer (#24857)
* Add new ctors for RequestQueue and UrlGroup * Add DelegateRequest pinvokes * Add Request Transfer Feature * Fix accessibility of feature * Test cleanup * Update ref assembly * hack: Make HttpSysServer packable * Cleanup based on PR feedback * Avoid sending headers after transfer * Fix ref assembly * Fix rebase conflict * Switch to DelegateRequestEx * Add feature detection * Delete ref folder * Add server feature * s/RequestQueueWrapper/DelegationRule * Fix UrlGroup was null issue * Add light-up for ServerDelegationPropertyFeature * Revert changes to sample * Revert changes to sample take 2 * PR feedback * s/Transfered/Transferred * DelegateAfterRequestBodyReadShouldThrow * Make DelegationRule disposable * More license headers * Incomplete XML doc * PR feedback * Fix broken test * PR feedback * Fixup test * s/Transfer/Delegate * s/transfer/delegate * PR feedback
This commit is contained in:
parent
c6814f4f4d
commit
b140c09c02
|
|
@ -127,7 +127,9 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
statusCode = HttpApi.HttpReceiveHttpRequest(
|
statusCode = HttpApi.HttpReceiveHttpRequest(
|
||||||
Server.RequestQueue.Handle,
|
Server.RequestQueue.Handle,
|
||||||
_nativeRequestContext.RequestId,
|
_nativeRequestContext.RequestId,
|
||||||
(uint)HttpApiTypes.HTTP_FLAGS.HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY,
|
// Small perf impact by not using HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY
|
||||||
|
// if the request sends header+body in a single TCP packet
|
||||||
|
(uint)HttpApiTypes.HTTP_FLAGS.NONE,
|
||||||
_nativeRequestContext.NativeRequest,
|
_nativeRequestContext.NativeRequest,
|
||||||
_nativeRequestContext.Size,
|
_nativeRequestContext.Size,
|
||||||
&bytesTransferred,
|
&bytesTransferred,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Rule that maintains a handle to the Request Queue and UrlPrefix to
|
||||||
|
/// delegate to.
|
||||||
|
/// </summary>
|
||||||
|
public class DelegationRule : IDisposable
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the Http.Sys request queue
|
||||||
|
/// </summary>
|
||||||
|
public string QueueName { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// The URL of the Http.Sys Url Prefix
|
||||||
|
/// </summary>
|
||||||
|
public string UrlPrefix { get; }
|
||||||
|
internal RequestQueue Queue { get; }
|
||||||
|
|
||||||
|
internal DelegationRule(string queueName, string urlPrefix, ILogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
QueueName = queueName;
|
||||||
|
UrlPrefix = urlPrefix;
|
||||||
|
Queue = new RequestQueue(queueName, UrlPrefix, _logger, receiver: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Queue.UrlGroup?.Dispose();
|
||||||
|
Queue?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -37,7 +37,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
IHttpBodyControlFeature,
|
IHttpBodyControlFeature,
|
||||||
IHttpSysRequestInfoFeature,
|
IHttpSysRequestInfoFeature,
|
||||||
IHttpResponseTrailersFeature,
|
IHttpResponseTrailersFeature,
|
||||||
IHttpResetFeature
|
IHttpResetFeature,
|
||||||
|
IHttpSysRequestDelegationFeature
|
||||||
{
|
{
|
||||||
private RequestContext _requestContext;
|
private RequestContext _requestContext;
|
||||||
private IFeatureCollection _features;
|
private IFeatureCollection _features;
|
||||||
|
|
@ -591,6 +592,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
set => _responseTrailers = value;
|
set => _responseTrailers = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CanDelegate => Request.CanDelegate;
|
||||||
|
|
||||||
internal async Task OnResponseStart()
|
internal async Task OnResponseStart()
|
||||||
{
|
{
|
||||||
if (_responseStarted)
|
if (_responseStarted)
|
||||||
|
|
@ -711,5 +714,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
await actionPair.Item1(actionPair.Item2);
|
await actionPair.Item1(actionPair.Item2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DelegateRequest(DelegationRule destination)
|
||||||
|
{
|
||||||
|
_requestContext.Delegate(destination);
|
||||||
|
_responseStarted = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
{
|
||||||
|
public interface IHttpSysRequestDelegationFeature
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the server can delegate this request to another HttpSys request queue.
|
||||||
|
/// </summary>
|
||||||
|
bool CanDelegate { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempt to delegate the request to another Http.Sys request queue. The request body
|
||||||
|
/// must not be read nor the response started before this is invoked. Check <see cref="CanDelegate"/>
|
||||||
|
/// before invoking.
|
||||||
|
/// </summary>
|
||||||
|
void DelegateRequest(DelegationRule destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
{
|
||||||
|
public interface IServerDelegationFeature
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a delegation rule on request queue owned by the server.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Creates a <see cref="DelegationRule"/> that can used to delegate individual requests.
|
||||||
|
/// </returns>
|
||||||
|
DelegationRule CreateDelegationRule(string queueName, string urlPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -55,6 +55,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
_serverAddresses = new ServerAddressesFeature();
|
_serverAddresses = new ServerAddressesFeature();
|
||||||
Features.Set<IServerAddressesFeature>(_serverAddresses);
|
Features.Set<IServerAddressesFeature>(_serverAddresses);
|
||||||
|
|
||||||
|
if (HttpApi.IsFeatureSupported(HttpApiTypes.HTTP_FEATURE_ID.HttpFeatureDelegateEx))
|
||||||
|
{
|
||||||
|
var delegationProperty = new ServerDelegationPropertyFeature(Listener.RequestQueue, _logger);
|
||||||
|
Features.Set<IServerDelegationFeature>(delegationProperty);
|
||||||
|
}
|
||||||
|
|
||||||
_maxAccepts = _options.MaxAccepts;
|
_maxAccepts = _options.MaxAccepts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,9 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||||
internal static extern uint HttpCreateUrlGroup(ulong serverSessionId, ulong* urlGroupId, uint reserved);
|
internal static extern uint HttpCreateUrlGroup(ulong serverSessionId, ulong* urlGroupId, uint reserved);
|
||||||
|
|
||||||
|
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||||
|
internal static extern uint HttpFindUrlGroupId(string pFullyQualifiedUrl, SafeHandle requestQueueHandle, ulong* urlGroupId);
|
||||||
|
|
||||||
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
|
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||||
internal static extern uint HttpAddUrlToUrlGroup(ulong urlGroupId, string pFullyQualifiedUrl, ulong context, uint pReserved);
|
internal static extern uint HttpAddUrlToUrlGroup(ulong urlGroupId, string pFullyQualifiedUrl, ulong context, uint pReserved);
|
||||||
|
|
||||||
|
|
@ -70,6 +73,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||||
internal static extern unsafe uint HttpCloseRequestQueue(IntPtr pReqQueueHandle);
|
internal static extern unsafe uint HttpCloseRequestQueue(IntPtr pReqQueueHandle);
|
||||||
|
|
||||||
|
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||||
|
internal static extern bool HttpIsFeatureSupported(HTTP_FEATURE_ID feature);
|
||||||
|
|
||||||
|
[DllImport(HTTPAPI, ExactSpelling = true, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||||
|
internal static extern unsafe uint HttpDelegateRequestEx(SafeHandle pReqQueueHandle, SafeHandle pDelegateQueueHandle, ulong requestId,
|
||||||
|
ulong delegateUrlGroupId, ulong propertyInfoSetSize, HTTP_DELEGATE_REQUEST_PROPERTY_INFO* pRequestPropertyBuffer);
|
||||||
|
|
||||||
internal delegate uint HttpSetRequestPropertyInvoker(SafeHandle requestQueueHandle, ulong requestId, HTTP_REQUEST_PROPERTY propertyId, void* input, uint inputSize, IntPtr overlapped);
|
internal delegate uint HttpSetRequestPropertyInvoker(SafeHandle requestQueueHandle, ulong requestId, HTTP_REQUEST_PROPERTY propertyId, void* input, uint inputSize, IntPtr overlapped);
|
||||||
|
|
||||||
private static HTTPAPI_VERSION version;
|
private static HTTPAPI_VERSION version;
|
||||||
|
|
@ -145,5 +155,16 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
return supported;
|
return supported;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool IsFeatureSupported(HTTP_FEATURE_ID feature)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return HttpIsFeatureSupported(feature);
|
||||||
|
}
|
||||||
|
catch (EntryPointNotFoundException) { }
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,22 +16,44 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
Marshal.SizeOf<HttpApiTypes.HTTP_BINDING_INFO>();
|
Marshal.SizeOf<HttpApiTypes.HTTP_BINDING_INFO>();
|
||||||
|
|
||||||
private readonly RequestQueueMode _mode;
|
private readonly RequestQueueMode _mode;
|
||||||
private readonly UrlGroup _urlGroup;
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
|
internal RequestQueue(string requestQueueName, string urlPrefix, ILogger logger, bool receiver)
|
||||||
|
: this(urlGroup: null, requestQueueName, RequestQueueMode.Attach, logger, receiver)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
UrlGroup = new UrlGroup(this, UrlPrefix.Create(urlPrefix));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal RequestQueue(UrlGroup urlGroup, string requestQueueName, RequestQueueMode mode, ILogger logger)
|
internal RequestQueue(UrlGroup urlGroup, string requestQueueName, RequestQueueMode mode, ILogger logger)
|
||||||
|
: this(urlGroup, requestQueueName, mode, logger, false)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
private RequestQueue(UrlGroup urlGroup, string requestQueueName, RequestQueueMode mode, ILogger logger, bool receiver)
|
||||||
{
|
{
|
||||||
_mode = mode;
|
_mode = mode;
|
||||||
_urlGroup = urlGroup;
|
UrlGroup = urlGroup;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
var flags = HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.None;
|
var flags = HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.None;
|
||||||
Created = true;
|
Created = true;
|
||||||
|
|
||||||
if (_mode == RequestQueueMode.Attach)
|
if (_mode == RequestQueueMode.Attach)
|
||||||
{
|
{
|
||||||
flags = HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.OpenExisting;
|
flags = HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.OpenExisting;
|
||||||
Created = false;
|
Created = false;
|
||||||
|
if (receiver)
|
||||||
|
{
|
||||||
|
flags |= HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.Delegation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusCode = HttpApi.HttpCreateRequestQueue(
|
var statusCode = HttpApi.HttpCreateRequestQueue(
|
||||||
|
|
@ -54,7 +76,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
out requestQueueHandle);
|
out requestQueueHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags == HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.OpenExisting && statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_FILE_NOT_FOUND)
|
if (flags.HasFlag(HttpApiTypes.HTTP_CREATE_REQUEST_QUEUE_FLAG.OpenExisting) && statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_FILE_NOT_FOUND)
|
||||||
{
|
{
|
||||||
throw new HttpSysException((int)statusCode, $"Failed to attach to the given request queue '{requestQueueName}', the queue could not be found.");
|
throw new HttpSysException((int)statusCode, $"Failed to attach to the given request queue '{requestQueueName}', the queue could not be found.");
|
||||||
}
|
}
|
||||||
|
|
@ -95,6 +117,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
internal SafeHandle Handle { get; }
|
internal SafeHandle Handle { get; }
|
||||||
internal ThreadPoolBoundHandle BoundHandle { get; }
|
internal ThreadPoolBoundHandle BoundHandle { get; }
|
||||||
|
|
||||||
|
internal UrlGroup UrlGroup { get; }
|
||||||
|
|
||||||
internal unsafe void AttachToUrlGroup()
|
internal unsafe void AttachToUrlGroup()
|
||||||
{
|
{
|
||||||
Debug.Assert(Created);
|
Debug.Assert(Created);
|
||||||
|
|
@ -108,7 +132,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
var infoptr = new IntPtr(&info);
|
var infoptr = new IntPtr(&info);
|
||||||
|
|
||||||
_urlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
|
UrlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
|
||||||
infoptr, (uint)BindingInfoSize);
|
infoptr, (uint)BindingInfoSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,7 +152,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
var infoptr = new IntPtr(&info);
|
var infoptr = new IntPtr(&info);
|
||||||
|
|
||||||
_urlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
|
UrlGroup.SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerBindingProperty,
|
||||||
infoptr, (uint)BindingInfoSize, throwOnError: false);
|
infoptr, (uint)BindingInfoSize, throwOnError: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
{
|
{
|
||||||
private static readonly int QosInfoSize =
|
private static readonly int QosInfoSize =
|
||||||
Marshal.SizeOf<HttpApiTypes.HTTP_QOS_SETTING_INFO>();
|
Marshal.SizeOf<HttpApiTypes.HTTP_QOS_SETTING_INFO>();
|
||||||
|
private static readonly int RequestPropertyInfoSize =
|
||||||
|
Marshal.SizeOf<HttpApiTypes.HTTP_BINDING_INFO>();
|
||||||
|
|
||||||
private ServerSession _serverSession;
|
private ServerSession _serverSession;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
|
@ -36,6 +38,21 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
Id = urlGroupId;
|
Id = urlGroupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal unsafe UrlGroup(RequestQueue requestQueue, UrlPrefix url)
|
||||||
|
{
|
||||||
|
ulong urlGroupId = 0;
|
||||||
|
var statusCode = HttpApi.HttpFindUrlGroupId(
|
||||||
|
url.FullPrefix, requestQueue.Handle, &urlGroupId);
|
||||||
|
|
||||||
|
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
throw new HttpSysException((int)statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(urlGroupId != 0, "Invalid id returned by HttpCreateUrlGroup");
|
||||||
|
Id = urlGroupId;
|
||||||
|
}
|
||||||
|
|
||||||
internal ulong Id { get; private set; }
|
internal ulong Id { get; private set; }
|
||||||
|
|
||||||
internal unsafe void SetMaxConnections(long maxConnections)
|
internal unsafe void SetMaxConnections(long maxConnections)
|
||||||
|
|
@ -51,6 +68,15 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerQosProperty, new IntPtr(&qosSettings), (uint)QosInfoSize);
|
SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerQosProperty, new IntPtr(&qosSettings), (uint)QosInfoSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal unsafe void SetDelegationProperty(RequestQueue destination)
|
||||||
|
{
|
||||||
|
var propertyInfo = new HttpApiTypes.HTTP_BINDING_INFO();
|
||||||
|
propertyInfo.Flags = HttpApiTypes.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
|
||||||
|
propertyInfo.RequestQueueHandle = destination.Handle.DangerousGetHandle();
|
||||||
|
|
||||||
|
SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY.HttpServerDelegationProperty, new IntPtr(&propertyInfo), (uint)RequestPropertyInfoSize);
|
||||||
|
}
|
||||||
|
|
||||||
internal void SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true)
|
internal void SetProperty(HttpApiTypes.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true)
|
||||||
{
|
{
|
||||||
Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer");
|
Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer");
|
||||||
|
|
|
||||||
|
|
@ -61,43 +61,40 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
var rawUrlInBytes = _nativeRequestContext.GetRawUrlInBytes();
|
var rawUrlInBytes = _nativeRequestContext.GetRawUrlInBytes();
|
||||||
var originalPath = RequestUriBuilder.DecodeAndUnescapePath(rawUrlInBytes);
|
var originalPath = RequestUriBuilder.DecodeAndUnescapePath(rawUrlInBytes);
|
||||||
|
|
||||||
|
PathBase = string.Empty;
|
||||||
|
Path = originalPath;
|
||||||
|
|
||||||
// 'OPTIONS * HTTP/1.1'
|
// 'OPTIONS * HTTP/1.1'
|
||||||
if (KnownMethod == HttpApiTypes.HTTP_VERB.HttpVerbOPTIONS && string.Equals(RawUrl, "*", StringComparison.Ordinal))
|
if (KnownMethod == HttpApiTypes.HTTP_VERB.HttpVerbOPTIONS && string.Equals(RawUrl, "*", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
PathBase = string.Empty;
|
PathBase = string.Empty;
|
||||||
Path = string.Empty;
|
Path = string.Empty;
|
||||||
}
|
}
|
||||||
else if (requestContext.Server.RequestQueue.Created)
|
|
||||||
{
|
|
||||||
var prefix = requestContext.Server.Options.UrlPrefixes.GetPrefix((int)nativeRequestContext.UrlContext);
|
|
||||||
|
|
||||||
if (originalPath.Length == prefix.PathWithoutTrailingSlash.Length)
|
|
||||||
{
|
|
||||||
// They matched exactly except for the trailing slash.
|
|
||||||
PathBase = originalPath;
|
|
||||||
Path = string.Empty;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// url: /base/path, prefix: /base/, base: /base, path: /path
|
|
||||||
// url: /, prefix: /, base: , path: /
|
|
||||||
PathBase = originalPath.Substring(0, prefix.PathWithoutTrailingSlash.Length); // Preserve the user input casing
|
|
||||||
Path = originalPath.Substring(prefix.PathWithoutTrailingSlash.Length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// When attaching to an existing queue, the UrlContext hint may not match our configuration. Search manualy.
|
var prefix = requestContext.Server.Options.UrlPrefixes.GetPrefix((int)nativeRequestContext.UrlContext);
|
||||||
if (requestContext.Server.Options.UrlPrefixes.TryMatchLongestPrefix(IsHttps, cookedUrl.GetHost(), originalPath, out var pathBase, out var path))
|
// Prefix may be null if the requested has been transfered to our queue
|
||||||
|
if (!(prefix is null))
|
||||||
|
{
|
||||||
|
if (originalPath.Length == prefix.PathWithoutTrailingSlash.Length)
|
||||||
|
{
|
||||||
|
// They matched exactly except for the trailing slash.
|
||||||
|
PathBase = originalPath;
|
||||||
|
Path = string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// url: /base/path, prefix: /base/, base: /base, path: /path
|
||||||
|
// url: /, prefix: /, base: , path: /
|
||||||
|
PathBase = originalPath.Substring(0, prefix.PathWithoutTrailingSlash.Length); // Preserve the user input casing
|
||||||
|
Path = originalPath.Substring(prefix.PathWithoutTrailingSlash.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (requestContext.Server.Options.UrlPrefixes.TryMatchLongestPrefix(IsHttps, cookedUrl.GetHost(), originalPath, out var pathBase, out var path))
|
||||||
{
|
{
|
||||||
PathBase = pathBase;
|
PathBase = pathBase;
|
||||||
Path = path;
|
Path = path;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
PathBase = string.Empty;
|
|
||||||
Path = originalPath;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ProtocolVersion = _nativeRequestContext.GetVersion();
|
ProtocolVersion = _nativeRequestContext.GetVersion();
|
||||||
|
|
@ -350,6 +347,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CanDelegate => !(HasRequestBodyStarted || RequestContext.Response.HasStarted);
|
||||||
|
|
||||||
// Populates the client certificate. The result may be null if there is no client cert.
|
// Populates the client certificate. The result may be null if there is no client cert.
|
||||||
// TODO: Does it make sense for this to be invoked multiple times (e.g. renegotiate)? Client and server code appear to
|
// TODO: Does it make sense for this to be invoked multiple times (e.g. renegotiate)? Client and server code appear to
|
||||||
// enable this, but it's unclear what Http.Sys would do.
|
// enable this, but it's unclear what Http.Sys would do.
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Authentication.ExtendedProtection;
|
using System.Security.Authentication.ExtendedProtection;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -17,7 +19,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
internal sealed class RequestContext : IDisposable, IThreadPoolWorkItem
|
internal sealed class RequestContext : IDisposable, IThreadPoolWorkItem
|
||||||
{
|
{
|
||||||
private static readonly Action<object> AbortDelegate = Abort;
|
private static readonly Action<object> AbortDelegate = Abort;
|
||||||
|
|
||||||
private NativeRequestContext _memoryBlob;
|
private NativeRequestContext _memoryBlob;
|
||||||
private CancellationTokenSource _requestAbortSource;
|
private CancellationTokenSource _requestAbortSource;
|
||||||
private CancellationToken? _disconnectToken;
|
private CancellationToken? _disconnectToken;
|
||||||
|
|
@ -322,5 +323,45 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
Response.ContentLength = 0;
|
Response.ContentLength = 0;
|
||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal unsafe void Delegate(DelegationRule destination)
|
||||||
|
{
|
||||||
|
if (Request.HasRequestBodyStarted)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("This request cannot be delegated, the request body has already started.");
|
||||||
|
}
|
||||||
|
if (Response.HasStarted)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("This request cannot be delegated, the response has already started.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var source = Server.RequestQueue;
|
||||||
|
|
||||||
|
uint statusCode;
|
||||||
|
|
||||||
|
fixed (char* uriPointer = destination.UrlPrefix)
|
||||||
|
{
|
||||||
|
var property = new HttpApiTypes.HTTP_DELEGATE_REQUEST_PROPERTY_INFO()
|
||||||
|
{
|
||||||
|
ProperyId = HttpApiTypes.HTTP_DELEGATE_REQUEST_PROPERTY_ID.DelegateRequestDelegateUrlProperty,
|
||||||
|
PropertyInfo = (IntPtr)uriPointer,
|
||||||
|
PropertyInfoLength = (uint)System.Text.Encoding.Unicode.GetByteCount(destination.UrlPrefix)
|
||||||
|
};
|
||||||
|
|
||||||
|
statusCode = HttpApi.HttpDelegateRequestEx(source.Handle,
|
||||||
|
destination.Queue.Handle,
|
||||||
|
Request.RequestId,
|
||||||
|
destination.Queue.UrlGroup.Id,
|
||||||
|
propertyInfoSetSize: 1,
|
||||||
|
&property);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
throw new HttpSysException((int)statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Response.MarkDelegated();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -730,6 +730,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void MarkDelegated()
|
||||||
|
{
|
||||||
|
Abort();
|
||||||
|
_nativeStream?.MarkDelegated();
|
||||||
|
}
|
||||||
|
|
||||||
internal void CancelLastWrite()
|
internal void CancelLastWrite()
|
||||||
{
|
{
|
||||||
_nativeStream?.CancelLastWrite();
|
_nativeStream?.CancelLastWrite();
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
FlushInternal(endOfRequest: false);
|
FlushInternal(endOfRequest: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void MarkDelegated()
|
||||||
|
{
|
||||||
|
_skipWrites = true;
|
||||||
|
}
|
||||||
|
|
||||||
// We never expect endOfRequest and data at the same time
|
// We never expect endOfRequest and data at the same time
|
||||||
private unsafe void FlushInternal(bool endOfRequest, ArraySegment<byte> data = new ArraySegment<byte>())
|
private unsafe void FlushInternal(bool endOfRequest, ArraySegment<byte> data = new ArraySegment<byte>())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
{
|
||||||
|
internal class ServerDelegationPropertyFeature : IServerDelegationFeature
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly RequestQueue _queue;
|
||||||
|
|
||||||
|
public ServerDelegationPropertyFeature(RequestQueue queue, ILogger logger)
|
||||||
|
{
|
||||||
|
_queue = queue;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DelegationRule CreateDelegationRule(string queueName, string uri)
|
||||||
|
{
|
||||||
|
var rule = new DelegationRule(queueName, uri, _logger);
|
||||||
|
_queue.UrlGroup.SetDelegationProperty(rule.Queue);
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||||
|
using Microsoft.AspNetCore.HttpSys.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.HttpSys
|
namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
{
|
{
|
||||||
|
|
@ -44,6 +45,11 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
// Win8+
|
// Win8+
|
||||||
_featureFuncLookup[typeof(ITlsHandshakeFeature)] = ctx => ctx.GetTlsHandshakeFeature();
|
_featureFuncLookup[typeof(ITlsHandshakeFeature)] = ctx => ctx.GetTlsHandshakeFeature();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (HttpApi.IsFeatureSupported(HttpApiTypes.HTTP_FEATURE_ID.HttpFeatureDelegateEx))
|
||||||
|
{
|
||||||
|
_featureFuncLookup[typeof(IHttpSysRequestDelegationFeature)] = _identityFunc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public StandardFeatureCollection(FeatureContext featureContext)
|
public StandardFeatureCollection(FeatureContext featureContext)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using static Microsoft.AspNetCore.HttpSys.Internal.HttpApiTypes;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||||
|
public class DelegateSupportedConditionAttribute : Attribute, ITestCondition
|
||||||
|
{
|
||||||
|
private readonly bool _isSupported;
|
||||||
|
public DelegateSupportedConditionAttribute(bool isSupported) => _isSupported = isSupported;
|
||||||
|
|
||||||
|
private readonly Lazy<bool> _isDelegateSupported = new Lazy<bool>(CanDelegate);
|
||||||
|
public bool IsMet => (_isDelegateSupported.Value == _isSupported);
|
||||||
|
|
||||||
|
public string SkipReason => $"Http.Sys does {(_isSupported ? "not" : "")} support delegating requests";
|
||||||
|
|
||||||
|
private static bool CanDelegate()
|
||||||
|
{
|
||||||
|
return HttpApi.IsFeatureSupported(HTTP_FEATURE_ID.HttpFeatureDelegateEx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
// 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.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
|
||||||
|
{
|
||||||
|
public class DelegateTests
|
||||||
|
{
|
||||||
|
private static readonly string _expectedResponseString = "Hello from delegatee";
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
[DelegateSupportedCondition(true)]
|
||||||
|
public async Task DelegateRequestTest()
|
||||||
|
{
|
||||||
|
var queueName = Guid.NewGuid().ToString();
|
||||||
|
using var receiver = Utilities.CreateHttpServer(out var receiverAddress, async httpContext =>
|
||||||
|
{
|
||||||
|
await httpContext.Response.WriteAsync(_expectedResponseString);
|
||||||
|
},
|
||||||
|
options =>
|
||||||
|
{
|
||||||
|
options.RequestQueueName = queueName;
|
||||||
|
});
|
||||||
|
|
||||||
|
DelegationRule destination = default;
|
||||||
|
|
||||||
|
using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, httpContext =>
|
||||||
|
{
|
||||||
|
var delegateFeature = httpContext.Features.Get<IHttpSysRequestDelegationFeature>();
|
||||||
|
delegateFeature.DelegateRequest(destination);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
var delegationProperty = delegator.Features.Get<IServerDelegationFeature>();
|
||||||
|
destination = delegationProperty.CreateDelegationRule(queueName, receiverAddress);
|
||||||
|
|
||||||
|
var responseString = await SendRequestAsync(delegatorAddress);
|
||||||
|
Assert.Equal(_expectedResponseString, responseString);
|
||||||
|
destination?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
[DelegateSupportedCondition(true)]
|
||||||
|
public async Task DelegateAfterWriteToResponseBodyShouldThrowTest()
|
||||||
|
{
|
||||||
|
var queueName = Guid.NewGuid().ToString();
|
||||||
|
using var receiver = Utilities.CreateHttpServer(out var receiverAddress, httpContext =>
|
||||||
|
{
|
||||||
|
httpContext.Response.StatusCode = StatusCodes.Status418ImATeapot;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
options =>
|
||||||
|
{
|
||||||
|
options.RequestQueueName = queueName;
|
||||||
|
});
|
||||||
|
|
||||||
|
DelegationRule destination = default;
|
||||||
|
|
||||||
|
using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, async httpContext =>
|
||||||
|
{
|
||||||
|
await httpContext.Response.WriteAsync(_expectedResponseString);
|
||||||
|
var delegateFeature = httpContext.Features.Get<IHttpSysRequestDelegationFeature>();
|
||||||
|
Assert.False(delegateFeature.CanDelegate);
|
||||||
|
Assert.Throws<InvalidOperationException>(() => delegateFeature.DelegateRequest(destination));
|
||||||
|
});
|
||||||
|
|
||||||
|
var delegationProperty = delegator.Features.Get<IServerDelegationFeature>();
|
||||||
|
destination = delegationProperty.CreateDelegationRule(queueName, receiverAddress);
|
||||||
|
|
||||||
|
var responseString = await SendRequestAsync(delegatorAddress);
|
||||||
|
Assert.Equal(_expectedResponseString, responseString);
|
||||||
|
destination?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
[DelegateSupportedCondition(true)]
|
||||||
|
public async Task WriteToBodyAfterDelegateShouldNoOp()
|
||||||
|
{
|
||||||
|
var queueName = Guid.NewGuid().ToString();
|
||||||
|
using var receiver = Utilities.CreateHttpServer(out var receiverAddress, async httpContext =>
|
||||||
|
{
|
||||||
|
await httpContext.Response.WriteAsync(_expectedResponseString);
|
||||||
|
},
|
||||||
|
options =>
|
||||||
|
{
|
||||||
|
options.RequestQueueName = queueName;
|
||||||
|
});
|
||||||
|
|
||||||
|
DelegationRule destination = default;
|
||||||
|
|
||||||
|
using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, httpContext =>
|
||||||
|
{
|
||||||
|
var delegateFeature = httpContext.Features.Get<IHttpSysRequestDelegationFeature>();
|
||||||
|
delegateFeature.DelegateRequest(destination);
|
||||||
|
Assert.False(delegateFeature.CanDelegate);
|
||||||
|
httpContext.Response.WriteAsync(_expectedResponseString);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
var delegationProperty = delegator.Features.Get<IServerDelegationFeature>();
|
||||||
|
destination = delegationProperty.CreateDelegationRule(queueName, receiverAddress);
|
||||||
|
|
||||||
|
var responseString = await SendRequestAsync(delegatorAddress);
|
||||||
|
Assert.Equal(_expectedResponseString, responseString);
|
||||||
|
destination?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
[DelegateSupportedCondition(true)]
|
||||||
|
public async Task DelegateAfterRequestBodyReadShouldThrow()
|
||||||
|
{
|
||||||
|
var queueName = Guid.NewGuid().ToString();
|
||||||
|
using var receiver = Utilities.CreateHttpServer(out var receiverAddress, httpContext =>
|
||||||
|
{
|
||||||
|
httpContext.Response.StatusCode = StatusCodes.Status418ImATeapot;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
},
|
||||||
|
options =>
|
||||||
|
{
|
||||||
|
options.RequestQueueName = queueName;
|
||||||
|
});
|
||||||
|
|
||||||
|
DelegationRule destination = default;
|
||||||
|
|
||||||
|
using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, async httpContext =>
|
||||||
|
{
|
||||||
|
var memoryStream = new MemoryStream();
|
||||||
|
await httpContext.Request.Body.CopyToAsync(memoryStream);
|
||||||
|
var delegateFeature = httpContext.Features.Get<IHttpSysRequestDelegationFeature>();
|
||||||
|
Assert.Throws<InvalidOperationException>(() => delegateFeature.DelegateRequest(destination));
|
||||||
|
});
|
||||||
|
|
||||||
|
var delegationProperty = delegator.Features.Get<IServerDelegationFeature>();
|
||||||
|
destination = delegationProperty.CreateDelegationRule(queueName, receiverAddress);
|
||||||
|
|
||||||
|
_ = await SendRequestWithBodyAsync(delegatorAddress);
|
||||||
|
destination?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
[DelegateSupportedCondition(false)]
|
||||||
|
public async Task DelegationFeaturesAreNull()
|
||||||
|
{
|
||||||
|
using var delegator = Utilities.CreateHttpServer(out var delegatorAddress, httpContext =>
|
||||||
|
{
|
||||||
|
var delegateFeature = httpContext.Features.Get<IHttpSysRequestDelegationFeature>();
|
||||||
|
Assert.Null(delegateFeature);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
var delegationProperty = delegator.Features.Get<IServerDelegationFeature>();
|
||||||
|
Assert.Null(delegationProperty);
|
||||||
|
|
||||||
|
_ = await SendRequestAsync(delegatorAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> SendRequestAsync(string uri)
|
||||||
|
{
|
||||||
|
using var client = new HttpClient();
|
||||||
|
return await client.GetStringAsync(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> SendRequestWithBodyAsync(string uri)
|
||||||
|
{
|
||||||
|
using var client = new HttpClient();
|
||||||
|
var content = new StringContent("Sample request body");
|
||||||
|
var response = await client.PostAsync(uri, content);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,6 +32,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
|
||||||
HttpServerListenEndpointProperty,
|
HttpServerListenEndpointProperty,
|
||||||
HttpServerChannelBindProperty,
|
HttpServerChannelBindProperty,
|
||||||
HttpServerProtectionLevelProperty,
|
HttpServerProtectionLevelProperty,
|
||||||
|
HttpServerDelegationProperty = 16
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently only one request info type is supported but the enum is for future extensibility.
|
// Currently only one request info type is supported but the enum is for future extensibility.
|
||||||
|
|
@ -71,6 +72,28 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
|
||||||
MinSendRate,
|
MinSendRate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal enum HTTP_DELEGATE_REQUEST_PROPERTY_ID : uint
|
||||||
|
{
|
||||||
|
DelegateRequestReservedProperty,
|
||||||
|
DelegateRequestDelegateUrlProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum HTTP_FEATURE_ID
|
||||||
|
{
|
||||||
|
HttpFeatureUnknown = 0,
|
||||||
|
HttpFeatureResponseTrailers = 1,
|
||||||
|
HttpFeatureApiTimings = 2,
|
||||||
|
HttpFeatureDelegateEx = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||||
|
internal struct HTTP_DELEGATE_REQUEST_PROPERTY_INFO
|
||||||
|
{
|
||||||
|
internal HTTP_DELEGATE_REQUEST_PROPERTY_ID ProperyId;
|
||||||
|
internal uint PropertyInfoLength;
|
||||||
|
internal IntPtr PropertyInfo;
|
||||||
|
}
|
||||||
|
|
||||||
internal struct HTTP_REQUEST_PROPERTY_STREAM_ERROR
|
internal struct HTTP_REQUEST_PROPERTY_STREAM_ERROR
|
||||||
{
|
{
|
||||||
internal uint ErrorCode;
|
internal uint ErrorCode;
|
||||||
|
|
@ -651,6 +674,7 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
|
||||||
OpenExisting = 1,
|
OpenExisting = 1,
|
||||||
// The handle to the request queue created using this flag cannot be used to perform I/O operations. This flag can be set only when the request queue handle is created.
|
// The handle to the request queue created using this flag cannot be used to perform I/O operations. This flag can be set only when the request queue handle is created.
|
||||||
Controller = 2,
|
Controller = 2,
|
||||||
|
Delegation = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class HTTP_RESPONSE_HEADER_ID
|
internal static class HTTP_RESPONSE_HEADER_ID
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue