Remove managed InProcess code (#809)
This commit is contained in:
parent
73c5e23ffb
commit
361caff1ad
|
|
@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.IIS;
|
|
||||||
using Microsoft.AspNetCore.Server.IISIntegration;
|
using Microsoft.AspNetCore.Server.IISIntegration;
|
||||||
|
|
||||||
namespace NativeIISSample
|
namespace NativeIISSample
|
||||||
|
|
@ -66,12 +65,6 @@ namespace NativeIISSample
|
||||||
// accessing IIS server variables
|
// accessing IIS server variables
|
||||||
await context.Response.WriteAsync("Server Variables:" + Environment.NewLine);
|
await context.Response.WriteAsync("Server Variables:" + Environment.NewLine);
|
||||||
|
|
||||||
foreach (var varName in IISServerVarNames)
|
|
||||||
{
|
|
||||||
await context.Response.WriteAsync(varName + ": " + context.GetIISServerVariable(varName) + Environment.NewLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
await context.Response.WriteAsync(Environment.NewLine);
|
|
||||||
if (context.Features.Get<IHttpUpgradeFeature>() != null)
|
if (context.Features.Get<IHttpUpgradeFeature>() != null)
|
||||||
{
|
{
|
||||||
await context.Response.WriteAsync("Websocket feature is enabled.");
|
await context.Response.WriteAsync("Websocket feature is enabled.");
|
||||||
|
|
@ -82,20 +75,6 @@ namespace NativeIISSample
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly string[] IISServerVarNames =
|
|
||||||
{
|
|
||||||
"AUTH_TYPE",
|
|
||||||
"AUTH_USER",
|
|
||||||
"CONTENT_TYPE",
|
|
||||||
"HTTP_HOST",
|
|
||||||
"HTTPS",
|
|
||||||
"REMOTE_PORT",
|
|
||||||
"REMOTE_USER",
|
|
||||||
"REQUEST_METHOD",
|
|
||||||
"WEBSOCKET_VERSION"
|
|
||||||
};
|
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
var host = new WebHostBuilder()
|
var host = new WebHostBuilder()
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
// 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.Http;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IIS
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Extensions to <see cref="HttpContext"/> that enable access to IIS features.
|
|
||||||
/// </summary>
|
|
||||||
public static class IISHttpContextExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the value of a server variable for the current request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The http context for the request.</param>
|
|
||||||
/// <param name="variableName">The name of the variable.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// <c>null</c> if the feature does not support the <see cref="IServerVariablesFeature"/> feature.
|
|
||||||
/// May return null or empty if the variable does not exist or is not set.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// For a list of common server variables available in IIS, see http://go.microsoft.com/fwlink/?LinkId=52471.
|
|
||||||
/// </remarks>
|
|
||||||
public static string GetIISServerVariable(this HttpContext context, string variableName)
|
|
||||||
{
|
|
||||||
var feature = context.Features.Get<IServerVariablesFeature>();
|
|
||||||
|
|
||||||
if (feature == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return feature[variableName];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
// 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.Http.Features
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This feature provides access to request server variables set.
|
|
||||||
/// <para>
|
|
||||||
/// This feature is only available when hosting ASP.NET Core in-process with IIS or IIS Express.
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// For a list of common server variables available in IIS, see http://go.microsoft.com/fwlink/?LinkId=52471.
|
|
||||||
/// </remarks>
|
|
||||||
public interface IServerVariablesFeature
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the value of a server variable for the current request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="variableName">The variable name</param>
|
|
||||||
/// <returns>May return null or empty if the variable does not exist or is not set.</returns>
|
|
||||||
string this[string variableName] { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,264 +9,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
|
||||||
{
|
{
|
||||||
internal static class NativeMethods
|
internal static class NativeMethods
|
||||||
{
|
{
|
||||||
private const int HR_NOT_FOUND = unchecked((int)0x80070490);
|
|
||||||
private const int HR_OK = 0;
|
|
||||||
|
|
||||||
private const string KERNEL32 = "kernel32.dll";
|
private const string KERNEL32 = "kernel32.dll";
|
||||||
|
|
||||||
private const string AspNetCoreModuleDll = "aspnetcorerh.dll";
|
|
||||||
|
|
||||||
[DllImport(KERNEL32, ExactSpelling = true, SetLastError = true)]
|
[DllImport(KERNEL32, ExactSpelling = true, SetLastError = true)]
|
||||||
|
|
||||||
public static extern bool CloseHandle(IntPtr handle);
|
public static extern bool CloseHandle(IntPtr handle);
|
||||||
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll")]
|
|
||||||
private static extern IntPtr GetModuleHandle(string lpModuleName);
|
|
||||||
|
|
||||||
public static bool IsAspNetCoreModuleLoaded()
|
|
||||||
{
|
|
||||||
return GetModuleHandle(AspNetCoreModuleDll) != IntPtr.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum REQUEST_NOTIFICATION_STATUS
|
|
||||||
{
|
|
||||||
RQ_NOTIFICATION_CONTINUE,
|
|
||||||
RQ_NOTIFICATION_PENDING,
|
|
||||||
RQ_NOTIFICATION_FINISH_REQUEST
|
|
||||||
}
|
|
||||||
|
|
||||||
public delegate REQUEST_NOTIFICATION_STATUS PFN_REQUEST_HANDLER(IntPtr pInProcessHandler, IntPtr pvRequestContext);
|
|
||||||
public delegate bool PFN_SHUTDOWN_HANDLER(IntPtr pvRequestContext);
|
|
||||||
public delegate REQUEST_NOTIFICATION_STATUS PFN_ASYNC_COMPLETION(IntPtr pvManagedHttpContext, int hr, int bytes);
|
|
||||||
public delegate REQUEST_NOTIFICATION_STATUS PFN_WEBSOCKET_ASYNC_COMPLETION(IntPtr pInProcessHandler, IntPtr completionInfo, IntPtr pvCompletionContext);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern int http_post_completion(IntPtr pInProcessHandler, int cbBytes);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern int http_set_completion_status(IntPtr pInProcessHandler, REQUEST_NOTIFICATION_STATUS rquestNotificationStatus);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern void http_indicate_completion(IntPtr pInProcessHandler, REQUEST_NOTIFICATION_STATUS notificationStatus);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern void register_callbacks(PFN_REQUEST_HANDLER request_callback, PFN_SHUTDOWN_HANDLER shutdown_callback, PFN_ASYNC_COMPLETION managed_context_handler, IntPtr pvRequestContext, IntPtr pvShutdownContext);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern unsafe int http_write_response_bytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern int http_flush_response_bytes(IntPtr pInProcessHandler, out bool fCompletionExpected);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern unsafe HttpApiTypes.HTTP_REQUEST_V2* http_get_raw_request(IntPtr pInProcessHandler);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern void http_stop_calls_into_managed();
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern void http_stop_incoming_requests();
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern unsafe HttpApiTypes.HTTP_RESPONSE_V2* http_get_raw_response(IntPtr pInProcessHandler);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll, CharSet = CharSet.Ansi)]
|
|
||||||
private static extern int http_set_response_status_code(IntPtr pInProcessHandler, ushort statusCode, string pszReason);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern unsafe int http_read_request_bytes(IntPtr pInProcessHandler, byte* pvBuffer, int cbBuffer, out int dwBytesReceived, out bool fCompletionExpected);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern void http_get_completion_info(IntPtr pCompletionInfo, out int cbBytes, out int hr);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern int http_set_managed_context(IntPtr pInProcessHandler, IntPtr pvManagedContext);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern int http_get_application_properties(ref IISConfigurationData iiConfigData);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern int http_get_server_variable(
|
|
||||||
IntPtr pInProcessHandler,
|
|
||||||
[MarshalAs(UnmanagedType.LPStr)] string variableName,
|
|
||||||
[MarshalAs(UnmanagedType.BStr)] out string value);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern unsafe int http_websockets_read_bytes(
|
|
||||||
IntPtr pInProcessHandler,
|
|
||||||
byte* pvBuffer,
|
|
||||||
int cbBuffer,
|
|
||||||
PFN_WEBSOCKET_ASYNC_COMPLETION pfnCompletionCallback,
|
|
||||||
IntPtr pvCompletionContext,
|
|
||||||
out int dwBytesReceived,
|
|
||||||
out bool fCompletionExpected);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern unsafe int http_websockets_write_bytes(
|
|
||||||
IntPtr pInProcessHandler,
|
|
||||||
HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks,
|
|
||||||
int nChunks,
|
|
||||||
PFN_WEBSOCKET_ASYNC_COMPLETION pfnCompletionCallback,
|
|
||||||
IntPtr pvCompletionContext,
|
|
||||||
out bool fCompletionExpected);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern int http_enable_websockets(IntPtr pInProcessHandler);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern int http_cancel_io(IntPtr pInProcessHandler);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern unsafe int http_response_set_unknown_header(IntPtr pInProcessHandler, byte* pszHeaderName, byte* pszHeaderValue, ushort usHeaderValueLength, bool fReplace);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern unsafe int http_response_set_known_header(IntPtr pInProcessHandler, int headerId, byte* pHeaderValue, ushort length, bool fReplace);
|
|
||||||
|
|
||||||
[DllImport(AspNetCoreModuleDll)]
|
|
||||||
private static extern int http_get_authentication_information(IntPtr pInProcessHandler, [MarshalAs(UnmanagedType.BStr)] out string authType, out IntPtr token);
|
|
||||||
|
|
||||||
public static void HttpPostCompletion(IntPtr pInProcessHandler, int cbBytes)
|
|
||||||
{
|
|
||||||
Validate(http_post_completion(pInProcessHandler, cbBytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HttpSetCompletionStatus(IntPtr pInProcessHandler, REQUEST_NOTIFICATION_STATUS rquestNotificationStatus)
|
|
||||||
{
|
|
||||||
Validate(http_set_completion_status(pInProcessHandler, rquestNotificationStatus));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HttpIndicateCompletion(IntPtr pInProcessHandler, REQUEST_NOTIFICATION_STATUS notificationStatus)
|
|
||||||
{
|
|
||||||
http_indicate_completion(pInProcessHandler, notificationStatus);
|
|
||||||
}
|
|
||||||
public static void HttpRegisterCallbacks(PFN_REQUEST_HANDLER request_callback, PFN_SHUTDOWN_HANDLER shutdown_callback, PFN_ASYNC_COMPLETION managed_context_handler, IntPtr pvRequestContext, IntPtr pvShutdownContext)
|
|
||||||
{
|
|
||||||
register_callbacks(request_callback, shutdown_callback, managed_context_handler, pvRequestContext, pvShutdownContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe int HttpWriteResponseBytes(IntPtr pInProcessHandler, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, int nChunks, out bool fCompletionExpected)
|
|
||||||
{
|
|
||||||
return http_write_response_bytes(pInProcessHandler, pDataChunks, nChunks, out fCompletionExpected);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int HttpFlushResponseBytes(IntPtr pInProcessHandler, out bool fCompletionExpected)
|
|
||||||
{
|
|
||||||
return http_flush_response_bytes(pInProcessHandler, out fCompletionExpected);
|
|
||||||
}
|
|
||||||
public static unsafe HttpApiTypes.HTTP_REQUEST_V2* HttpGetRawRequest(IntPtr pInProcessHandler)
|
|
||||||
{
|
|
||||||
return http_get_raw_request(pInProcessHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HttpStopCallsIntoManaged()
|
|
||||||
{
|
|
||||||
http_stop_calls_into_managed();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HttpStopIncomingRequests()
|
|
||||||
{
|
|
||||||
http_stop_incoming_requests();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe HttpApiTypes.HTTP_RESPONSE_V2* HttpGetRawResponse(IntPtr pInProcessHandler)
|
|
||||||
{
|
|
||||||
return http_get_raw_response(pInProcessHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HttpSetResponseStatusCode(IntPtr pInProcessHandler, ushort statusCode, string pszReason)
|
|
||||||
{
|
|
||||||
Validate(http_set_response_status_code(pInProcessHandler, statusCode, pszReason));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe int HttpReadRequestBytes(IntPtr pInProcessHandler, byte* pvBuffer, int cbBuffer, out int dwBytesReceived, out bool fCompletionExpected)
|
|
||||||
{
|
|
||||||
return http_read_request_bytes(pInProcessHandler, pvBuffer, cbBuffer, out dwBytesReceived, out fCompletionExpected);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HttpGetCompletionInfo(IntPtr pCompletionInfo, out int cbBytes, out int hr)
|
|
||||||
{
|
|
||||||
http_get_completion_info(pCompletionInfo, out cbBytes, out hr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HttpSetManagedContext(IntPtr pInProcessHandler, IntPtr pvManagedContext)
|
|
||||||
{
|
|
||||||
Validate(http_set_managed_context(pInProcessHandler, pvManagedContext));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IISConfigurationData HttpGetApplicationProperties()
|
|
||||||
{
|
|
||||||
var iisConfigurationData = new IISConfigurationData();
|
|
||||||
Validate(http_get_application_properties(ref iisConfigurationData));
|
|
||||||
return iisConfigurationData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool HttpTryGetServerVariable(IntPtr pInProcessHandler, string variableName, out string value)
|
|
||||||
{
|
|
||||||
return http_get_server_variable(pInProcessHandler, variableName, out value) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe int HttpWebsocketsReadBytes(
|
|
||||||
IntPtr pInProcessHandler,
|
|
||||||
byte* pvBuffer,
|
|
||||||
int cbBuffer,
|
|
||||||
PFN_WEBSOCKET_ASYNC_COMPLETION pfnCompletionCallback,
|
|
||||||
IntPtr pvCompletionContext, out int dwBytesReceived,
|
|
||||||
out bool fCompletionExpected)
|
|
||||||
{
|
|
||||||
return http_websockets_read_bytes(pInProcessHandler, pvBuffer, cbBuffer, pfnCompletionCallback, pvCompletionContext, out dwBytesReceived, out fCompletionExpected);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe int HttpWebsocketsWriteBytes(
|
|
||||||
IntPtr pInProcessHandler,
|
|
||||||
HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks,
|
|
||||||
int nChunks,
|
|
||||||
PFN_WEBSOCKET_ASYNC_COMPLETION pfnCompletionCallback,
|
|
||||||
IntPtr pvCompletionContext,
|
|
||||||
out bool fCompletionExpected)
|
|
||||||
{
|
|
||||||
return http_websockets_write_bytes(pInProcessHandler, pDataChunks, nChunks, pfnCompletionCallback, pvCompletionContext, out fCompletionExpected);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HttpEnableWebsockets(IntPtr pInProcessHandler)
|
|
||||||
{
|
|
||||||
Validate(http_enable_websockets(pInProcessHandler));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool HttpTryCancelIO(IntPtr pInProcessHandler)
|
|
||||||
{
|
|
||||||
var hr = http_cancel_io(pInProcessHandler);
|
|
||||||
// Async operation finished
|
|
||||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363792(v=vs.85).aspx
|
|
||||||
if (hr == HR_NOT_FOUND)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Validate(hr);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe void HttpResponseSetUnknownHeader(IntPtr pInProcessHandler, byte* pszHeaderName, byte* pszHeaderValue, ushort usHeaderValueLength, bool fReplace)
|
|
||||||
{
|
|
||||||
Validate(http_response_set_unknown_header(pInProcessHandler, pszHeaderName, pszHeaderValue, usHeaderValueLength, fReplace));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe void HttpResponseSetKnownHeader(IntPtr pInProcessHandler, int headerId, byte* pHeaderValue, ushort length, bool fReplace)
|
|
||||||
{
|
|
||||||
Validate(http_response_set_known_header(pInProcessHandler, headerId, pHeaderValue, length, fReplace));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void HttpGetAuthenticationInformation(IntPtr pInProcessHandler, out string authType, out IntPtr token)
|
|
||||||
{
|
|
||||||
Validate(http_get_authentication_information(pInProcessHandler, out authType, out token));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Validate(int hr)
|
|
||||||
{
|
|
||||||
if (hr != HR_OK)
|
|
||||||
{
|
|
||||||
throw Marshal.GetExceptionForHR(hr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
// 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;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
// TODO redudant file, remove
|
|
||||||
// See https://github.com/aspnet/IISIntegration/issues/426
|
|
||||||
internal class DuplexStream : Stream
|
|
||||||
{
|
|
||||||
private Stream _requestBody;
|
|
||||||
private Stream _responseBody;
|
|
||||||
|
|
||||||
public DuplexStream(Stream requestBody, Stream responseBody)
|
|
||||||
{
|
|
||||||
_requestBody = requestBody;
|
|
||||||
_responseBody = responseBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanRead => true;
|
|
||||||
|
|
||||||
public override bool CanSeek => false;
|
|
||||||
|
|
||||||
public override bool CanWrite => true;
|
|
||||||
|
|
||||||
public override long Length => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
|
|
||||||
|
|
||||||
public override void Flush()
|
|
||||||
{
|
|
||||||
_responseBody.Flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
return _requestBody.Read(buffer, offset, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
_responseBody.Write(buffer, offset, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return _requestBody.ReadAsync(buffer, offset, count, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return _responseBody.WriteAsync(buffer, offset, count, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
// 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.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
// Primarily copied from https://github.com/aspnet/KestrelHttpServer/blob/dev/src/Kestrel.Transport.Libuv/Internal/LibuvAwaitable.cs
|
|
||||||
internal class IISAwaitable : ICriticalNotifyCompletion
|
|
||||||
{
|
|
||||||
private readonly static Action _callbackCompleted = () => { };
|
|
||||||
|
|
||||||
private Action _callback;
|
|
||||||
|
|
||||||
private Exception _exception;
|
|
||||||
private int _cbBytes;
|
|
||||||
private int _hr;
|
|
||||||
|
|
||||||
public static readonly NativeMethods.PFN_WEBSOCKET_ASYNC_COMPLETION ReadCallback = (IntPtr pHttpContext, IntPtr pCompletionInfo, IntPtr pvCompletionContext) =>
|
|
||||||
{
|
|
||||||
var context = (IISHttpContext)GCHandle.FromIntPtr(pvCompletionContext).Target;
|
|
||||||
|
|
||||||
NativeMethods.HttpGetCompletionInfo(pCompletionInfo, out int cbBytes, out int hr);
|
|
||||||
|
|
||||||
context.CompleteReadWebSockets(hr, cbBytes);
|
|
||||||
|
|
||||||
return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_PENDING;
|
|
||||||
};
|
|
||||||
|
|
||||||
public static readonly NativeMethods.PFN_WEBSOCKET_ASYNC_COMPLETION WriteCallback = (IntPtr pHttpContext, IntPtr pCompletionInfo, IntPtr pvCompletionContext) =>
|
|
||||||
{
|
|
||||||
var context = (IISHttpContext)GCHandle.FromIntPtr(pvCompletionContext).Target;
|
|
||||||
|
|
||||||
NativeMethods.HttpGetCompletionInfo(pCompletionInfo, out int cbBytes, out int hr);
|
|
||||||
|
|
||||||
context.CompleteWriteWebSockets(hr, cbBytes);
|
|
||||||
|
|
||||||
return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_PENDING;
|
|
||||||
};
|
|
||||||
|
|
||||||
public IISAwaitable GetAwaiter() => this;
|
|
||||||
public bool IsCompleted => _callback == _callbackCompleted;
|
|
||||||
|
|
||||||
public bool HasContinuation => _callback != null && !IsCompleted;
|
|
||||||
|
|
||||||
public int GetResult()
|
|
||||||
{
|
|
||||||
var exception = _exception;
|
|
||||||
var cbBytes = _cbBytes;
|
|
||||||
var hr = _hr;
|
|
||||||
|
|
||||||
// Reset the awaitable state
|
|
||||||
_exception = null;
|
|
||||||
_cbBytes = 0;
|
|
||||||
_callback = null;
|
|
||||||
_hr = 0;
|
|
||||||
|
|
||||||
if (exception != null)
|
|
||||||
{
|
|
||||||
// If the exception was an aborted read operation,
|
|
||||||
// return -1 to notify NativeReadAsync that the write was cancelled.
|
|
||||||
// E_OPERATIONABORTED == 0x800703e3 == -2147023901
|
|
||||||
// We also don't throw the exception here as this is expected behavior
|
|
||||||
// and can negatively impact perf if we catch an exception for each
|
|
||||||
// cann
|
|
||||||
if (hr != IISServerConstants.HResultCancelIO)
|
|
||||||
{
|
|
||||||
throw exception;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cbBytes = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cbBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnCompleted(Action continuation)
|
|
||||||
{
|
|
||||||
// There should never be a race between IsCompleted and OnCompleted since both operations
|
|
||||||
// should always be on the libuv thread
|
|
||||||
|
|
||||||
if (_callback == _callbackCompleted ||
|
|
||||||
Interlocked.CompareExchange(ref _callback, continuation, null) == _callbackCompleted)
|
|
||||||
{
|
|
||||||
// Just run it inline
|
|
||||||
Task.Run(continuation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnsafeOnCompleted(Action continuation)
|
|
||||||
{
|
|
||||||
OnCompleted(continuation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Complete(int hr, int cbBytes)
|
|
||||||
{
|
|
||||||
_hr = hr;
|
|
||||||
_exception = Marshal.GetExceptionForHR(hr);
|
|
||||||
_cbBytes = cbBytes;
|
|
||||||
var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted);
|
|
||||||
continuation?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Action GetCompletion(int hr, int cbBytes)
|
|
||||||
{
|
|
||||||
_hr = hr;
|
|
||||||
_exception = Marshal.GetExceptionForHR(hr);
|
|
||||||
_cbBytes = cbBytes;
|
|
||||||
|
|
||||||
return Interlocked.Exchange(ref _callback, _callbackCompleted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
// 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.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
internal unsafe struct IISConfigurationData
|
|
||||||
{
|
|
||||||
[MarshalAs(UnmanagedType.BStr)]
|
|
||||||
public string pwzFullApplicationPath;
|
|
||||||
[MarshalAs(UnmanagedType.BStr)]
|
|
||||||
public string pwzVirtualApplicationPath;
|
|
||||||
public bool fWindowsAuthEnabled;
|
|
||||||
public bool fBasicAuthEnabled;
|
|
||||||
public bool fAnonymousAuthEnable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,276 +0,0 @@
|
||||||
// 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.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
internal partial class IISHttpContext : IFeatureCollection,
|
|
||||||
IHttpRequestFeature,
|
|
||||||
IHttpResponseFeature,
|
|
||||||
IHttpUpgradeFeature,
|
|
||||||
IHttpRequestLifetimeFeature,
|
|
||||||
IHttpAuthenticationFeature,
|
|
||||||
IServerVariablesFeature
|
|
||||||
{
|
|
||||||
// NOTE: When feature interfaces are added to or removed from this HttpProtocol implementation,
|
|
||||||
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
|
|
||||||
|
|
||||||
private int _featureRevision;
|
|
||||||
private string _httpProtocolVersion = null;
|
|
||||||
|
|
||||||
private List<KeyValuePair<Type, object>> MaybeExtra;
|
|
||||||
public void ResetFeatureCollection()
|
|
||||||
{
|
|
||||||
Initialize();
|
|
||||||
MaybeExtra?.Clear();
|
|
||||||
_featureRevision++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private object ExtraFeatureGet(Type key)
|
|
||||||
{
|
|
||||||
if (MaybeExtra == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (var i = 0; i < MaybeExtra.Count; i++)
|
|
||||||
{
|
|
||||||
var kv = MaybeExtra[i];
|
|
||||||
if (kv.Key == key)
|
|
||||||
{
|
|
||||||
return kv.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExtraFeatureSet(Type key, object value)
|
|
||||||
{
|
|
||||||
if (MaybeExtra == null)
|
|
||||||
{
|
|
||||||
MaybeExtra = new List<KeyValuePair<Type, object>>(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < MaybeExtra.Count; i++)
|
|
||||||
{
|
|
||||||
if (MaybeExtra[i].Key == key)
|
|
||||||
{
|
|
||||||
MaybeExtra[i] = new KeyValuePair<Type, object>(key, value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MaybeExtra.Add(new KeyValuePair<Type, object>(key, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
string IHttpRequestFeature.Protocol
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_httpProtocolVersion == null)
|
|
||||||
{
|
|
||||||
var protocol = HttpVersion;
|
|
||||||
if (protocol.Major == 1 && protocol.Minor == 1)
|
|
||||||
{
|
|
||||||
_httpProtocolVersion = "HTTP/1.1";
|
|
||||||
}
|
|
||||||
else if (protocol.Major == 1 && protocol.Minor == 0)
|
|
||||||
{
|
|
||||||
_httpProtocolVersion = "HTTP/1.0";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_httpProtocolVersion = "HTTP/" + protocol.ToString(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _httpProtocolVersion;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_httpProtocolVersion = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string IHttpRequestFeature.Scheme
|
|
||||||
{
|
|
||||||
get => Scheme;
|
|
||||||
set => Scheme = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
string IHttpRequestFeature.Method
|
|
||||||
{
|
|
||||||
get => Method;
|
|
||||||
set => Method = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
string IHttpRequestFeature.PathBase
|
|
||||||
{
|
|
||||||
get => PathBase;
|
|
||||||
set => PathBase = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
string IHttpRequestFeature.Path
|
|
||||||
{
|
|
||||||
get => Path;
|
|
||||||
set => Path = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
string IHttpRequestFeature.QueryString
|
|
||||||
{
|
|
||||||
get => QueryString;
|
|
||||||
set => QueryString = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
string IHttpRequestFeature.RawTarget
|
|
||||||
{
|
|
||||||
get => RawTarget;
|
|
||||||
set => RawTarget = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
IHeaderDictionary IHttpRequestFeature.Headers
|
|
||||||
{
|
|
||||||
get => RequestHeaders;
|
|
||||||
set => RequestHeaders = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream IHttpRequestFeature.Body
|
|
||||||
{
|
|
||||||
get => RequestBody;
|
|
||||||
set => RequestBody = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int IHttpResponseFeature.StatusCode
|
|
||||||
{
|
|
||||||
get => StatusCode;
|
|
||||||
set => StatusCode = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
string IHttpResponseFeature.ReasonPhrase
|
|
||||||
{
|
|
||||||
get => ReasonPhrase;
|
|
||||||
set => ReasonPhrase = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
IHeaderDictionary IHttpResponseFeature.Headers
|
|
||||||
{
|
|
||||||
get => ResponseHeaders;
|
|
||||||
set => ResponseHeaders = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream IHttpResponseFeature.Body
|
|
||||||
{
|
|
||||||
get => ResponseBody;
|
|
||||||
set => ResponseBody = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
CancellationToken IHttpRequestLifetimeFeature.RequestAborted
|
|
||||||
{
|
|
||||||
get => RequestAborted;
|
|
||||||
set => RequestAborted = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IHttpResponseFeature.HasStarted => HasResponseStarted;
|
|
||||||
|
|
||||||
bool IHttpUpgradeFeature.IsUpgradableRequest => _server.IsWebSocketAvailible(_pInProcessHandler);
|
|
||||||
|
|
||||||
bool IFeatureCollection.IsReadOnly => false;
|
|
||||||
|
|
||||||
int IFeatureCollection.Revision => _featureRevision;
|
|
||||||
|
|
||||||
ClaimsPrincipal IHttpAuthenticationFeature.User
|
|
||||||
{
|
|
||||||
get => User;
|
|
||||||
set => User = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IAuthenticationHandler Handler { get; set; }
|
|
||||||
|
|
||||||
string IServerVariablesFeature.this[string variableName]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(variableName))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NativeMethods.HttpTryGetServerVariable(_pInProcessHandler, variableName, out var value) ? value : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object IFeatureCollection.this[Type key]
|
|
||||||
{
|
|
||||||
get => FastFeatureGet(key);
|
|
||||||
set => FastFeatureSet(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
TFeature IFeatureCollection.Get<TFeature>()
|
|
||||||
{
|
|
||||||
return (TFeature)FastFeatureGet(typeof(TFeature));
|
|
||||||
}
|
|
||||||
|
|
||||||
void IFeatureCollection.Set<TFeature>(TFeature instance)
|
|
||||||
{
|
|
||||||
FastFeatureSet(typeof(TFeature), instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IHttpResponseFeature.OnStarting(Func<object, Task> callback, object state)
|
|
||||||
{
|
|
||||||
OnStarting(callback, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IHttpResponseFeature.OnCompleted(Func<object, Task> callback, object state)
|
|
||||||
{
|
|
||||||
OnCompleted(callback, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()
|
|
||||||
{
|
|
||||||
// TODO fix these exceptions strings
|
|
||||||
if (!((IHttpUpgradeFeature)this).IsUpgradableRequest)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("CoreStrings.CannotUpgradeNonUpgradableRequest");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_wasUpgraded)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("CoreStrings.UpgradeCannotBeCalledMultipleTimes");
|
|
||||||
}
|
|
||||||
if (HasResponseStarted)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("CoreStrings.UpgradeCannotBeCalledMultipleTimes");
|
|
||||||
}
|
|
||||||
_wasUpgraded = true;
|
|
||||||
|
|
||||||
StatusCode = StatusCodes.Status101SwitchingProtocols;
|
|
||||||
ReasonPhrase = ReasonPhrases.GetReasonPhrase(StatusCodes.Status101SwitchingProtocols);
|
|
||||||
_readWebSocketsOperation = new IISAwaitable();
|
|
||||||
_writeWebSocketsOperation = new IISAwaitable();
|
|
||||||
NativeMethods.HttpEnableWebsockets(_pInProcessHandler);
|
|
||||||
|
|
||||||
// Upgrade async will cause the stream processing to go into duplex mode
|
|
||||||
await UpgradeAsync();
|
|
||||||
|
|
||||||
return new DuplexStream(RequestBody, ResponseBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator<KeyValuePair<Type, object>> IEnumerable<KeyValuePair<Type, object>>.GetEnumerator() => FastEnumerable().GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator();
|
|
||||||
|
|
||||||
void IHttpRequestLifetimeFeature.Abort()
|
|
||||||
{
|
|
||||||
Abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,336 +0,0 @@
|
||||||
// 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.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
internal partial class IISHttpContext
|
|
||||||
{
|
|
||||||
private static readonly Type IHttpRequestFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestFeature);
|
|
||||||
private static readonly Type IHttpResponseFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpResponseFeature);
|
|
||||||
private static readonly Type IHttpRequestIdentifierFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature);
|
|
||||||
private static readonly Type IServiceProvidersFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature);
|
|
||||||
private static readonly Type IHttpRequestLifetimeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature);
|
|
||||||
private static readonly Type IHttpConnectionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature);
|
|
||||||
private static readonly Type IHttpAuthenticationFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature);
|
|
||||||
private static readonly Type IQueryFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IQueryFeature);
|
|
||||||
private static readonly Type IFormFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IFormFeature);
|
|
||||||
private static readonly Type IHttpUpgradeFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature);
|
|
||||||
private static readonly Type IResponseCookiesFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature);
|
|
||||||
private static readonly Type IItemsFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IItemsFeature);
|
|
||||||
private static readonly Type ITlsConnectionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature);
|
|
||||||
private static readonly Type IHttpWebSocketFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature);
|
|
||||||
private static readonly Type ISessionFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
|
|
||||||
private static readonly Type IHttpBodyControlFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
|
|
||||||
private static readonly Type IHttpSendFileFeatureType = typeof(global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
|
||||||
private static readonly Type IISHttpContextType = typeof(IISHttpContext);
|
|
||||||
private static readonly Type IServerVariablesFeature = typeof(global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature);
|
|
||||||
|
|
||||||
private object _currentIHttpRequestFeature;
|
|
||||||
private object _currentIHttpResponseFeature;
|
|
||||||
private object _currentIHttpRequestIdentifierFeature;
|
|
||||||
private object _currentIServiceProvidersFeature;
|
|
||||||
private object _currentIHttpRequestLifetimeFeature;
|
|
||||||
private object _currentIHttpConnectionFeature;
|
|
||||||
private object _currentIHttpAuthenticationFeature;
|
|
||||||
private object _currentIQueryFeature;
|
|
||||||
private object _currentIFormFeature;
|
|
||||||
private object _currentIHttpUpgradeFeature;
|
|
||||||
private object _currentIResponseCookiesFeature;
|
|
||||||
private object _currentIItemsFeature;
|
|
||||||
private object _currentITlsConnectionFeature;
|
|
||||||
private object _currentIHttpMaxRequestBodySizeFeature;
|
|
||||||
private object _currentIHttpMinRequestBodyDataRateFeature;
|
|
||||||
private object _currentIHttpMinResponseDataRateFeature;
|
|
||||||
private object _currentIHttpWebSocketFeature;
|
|
||||||
private object _currentISessionFeature;
|
|
||||||
private object _currentIHttpBodyControlFeature;
|
|
||||||
private object _currentIHttpSendFileFeature;
|
|
||||||
private object _currentIServerVariablesFeature;
|
|
||||||
|
|
||||||
private void Initialize()
|
|
||||||
{
|
|
||||||
_currentIHttpRequestFeature = this;
|
|
||||||
_currentIHttpResponseFeature = this;
|
|
||||||
_currentIHttpUpgradeFeature = this;
|
|
||||||
_currentIHttpRequestIdentifierFeature = this;
|
|
||||||
_currentIHttpRequestLifetimeFeature = this;
|
|
||||||
_currentIHttpConnectionFeature = this;
|
|
||||||
_currentIHttpMaxRequestBodySizeFeature = this;
|
|
||||||
_currentIHttpMinRequestBodyDataRateFeature = this;
|
|
||||||
_currentIHttpMinResponseDataRateFeature = this;
|
|
||||||
_currentIHttpBodyControlFeature = this;
|
|
||||||
_currentIHttpAuthenticationFeature = this;
|
|
||||||
_currentIServerVariablesFeature = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal object FastFeatureGet(Type key)
|
|
||||||
{
|
|
||||||
if (key == IHttpRequestFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIHttpRequestFeature;
|
|
||||||
}
|
|
||||||
if (key == IHttpResponseFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIHttpResponseFeature;
|
|
||||||
}
|
|
||||||
if (key == IHttpRequestIdentifierFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIHttpRequestIdentifierFeature;
|
|
||||||
}
|
|
||||||
if (key == IServiceProvidersFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIServiceProvidersFeature;
|
|
||||||
}
|
|
||||||
if (key == IHttpRequestLifetimeFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIHttpRequestLifetimeFeature;
|
|
||||||
}
|
|
||||||
if (key == IHttpConnectionFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIHttpConnectionFeature;
|
|
||||||
}
|
|
||||||
if (key == IHttpAuthenticationFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIHttpAuthenticationFeature;
|
|
||||||
}
|
|
||||||
if (key == IQueryFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIQueryFeature;
|
|
||||||
}
|
|
||||||
if (key == IFormFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIFormFeature;
|
|
||||||
}
|
|
||||||
if (key == IHttpUpgradeFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIHttpUpgradeFeature;
|
|
||||||
}
|
|
||||||
if (key == IResponseCookiesFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIResponseCookiesFeature;
|
|
||||||
}
|
|
||||||
if (key == IItemsFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIItemsFeature;
|
|
||||||
}
|
|
||||||
if (key == ITlsConnectionFeatureType)
|
|
||||||
{
|
|
||||||
return _currentITlsConnectionFeature;
|
|
||||||
}
|
|
||||||
if (key == IHttpWebSocketFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIHttpWebSocketFeature;
|
|
||||||
}
|
|
||||||
if (key == ISessionFeatureType)
|
|
||||||
{
|
|
||||||
return _currentISessionFeature;
|
|
||||||
}
|
|
||||||
if (key == IHttpBodyControlFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIHttpBodyControlFeature;
|
|
||||||
}
|
|
||||||
if (key == IHttpSendFileFeatureType)
|
|
||||||
{
|
|
||||||
return _currentIHttpSendFileFeature;
|
|
||||||
}
|
|
||||||
if (key == IISHttpContextType)
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
if (key == IServerVariablesFeature)
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExtraFeatureGet(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void FastFeatureSet(Type key, object feature)
|
|
||||||
{
|
|
||||||
_featureRevision++;
|
|
||||||
|
|
||||||
if (key == IHttpRequestFeatureType)
|
|
||||||
{
|
|
||||||
_currentIHttpRequestFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IHttpResponseFeatureType)
|
|
||||||
{
|
|
||||||
_currentIHttpResponseFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IHttpRequestIdentifierFeatureType)
|
|
||||||
{
|
|
||||||
_currentIHttpRequestIdentifierFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IServiceProvidersFeatureType)
|
|
||||||
{
|
|
||||||
_currentIServiceProvidersFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IHttpRequestLifetimeFeatureType)
|
|
||||||
{
|
|
||||||
_currentIHttpRequestLifetimeFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IHttpConnectionFeatureType)
|
|
||||||
{
|
|
||||||
_currentIHttpConnectionFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IHttpAuthenticationFeatureType)
|
|
||||||
{
|
|
||||||
_currentIHttpAuthenticationFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IQueryFeatureType)
|
|
||||||
{
|
|
||||||
_currentIQueryFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IFormFeatureType)
|
|
||||||
{
|
|
||||||
_currentIFormFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IHttpUpgradeFeatureType)
|
|
||||||
{
|
|
||||||
_currentIHttpUpgradeFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IResponseCookiesFeatureType)
|
|
||||||
{
|
|
||||||
_currentIResponseCookiesFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IItemsFeatureType)
|
|
||||||
{
|
|
||||||
_currentIItemsFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == ITlsConnectionFeatureType)
|
|
||||||
{
|
|
||||||
_currentITlsConnectionFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IHttpWebSocketFeatureType)
|
|
||||||
{
|
|
||||||
_currentIHttpWebSocketFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == ISessionFeatureType)
|
|
||||||
{
|
|
||||||
_currentISessionFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IHttpBodyControlFeatureType)
|
|
||||||
{
|
|
||||||
_currentIHttpBodyControlFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IHttpSendFileFeatureType)
|
|
||||||
{
|
|
||||||
_currentIHttpSendFileFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IServerVariablesFeature)
|
|
||||||
{
|
|
||||||
_currentIServerVariablesFeature = feature;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key == IISHttpContextType)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Cannot set IISHttpContext in feature collection");
|
|
||||||
};
|
|
||||||
ExtraFeatureSet(key, feature);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<KeyValuePair<Type, object>> FastEnumerable()
|
|
||||||
{
|
|
||||||
if (_currentIHttpRequestFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IHttpRequestFeatureType, _currentIHttpRequestFeature as global::Microsoft.AspNetCore.Http.Features.IHttpRequestFeature);
|
|
||||||
}
|
|
||||||
if (_currentIHttpResponseFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IHttpResponseFeatureType, _currentIHttpResponseFeature as global::Microsoft.AspNetCore.Http.Features.IHttpResponseFeature);
|
|
||||||
}
|
|
||||||
if (_currentIHttpRequestIdentifierFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IHttpRequestIdentifierFeatureType, _currentIHttpRequestIdentifierFeature as global::Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature);
|
|
||||||
}
|
|
||||||
if (_currentIServiceProvidersFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IServiceProvidersFeatureType, _currentIServiceProvidersFeature as global::Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature);
|
|
||||||
}
|
|
||||||
if (_currentIHttpRequestLifetimeFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IHttpRequestLifetimeFeatureType, _currentIHttpRequestLifetimeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature);
|
|
||||||
}
|
|
||||||
if (_currentIHttpConnectionFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IHttpConnectionFeatureType, _currentIHttpConnectionFeature as global::Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature);
|
|
||||||
}
|
|
||||||
if (_currentIHttpAuthenticationFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature as global::Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature);
|
|
||||||
}
|
|
||||||
if (_currentIQueryFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IQueryFeatureType, _currentIQueryFeature as global::Microsoft.AspNetCore.Http.Features.IQueryFeature);
|
|
||||||
}
|
|
||||||
if (_currentIFormFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IFormFeatureType, _currentIFormFeature as global::Microsoft.AspNetCore.Http.Features.IFormFeature);
|
|
||||||
}
|
|
||||||
if (_currentIHttpUpgradeFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IHttpUpgradeFeatureType, _currentIHttpUpgradeFeature as global::Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature);
|
|
||||||
}
|
|
||||||
if (_currentIResponseCookiesFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IResponseCookiesFeatureType, _currentIResponseCookiesFeature as global::Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature);
|
|
||||||
}
|
|
||||||
if (_currentIItemsFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IItemsFeatureType, _currentIItemsFeature as global::Microsoft.AspNetCore.Http.Features.IItemsFeature);
|
|
||||||
}
|
|
||||||
if (_currentITlsConnectionFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(ITlsConnectionFeatureType, _currentITlsConnectionFeature as global::Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature);
|
|
||||||
}
|
|
||||||
if (_currentIHttpWebSocketFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IHttpWebSocketFeatureType, _currentIHttpWebSocketFeature as global::Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature);
|
|
||||||
}
|
|
||||||
if (_currentISessionFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(ISessionFeatureType, _currentISessionFeature as global::Microsoft.AspNetCore.Http.Features.ISessionFeature);
|
|
||||||
}
|
|
||||||
if (_currentIHttpBodyControlFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IHttpBodyControlFeatureType, _currentIHttpBodyControlFeature as global::Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature);
|
|
||||||
}
|
|
||||||
if (_currentIHttpSendFileFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as global::Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature);
|
|
||||||
}
|
|
||||||
if (_currentIServerVariablesFeature != null)
|
|
||||||
{
|
|
||||||
yield return new KeyValuePair<Type, object>(IServerVariablesFeature, _currentIServerVariablesFeature as global::Microsoft.AspNetCore.Http.Features.IServerVariablesFeature);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MaybeExtra != null)
|
|
||||||
{
|
|
||||||
foreach (var item in MaybeExtra)
|
|
||||||
{
|
|
||||||
yield return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
InitializeLocalEndpoint();
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
InitializeLocalEndpoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,475 +0,0 @@
|
||||||
// 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.Buffers;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.HttpSys.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
internal partial class IISHttpContext
|
|
||||||
{
|
|
||||||
private const int HttpDataChunkStackLimit = 128; // 16 bytes per HTTP_DATA_CHUNK
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads data from the Input pipe to the user.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="memory"></param>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal async Task<int> ReadAsync(Memory<byte> memory, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
StartProcessingRequestAndResponseBody();
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var result = await Input.Reader.ReadAsync();
|
|
||||||
var readableBuffer = result.Buffer;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!readableBuffer.IsEmpty)
|
|
||||||
{
|
|
||||||
var actual = Math.Min(readableBuffer.Length, memory.Length);
|
|
||||||
readableBuffer = readableBuffer.Slice(0, actual);
|
|
||||||
readableBuffer.CopyTo(memory.Span);
|
|
||||||
return (int)actual;
|
|
||||||
}
|
|
||||||
else if (result.IsCompleted)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Input.Reader.AdvanceTo(readableBuffer.End, readableBuffer.End);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes data to the output pipe.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="memory"></param>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal Task WriteAsync(ReadOnlyMemory<byte> memory, CancellationToken cancellationToken = default(CancellationToken))
|
|
||||||
{
|
|
||||||
|
|
||||||
// Want to keep exceptions consistent,
|
|
||||||
if (!_hasResponseStarted)
|
|
||||||
{
|
|
||||||
return WriteAsyncAwaited(memory, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_stateSync)
|
|
||||||
{
|
|
||||||
DisableReads();
|
|
||||||
return Output.WriteAsync(memory, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Flushes the data in the output pipe
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
|
|
||||||
{
|
|
||||||
if (!_hasResponseStarted)
|
|
||||||
{
|
|
||||||
return FlushAsyncAwaited(cancellationToken);
|
|
||||||
}
|
|
||||||
lock (_stateSync)
|
|
||||||
{
|
|
||||||
DisableReads();
|
|
||||||
return Output.FlushAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartProcessingRequestAndResponseBody()
|
|
||||||
{
|
|
||||||
if (_processBodiesTask == null)
|
|
||||||
{
|
|
||||||
lock (_createReadWriteBodySync)
|
|
||||||
{
|
|
||||||
if (_processBodiesTask == null)
|
|
||||||
{
|
|
||||||
_processBodiesTask = ConsumeAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task FlushAsyncAwaited(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
await InitializeResponseAwaited();
|
|
||||||
|
|
||||||
Task flushTask;
|
|
||||||
lock (_stateSync)
|
|
||||||
{
|
|
||||||
DisableReads();
|
|
||||||
|
|
||||||
// Want to guarantee that data has been written to the pipe before releasing the lock.
|
|
||||||
flushTask = Output.FlushAsync(cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
await flushTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task WriteAsyncAwaited(ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
// WriteAsyncAwaited is only called for the first write to the body.
|
|
||||||
// Ensure headers are flushed if Write(Chunked)Async isn't called.
|
|
||||||
await InitializeResponseAwaited();
|
|
||||||
|
|
||||||
Task writeTask;
|
|
||||||
lock (_stateSync)
|
|
||||||
{
|
|
||||||
DisableReads();
|
|
||||||
|
|
||||||
// Want to guarantee that data has been written to the pipe before releasing the lock.
|
|
||||||
writeTask = Output.WriteAsync(data, cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
await writeTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConsumeAsync is called when either the first read or first write is done.
|
|
||||||
// There are two modes for reading and writing to the request/response bodies without upgrade.
|
|
||||||
// 1. Await all reads and try to read from the Output pipe
|
|
||||||
// 2. Done reading and await all writes.
|
|
||||||
// If the request is upgraded, we will start bidirectional streams for the input and output.
|
|
||||||
private async Task ConsumeAsync()
|
|
||||||
{
|
|
||||||
await ReadAndWriteLoopAsync();
|
|
||||||
|
|
||||||
// The ReadAndWriteLoop can return due to being upgraded. Check if _wasUpgraded is true to determine
|
|
||||||
// whether we go to a bidirectional stream or only write.
|
|
||||||
if (_wasUpgraded)
|
|
||||||
{
|
|
||||||
await StartBidirectionalStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe IISAwaitable ReadFromIISAsync(int length)
|
|
||||||
{
|
|
||||||
Action completion = null;
|
|
||||||
lock (_stateSync)
|
|
||||||
{
|
|
||||||
// We don't want to read if there is data available in the output pipe
|
|
||||||
// Therefore, we mark the current operation as cancelled to allow for the read
|
|
||||||
// to be requeued.
|
|
||||||
if (Output.Reader.TryRead(out var result))
|
|
||||||
{
|
|
||||||
// If the buffer is empty, it is considered a write of zero.
|
|
||||||
// we still want to cancel and allow the write to occur.
|
|
||||||
completion = _operation.GetCompletion(hr: IISServerConstants.HResultCancelIO, cbBytes: 0);
|
|
||||||
Output.Reader.AdvanceTo(result.Buffer.Start);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var hr = NativeMethods.HttpReadRequestBytes(
|
|
||||||
_pInProcessHandler,
|
|
||||||
(byte*)_inputHandle.Pointer,
|
|
||||||
length,
|
|
||||||
out var dwReceivedBytes,
|
|
||||||
out bool fCompletionExpected);
|
|
||||||
// if we complete the read synchronously, there is no need to set the reading flag
|
|
||||||
// as there is no cancelable operation.
|
|
||||||
if (!fCompletionExpected)
|
|
||||||
{
|
|
||||||
completion = _operation.GetCompletion(hr, dwReceivedBytes);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_reading = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoke the completion outside of the lock if the reead finished synchronously.
|
|
||||||
completion?.Invoke();
|
|
||||||
|
|
||||||
return _operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe IISAwaitable WriteToIISAsync(ReadOnlySequence<byte> buffer)
|
|
||||||
{
|
|
||||||
var fCompletionExpected = false;
|
|
||||||
var hr = 0;
|
|
||||||
var nChunks = 0;
|
|
||||||
|
|
||||||
// Count the number of chunks in memory.
|
|
||||||
if (buffer.IsSingleSegment)
|
|
||||||
{
|
|
||||||
nChunks = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var memory in buffer)
|
|
||||||
{
|
|
||||||
nChunks++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nChunks == 1)
|
|
||||||
{
|
|
||||||
// If there is only a single chunk, use fixed to get a pointer to the buffer
|
|
||||||
var pDataChunks = stackalloc HttpApiTypes.HTTP_DATA_CHUNK[1];
|
|
||||||
|
|
||||||
fixed (byte* pBuffer = &MemoryMarshal.GetReference(buffer.First.Span))
|
|
||||||
{
|
|
||||||
ref var chunk = ref pDataChunks[0];
|
|
||||||
|
|
||||||
chunk.DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
|
|
||||||
chunk.fromMemory.pBuffer = (IntPtr)pBuffer;
|
|
||||||
chunk.fromMemory.BufferLength = (uint)buffer.Length;
|
|
||||||
hr = NativeMethods.HttpWriteResponseBytes(_pInProcessHandler, pDataChunks, nChunks, out fCompletionExpected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (nChunks < HttpDataChunkStackLimit)
|
|
||||||
{
|
|
||||||
// To avoid stackoverflows, we will only stackalloc if the write size is less than the StackChunkLimit
|
|
||||||
// The stack size is IIS is by default 128/256 KB, so we are generous with this threshold.
|
|
||||||
var pDataChunks = stackalloc HttpApiTypes.HTTP_DATA_CHUNK[nChunks];
|
|
||||||
hr = WriteSequenceToIIS(nChunks, buffer, pDataChunks, out fCompletionExpected);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Otherwise allocate the chunks on the heap.
|
|
||||||
var chunks = new HttpApiTypes.HTTP_DATA_CHUNK[nChunks];
|
|
||||||
fixed (HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks = chunks)
|
|
||||||
{
|
|
||||||
hr = WriteSequenceToIIS(nChunks, buffer, pDataChunks, out fCompletionExpected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fCompletionExpected)
|
|
||||||
{
|
|
||||||
_operation.Complete(hr, 0);
|
|
||||||
}
|
|
||||||
return _operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe int WriteSequenceToIIS(int nChunks, ReadOnlySequence<byte> buffer, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, out bool fCompletionExpected)
|
|
||||||
{
|
|
||||||
var currentChunk = 0;
|
|
||||||
var hr = 0;
|
|
||||||
|
|
||||||
// REVIEW: We don't really need this list since the memory is already pinned with the default pool,
|
|
||||||
// but shouldn't assume the pool implementation right now. Unfortunately, this causes a heap allocation...
|
|
||||||
var handles = new MemoryHandle[nChunks];
|
|
||||||
|
|
||||||
foreach (var b in buffer)
|
|
||||||
{
|
|
||||||
ref var handle = ref handles[currentChunk];
|
|
||||||
ref var chunk = ref pDataChunks[currentChunk];
|
|
||||||
handle = b.Pin();
|
|
||||||
|
|
||||||
chunk.DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
|
|
||||||
chunk.fromMemory.BufferLength = (uint)b.Length;
|
|
||||||
chunk.fromMemory.pBuffer = (IntPtr)handle.Pointer;
|
|
||||||
|
|
||||||
currentChunk++;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr = NativeMethods.HttpWriteResponseBytes(_pInProcessHandler, pDataChunks, nChunks, out fCompletionExpected);
|
|
||||||
|
|
||||||
// Free the handles
|
|
||||||
foreach (var handle in handles)
|
|
||||||
{
|
|
||||||
handle.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return hr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe IISAwaitable FlushToIISAsync()
|
|
||||||
{
|
|
||||||
// Calls flush
|
|
||||||
var hr = 0;
|
|
||||||
hr = NativeMethods.HttpFlushResponseBytes(_pInProcessHandler, out var fCompletionExpected);
|
|
||||||
if (!fCompletionExpected)
|
|
||||||
{
|
|
||||||
_operation.Complete(hr, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Main function for control flow with IIS.
|
|
||||||
/// Uses two Pipes (Input and Output) between application calls to Read/Write/FlushAsync
|
|
||||||
/// Control Flow:
|
|
||||||
/// Try to see if there is data written by the application code (using TryRead)
|
|
||||||
/// and write it to IIS.
|
|
||||||
/// Check if the connection has been upgraded and call StartBidirectionalStreams
|
|
||||||
/// if it has.
|
|
||||||
/// Await reading from IIS, which will be cancelled if application code calls Write/FlushAsync.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The Reading and Writing task.</returns>
|
|
||||||
private async Task ReadAndWriteLoopAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// First we check if there is anything to write from the Output pipe
|
|
||||||
// If there is, we call WriteToIISAsync
|
|
||||||
// Check if Output pipe has anything to write to IIS.
|
|
||||||
if (Output.Reader.TryRead(out var readResult))
|
|
||||||
{
|
|
||||||
var buffer = readResult.Buffer;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!buffer.IsEmpty)
|
|
||||||
{
|
|
||||||
// Write to IIS buffers
|
|
||||||
// Guaranteed to write the entire buffer to IIS
|
|
||||||
await WriteToIISAsync(buffer);
|
|
||||||
}
|
|
||||||
else if (readResult.IsCompleted)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Flush of zero bytes
|
|
||||||
await FlushToIISAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// Always Advance the data pointer to the end of the buffer.
|
|
||||||
Output.Reader.AdvanceTo(buffer.End);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there was an upgrade. If there is, we will replace the request and response bodies with
|
|
||||||
// two seperate loops. These will still be using the same Input and Output pipes here.
|
|
||||||
if (_upgradeTcs?.TrySetResult(null) == true)
|
|
||||||
{
|
|
||||||
// _wasUpgraded will be set at this point, exit the loop and we will check if we upgraded or not
|
|
||||||
// when going to next read/write type.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we handle the read.
|
|
||||||
var memory = Input.Writer.GetMemory();
|
|
||||||
_inputHandle = memory.Pin();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Lock around invoking ReadFromIISAsync as we don't want to call CancelIo
|
|
||||||
// when calling read
|
|
||||||
var read = await ReadFromIISAsync(memory.Length);
|
|
||||||
|
|
||||||
// read value of 0 == done reading
|
|
||||||
// read value of -1 == read cancelled, still allowed to read but we
|
|
||||||
// need a write to occur first.
|
|
||||||
if (read == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (read == -1)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Input.Writer.Advance(read);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// Always commit any changes to the Input pipe
|
|
||||||
_inputHandle.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush the read data for the Input Pipe writer
|
|
||||||
var flushResult = await Input.Writer.FlushAsync();
|
|
||||||
|
|
||||||
// If the pipe was closed, we are done reading,
|
|
||||||
if (flushResult.IsCompleted || flushResult.IsCanceled)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete the input writer as we are done reading the request body.
|
|
||||||
Input.Writer.Complete();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Input.Writer.Complete(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
await WriteLoopAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Secondary function for control flow with IIS. This is only called once we are done
|
|
||||||
/// reading the request body. We now await reading from the Output pipe.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
private async Task WriteLoopAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// Reading is done, so we will await all reads from the output pipe
|
|
||||||
var readResult = await Output.Reader.ReadAsync();
|
|
||||||
|
|
||||||
// Get data from pipe
|
|
||||||
var buffer = readResult.Buffer;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!buffer.IsEmpty)
|
|
||||||
{
|
|
||||||
// Write to IIS buffers
|
|
||||||
// Guaranteed to write the entire buffer to IIS
|
|
||||||
await WriteToIISAsync(buffer);
|
|
||||||
}
|
|
||||||
else if (readResult.IsCompleted)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Flush of zero bytes will
|
|
||||||
await FlushToIISAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// Always Advance the data pointer to the end of the buffer.
|
|
||||||
Output.Reader.AdvanceTo(buffer.End);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the output pipe as we are done reading from it.
|
|
||||||
Output.Reader.Complete();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Output.Reader.Complete(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always called from within a lock
|
|
||||||
private void DisableReads()
|
|
||||||
{
|
|
||||||
// To avoid concurrent reading and writing, if we have a pending read,
|
|
||||||
// we must cancel it.
|
|
||||||
// _reading will always be false if we upgrade to websockets, so we don't need to check wasUpgrade
|
|
||||||
// Also, we set _reading to false after cancelling to detect redundant calls
|
|
||||||
if (_reading)
|
|
||||||
{
|
|
||||||
_reading = false;
|
|
||||||
// Calls IHttpContext->CancelIo(), which will cause the OnAsyncCompletion handler to fire.
|
|
||||||
NativeMethods.HttpTryCancelIO(_pInProcessHandler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,225 +0,0 @@
|
||||||
// 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.Buffers;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.HttpSys.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the websocket portion of the <see cref="IISHttpContext"/>
|
|
||||||
/// </summary>
|
|
||||||
internal partial class IISHttpContext
|
|
||||||
{
|
|
||||||
private bool _wasUpgraded; // Used for detecting repeated upgrades in IISHttpContext
|
|
||||||
|
|
||||||
private IISAwaitable _readWebSocketsOperation;
|
|
||||||
private IISAwaitable _writeWebSocketsOperation;
|
|
||||||
private TaskCompletionSource<object> _upgradeTcs;
|
|
||||||
|
|
||||||
private Task StartBidirectionalStream()
|
|
||||||
{
|
|
||||||
// IIS allows for websocket support and duplex channels only on Win8 and above
|
|
||||||
// This allows us to have two tasks for reading the request and writing the response
|
|
||||||
var readWebsocketTask = ReadWebSockets();
|
|
||||||
var writeWebsocketTask = WriteWebSockets();
|
|
||||||
return Task.WhenAll(readWebsocketTask, writeWebsocketTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpgradeAsync()
|
|
||||||
{
|
|
||||||
if (_upgradeTcs == null)
|
|
||||||
{
|
|
||||||
_upgradeTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
// Flush any contents of the OutputPipe before upgrading to websockets.
|
|
||||||
await FlushAsync();
|
|
||||||
await _upgradeTcs.Task;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe IISAwaitable ReadWebSocketsFromIISAsync(int length)
|
|
||||||
{
|
|
||||||
var hr = 0;
|
|
||||||
int dwReceivedBytes;
|
|
||||||
bool fCompletionExpected;
|
|
||||||
|
|
||||||
// For websocket calls, we can directly provide a callback function to be called once the websocket operation completes.
|
|
||||||
hr = NativeMethods.HttpWebsocketsReadBytes(
|
|
||||||
_pInProcessHandler,
|
|
||||||
(byte*)_inputHandle.Pointer,
|
|
||||||
length,
|
|
||||||
IISAwaitable.ReadCallback,
|
|
||||||
(IntPtr)_thisHandle,
|
|
||||||
out dwReceivedBytes,
|
|
||||||
out fCompletionExpected);
|
|
||||||
if (!fCompletionExpected)
|
|
||||||
{
|
|
||||||
CompleteReadWebSockets(hr, dwReceivedBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _readWebSocketsOperation;
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe IISAwaitable WriteWebSocketsFromIISAsync(ReadOnlySequence<byte> buffer)
|
|
||||||
{
|
|
||||||
var fCompletionExpected = false;
|
|
||||||
var hr = 0;
|
|
||||||
var nChunks = 0;
|
|
||||||
|
|
||||||
if (buffer.IsSingleSegment)
|
|
||||||
{
|
|
||||||
nChunks = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var memory in buffer)
|
|
||||||
{
|
|
||||||
nChunks++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer.IsSingleSegment)
|
|
||||||
{
|
|
||||||
var pDataChunks = stackalloc HttpApiTypes.HTTP_DATA_CHUNK[1];
|
|
||||||
|
|
||||||
fixed (byte* pBuffer = &MemoryMarshal.GetReference(buffer.First.Span))
|
|
||||||
{
|
|
||||||
ref var chunk = ref pDataChunks[0];
|
|
||||||
|
|
||||||
chunk.DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
|
|
||||||
chunk.fromMemory.pBuffer = (IntPtr)pBuffer;
|
|
||||||
chunk.fromMemory.BufferLength = (uint)buffer.Length;
|
|
||||||
hr = NativeMethods.HttpWebsocketsWriteBytes(_pInProcessHandler, pDataChunks, nChunks, IISAwaitable.WriteCallback, (IntPtr)_thisHandle, out fCompletionExpected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// REVIEW: Do we need to guard against this getting too big? It seems unlikely that we'd have more than say 10 chunks in real life
|
|
||||||
var pDataChunks = stackalloc HttpApiTypes.HTTP_DATA_CHUNK[nChunks];
|
|
||||||
var currentChunk = 0;
|
|
||||||
|
|
||||||
// REVIEW: We don't really need this list since the memory is already pinned with the default pool,
|
|
||||||
// but shouldn't assume the pool implementation right now. Unfortunately, this causes a heap allocation...
|
|
||||||
var handles = new MemoryHandle[nChunks];
|
|
||||||
|
|
||||||
foreach (var b in buffer)
|
|
||||||
{
|
|
||||||
ref var handle = ref handles[currentChunk];
|
|
||||||
ref var chunk = ref pDataChunks[currentChunk];
|
|
||||||
|
|
||||||
handle = b.Pin();
|
|
||||||
|
|
||||||
chunk.DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
|
|
||||||
chunk.fromMemory.BufferLength = (uint)b.Length;
|
|
||||||
chunk.fromMemory.pBuffer = (IntPtr)handle.Pointer;
|
|
||||||
|
|
||||||
currentChunk++;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr = NativeMethods.HttpWebsocketsWriteBytes(_pInProcessHandler, pDataChunks, nChunks, IISAwaitable.WriteCallback, (IntPtr)_thisHandle, out fCompletionExpected);
|
|
||||||
|
|
||||||
foreach (var handle in handles)
|
|
||||||
{
|
|
||||||
handle.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fCompletionExpected)
|
|
||||||
{
|
|
||||||
CompleteWriteWebSockets(hr, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _writeWebSocketsOperation;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void CompleteWriteWebSockets(int hr, int cbBytes)
|
|
||||||
{
|
|
||||||
_writeWebSocketsOperation.Complete(hr, cbBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void CompleteReadWebSockets(int hr, int cbBytes)
|
|
||||||
{
|
|
||||||
_readWebSocketsOperation.Complete(hr, cbBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ReadWebSockets()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var memory = Input.Writer.GetMemory();
|
|
||||||
_inputHandle = memory.Pin();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int read = 0;
|
|
||||||
read = await ReadWebSocketsFromIISAsync(memory.Length);
|
|
||||||
|
|
||||||
if (read == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Input.Writer.Advance(read);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_inputHandle.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await Input.Writer.FlushAsync();
|
|
||||||
|
|
||||||
if (result.IsCompleted || result.IsCanceled)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Input.Writer.Complete();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Input.Writer.Complete(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task WriteWebSockets()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var result = await Output.Reader.ReadAsync();
|
|
||||||
|
|
||||||
var buffer = result.Buffer;
|
|
||||||
var consumed = buffer.End;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!buffer.IsEmpty)
|
|
||||||
{
|
|
||||||
await WriteWebSocketsFromIISAsync(buffer);
|
|
||||||
}
|
|
||||||
else if (result.IsCompleted)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Output.Reader.AdvanceTo(consumed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Output.Reader.Complete();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Output.Reader.Complete(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,514 +0,0 @@
|
||||||
// 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.Buffers;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Net;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Security.Principal;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.HttpSys.Internal;
|
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
internal abstract partial class IISHttpContext : NativeRequestContext, IDisposable
|
|
||||||
{
|
|
||||||
private const int MinAllocBufferSize = 2048;
|
|
||||||
private const int PauseWriterThreshold = 65536;
|
|
||||||
private const int ResumeWriterTheshold = PauseWriterThreshold / 2;
|
|
||||||
|
|
||||||
|
|
||||||
protected readonly IntPtr _pInProcessHandler;
|
|
||||||
|
|
||||||
private readonly IISOptions _options;
|
|
||||||
|
|
||||||
private bool _reading; // To know whether we are currently in a read operation.
|
|
||||||
private volatile bool _hasResponseStarted;
|
|
||||||
|
|
||||||
private int _statusCode;
|
|
||||||
private string _reasonPhrase;
|
|
||||||
private readonly object _onStartingSync = new object();
|
|
||||||
private readonly object _onCompletedSync = new object();
|
|
||||||
private readonly object _stateSync = new object();
|
|
||||||
protected readonly object _createReadWriteBodySync = new object();
|
|
||||||
|
|
||||||
protected Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
|
|
||||||
protected Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted;
|
|
||||||
|
|
||||||
protected Exception _applicationException;
|
|
||||||
private readonly MemoryPool<byte> _memoryPool;
|
|
||||||
private readonly IISHttpServer _server;
|
|
||||||
|
|
||||||
private GCHandle _thisHandle;
|
|
||||||
private MemoryHandle _inputHandle;
|
|
||||||
private IISAwaitable _operation = new IISAwaitable();
|
|
||||||
protected Task _processBodiesTask;
|
|
||||||
|
|
||||||
protected int _requestAborted;
|
|
||||||
|
|
||||||
private const string NtlmString = "NTLM";
|
|
||||||
private const string NegotiateString = "Negotiate";
|
|
||||||
private const string BasicString = "Basic";
|
|
||||||
|
|
||||||
internal unsafe IISHttpContext(MemoryPool<byte> memoryPool, IntPtr pInProcessHandler, IISOptions options, IISHttpServer server)
|
|
||||||
: base((HttpApiTypes.HTTP_REQUEST*)NativeMethods.HttpGetRawRequest(pInProcessHandler))
|
|
||||||
{
|
|
||||||
_memoryPool = memoryPool;
|
|
||||||
_pInProcessHandler = pInProcessHandler;
|
|
||||||
_options = options;
|
|
||||||
_server = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Version HttpVersion { get; set; }
|
|
||||||
public string Scheme { get; set; }
|
|
||||||
public string Method { get; set; }
|
|
||||||
public string PathBase { get; set; }
|
|
||||||
public string Path { get; set; }
|
|
||||||
public string QueryString { get; set; }
|
|
||||||
public string RawTarget { get; set; }
|
|
||||||
public CancellationToken RequestAborted { get; set; }
|
|
||||||
public bool HasResponseStarted => _hasResponseStarted;
|
|
||||||
public IPAddress RemoteIpAddress { get; set; }
|
|
||||||
public int RemotePort { get; set; }
|
|
||||||
public IPAddress LocalIpAddress { get; set; }
|
|
||||||
public int LocalPort { get; set; }
|
|
||||||
public string RequestConnectionId { get; set; }
|
|
||||||
public string TraceIdentifier { get; set; }
|
|
||||||
public ClaimsPrincipal User { get; set; }
|
|
||||||
internal WindowsPrincipal WindowsUser { get; set; }
|
|
||||||
public Stream RequestBody { get; set; }
|
|
||||||
public Stream ResponseBody { get; set; }
|
|
||||||
public Pipe Input { get; set; }
|
|
||||||
public OutputProducer Output { get; set; }
|
|
||||||
|
|
||||||
public IHeaderDictionary RequestHeaders { get; set; }
|
|
||||||
public IHeaderDictionary ResponseHeaders { get; set; }
|
|
||||||
private HeaderCollection HttpResponseHeaders { get; set; }
|
|
||||||
internal HttpApiTypes.HTTP_VERB KnownMethod { get; private set; }
|
|
||||||
|
|
||||||
protected void InitializeContext()
|
|
||||||
{
|
|
||||||
_thisHandle = GCHandle.Alloc(this);
|
|
||||||
|
|
||||||
NativeMethods.HttpSetManagedContext(_pInProcessHandler, (IntPtr)_thisHandle);
|
|
||||||
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cookedUrl = GetCookedUrl();
|
|
||||||
QueryString = cookedUrl.GetQueryString() ?? string.Empty;
|
|
||||||
|
|
||||||
RequestHeaders = new RequestHeaders(this);
|
|
||||||
HttpResponseHeaders = new HeaderCollection();
|
|
||||||
ResponseHeaders = HttpResponseHeaders;
|
|
||||||
|
|
||||||
if (_options.ForwardWindowsAuthentication)
|
|
||||||
{
|
|
||||||
WindowsUser = GetWindowsPrincipal();
|
|
||||||
if (_options.AutomaticAuthentication)
|
|
||||||
{
|
|
||||||
User = WindowsUser;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ResetFeatureCollection();
|
|
||||||
|
|
||||||
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));
|
|
||||||
Output = new OutputProducer(pipe);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int StatusCode
|
|
||||||
{
|
|
||||||
get { return _statusCode; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_hasResponseStarted)
|
|
||||||
{
|
|
||||||
ThrowResponseAlreadyStartedException(nameof(StatusCode));
|
|
||||||
}
|
|
||||||
_statusCode = (ushort)value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ReasonPhrase
|
|
||||||
{
|
|
||||||
get { return _reasonPhrase; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_hasResponseStarted)
|
|
||||||
{
|
|
||||||
ThrowResponseAlreadyStartedException(nameof(ReasonPhrase));
|
|
||||||
}
|
|
||||||
_reasonPhrase = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal IISHttpServer Server
|
|
||||||
{
|
|
||||||
get { return _server; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InitializeResponseAwaited()
|
|
||||||
{
|
|
||||||
await FireOnStarting();
|
|
||||||
|
|
||||||
if (_applicationException != null)
|
|
||||||
{
|
|
||||||
ThrowResponseAbortedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
await ProduceStart(appCompleted: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThrowResponseAbortedException()
|
|
||||||
{
|
|
||||||
throw new ObjectDisposedException("Unhandled application exception", _applicationException);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProduceStart(bool appCompleted)
|
|
||||||
{
|
|
||||||
if (_hasResponseStarted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_hasResponseStarted = true;
|
|
||||||
|
|
||||||
SendResponseHeaders(appCompleted);
|
|
||||||
|
|
||||||
// On first flush for websockets, we need to flush the headers such that
|
|
||||||
// IIS will know that an upgrade occured.
|
|
||||||
// If we don't have anything on the Output pipe, the TryRead in ReadAndWriteLoopAsync
|
|
||||||
// will fail and we will signal the upgradeTcs that we are upgrading. However, we still
|
|
||||||
// didn't flush. To fix this, we flush 0 bytes right after writing the headers.
|
|
||||||
Task flushTask;
|
|
||||||
lock (_stateSync)
|
|
||||||
{
|
|
||||||
DisableReads();
|
|
||||||
flushTask = Output.FlushAsync();
|
|
||||||
}
|
|
||||||
await flushTask;
|
|
||||||
|
|
||||||
StartProcessingRequestAndResponseBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Task ProduceEnd()
|
|
||||||
{
|
|
||||||
if (_applicationException != null)
|
|
||||||
{
|
|
||||||
if (_hasResponseStarted)
|
|
||||||
{
|
|
||||||
// We can no longer change the response, so we simply close the connection.
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the request was rejected, the error state has already been set by SetBadRequestState and
|
|
||||||
// that should take precedence.
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 500 Internal Server Error
|
|
||||||
SetErrorResponseHeaders(statusCode: StatusCodes.Status500InternalServerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_hasResponseStarted)
|
|
||||||
{
|
|
||||||
return ProduceEndAwaited();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetErrorResponseHeaders(int statusCode)
|
|
||||||
{
|
|
||||||
StatusCode = statusCode;
|
|
||||||
ReasonPhrase = string.Empty;
|
|
||||||
HttpResponseHeaders.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProduceEndAwaited()
|
|
||||||
{
|
|
||||||
if (_hasResponseStarted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_hasResponseStarted = true;
|
|
||||||
|
|
||||||
SendResponseHeaders(appCompleted: true);
|
|
||||||
StartProcessingRequestAndResponseBody();
|
|
||||||
|
|
||||||
Task flushAsync;
|
|
||||||
|
|
||||||
lock (_stateSync)
|
|
||||||
{
|
|
||||||
DisableReads();
|
|
||||||
flushAsync = Output.FlushAsync();
|
|
||||||
}
|
|
||||||
await flushAsync;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe void SendResponseHeaders(bool appCompleted)
|
|
||||||
{
|
|
||||||
// Verifies we have sent the statuscode before writing a header
|
|
||||||
var reasonPhrase = string.IsNullOrEmpty(ReasonPhrase) ? ReasonPhrases.GetReasonPhrase(StatusCode) : ReasonPhrase;
|
|
||||||
|
|
||||||
// This copies data into the underlying buffer
|
|
||||||
NativeMethods.HttpSetResponseStatusCode(_pInProcessHandler, (ushort)StatusCode, reasonPhrase);
|
|
||||||
|
|
||||||
HttpResponseHeaders.IsReadOnly = true;
|
|
||||||
foreach (var headerPair in HttpResponseHeaders)
|
|
||||||
{
|
|
||||||
var headerValues = headerPair.Value;
|
|
||||||
var knownHeaderIndex = HttpApiTypes.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerPair.Key);
|
|
||||||
if (knownHeaderIndex == -1)
|
|
||||||
{
|
|
||||||
var headerNameBytes = Encoding.UTF8.GetBytes(headerPair.Key);
|
|
||||||
for (var i = 0; i < headerValues.Count; i++)
|
|
||||||
{
|
|
||||||
var headerValueBytes = Encoding.UTF8.GetBytes(headerValues[i]);
|
|
||||||
fixed (byte* pHeaderName = headerNameBytes)
|
|
||||||
{
|
|
||||||
fixed (byte* pHeaderValue = headerValueBytes)
|
|
||||||
{
|
|
||||||
NativeMethods.HttpResponseSetUnknownHeader(_pInProcessHandler, pHeaderName, pHeaderValue, (ushort)headerValueBytes.Length, fReplace: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (var i = 0; i < headerValues.Count; i++)
|
|
||||||
{
|
|
||||||
var headerValueBytes = Encoding.UTF8.GetBytes(headerValues[i]);
|
|
||||||
fixed (byte* pHeaderValue = headerValueBytes)
|
|
||||||
{
|
|
||||||
NativeMethods.HttpResponseSetKnownHeader(_pInProcessHandler, knownHeaderIndex, pHeaderValue, (ushort)headerValueBytes.Length, fReplace: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Abort()
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Task<bool> ProcessRequestAsync();
|
|
||||||
|
|
||||||
public void OnStarting(Func<object, Task> callback, object state)
|
|
||||||
{
|
|
||||||
lock (_onStartingSync)
|
|
||||||
{
|
|
||||||
if (_hasResponseStarted)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Response already started");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_onStarting == null)
|
|
||||||
{
|
|
||||||
_onStarting = new Stack<KeyValuePair<Func<object, Task>, object>>();
|
|
||||||
}
|
|
||||||
_onStarting.Push(new KeyValuePair<Func<object, Task>, object>(callback, state));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnCompleted(Func<object, Task> callback, object state)
|
|
||||||
{
|
|
||||||
lock (_onCompletedSync)
|
|
||||||
{
|
|
||||||
if (_onCompleted == null)
|
|
||||||
{
|
|
||||||
_onCompleted = new Stack<KeyValuePair<Func<object, Task>, object>>();
|
|
||||||
}
|
|
||||||
_onCompleted.Push(new KeyValuePair<Func<object, Task>, object>(callback, state));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task FireOnStarting()
|
|
||||||
{
|
|
||||||
Stack<KeyValuePair<Func<object, Task>, object>> onStarting = null;
|
|
||||||
lock (_onStartingSync)
|
|
||||||
{
|
|
||||||
onStarting = _onStarting;
|
|
||||||
_onStarting = null;
|
|
||||||
}
|
|
||||||
if (onStarting != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var entry in onStarting)
|
|
||||||
{
|
|
||||||
await entry.Key.Invoke(entry.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ReportApplicationError(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task FireOnCompleted()
|
|
||||||
{
|
|
||||||
Stack<KeyValuePair<Func<object, Task>, object>> onCompleted = null;
|
|
||||||
lock (_onCompletedSync)
|
|
||||||
{
|
|
||||||
onCompleted = _onCompleted;
|
|
||||||
_onCompleted = null;
|
|
||||||
}
|
|
||||||
if (onCompleted != null)
|
|
||||||
{
|
|
||||||
foreach (var entry in onCompleted)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await entry.Key.Invoke(entry.Value);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ReportApplicationError(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void ReportApplicationError(Exception ex)
|
|
||||||
{
|
|
||||||
if (_applicationException == null)
|
|
||||||
{
|
|
||||||
_applicationException = ex;
|
|
||||||
}
|
|
||||||
else if (_applicationException is AggregateException)
|
|
||||||
{
|
|
||||||
_applicationException = new AggregateException(_applicationException, ex).Flatten();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_applicationException = new AggregateException(_applicationException, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PostCompletion(NativeMethods.REQUEST_NOTIFICATION_STATUS requestNotificationStatus)
|
|
||||||
{
|
|
||||||
Debug.Assert(!_operation.HasContinuation, "Pending async operation!");
|
|
||||||
|
|
||||||
NativeMethods.HttpSetCompletionStatus(_pInProcessHandler, requestNotificationStatus);
|
|
||||||
NativeMethods.HttpPostCompletion(_pInProcessHandler, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void IndicateCompletion(NativeMethods.REQUEST_NOTIFICATION_STATUS notificationStatus)
|
|
||||||
{
|
|
||||||
NativeMethods.HttpIndicateCompletion(_pInProcessHandler, notificationStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void OnAsyncCompletion(int hr, int cbBytes)
|
|
||||||
{
|
|
||||||
// Must acquire the _stateSync here as anytime we call complete, we need to hold the lock
|
|
||||||
// to avoid races with cancellation.
|
|
||||||
Action continuation;
|
|
||||||
lock (_stateSync)
|
|
||||||
{
|
|
||||||
_reading = false;
|
|
||||||
continuation = _operation.GetCompletion(hr, cbBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
continuation?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool disposedValue = false; // To detect redundant calls
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposedValue)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// TODO: dispose managed state (managed objects).
|
|
||||||
_thisHandle.Free();
|
|
||||||
}
|
|
||||||
if (WindowsUser?.Identity is WindowsIdentity wi)
|
|
||||||
{
|
|
||||||
wi.Dispose();
|
|
||||||
}
|
|
||||||
disposedValue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This code added to correctly implement the disposable pattern.
|
|
||||||
public override void Dispose()
|
|
||||||
{
|
|
||||||
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
|
||||||
Dispose(disposing: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThrowResponseAlreadyStartedException(string value)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Response already started");
|
|
||||||
}
|
|
||||||
|
|
||||||
private WindowsPrincipal GetWindowsPrincipal()
|
|
||||||
{
|
|
||||||
NativeMethods.HttpGetAuthenticationInformation(_pInProcessHandler, out var authenticationType, out var token);
|
|
||||||
|
|
||||||
if (token != IntPtr.Zero && authenticationType != null)
|
|
||||||
{
|
|
||||||
if ((authenticationType.Equals(NtlmString, StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| authenticationType.Equals(NegotiateString, StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| authenticationType.Equals(BasicString, StringComparison.OrdinalIgnoreCase)))
|
|
||||||
{
|
|
||||||
return new WindowsPrincipal(new WindowsIdentity(token, authenticationType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
// 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.Buffers;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
internal class IISHttpContextOfT<TContext> : IISHttpContext
|
|
||||||
{
|
|
||||||
private readonly IHttpApplication<TContext> _application;
|
|
||||||
|
|
||||||
public IISHttpContextOfT(MemoryPool<byte> memoryPool, IHttpApplication<TContext> application, IntPtr pInProcessHandler, IISOptions options, IISHttpServer server)
|
|
||||||
: base(memoryPool, pInProcessHandler, options, server)
|
|
||||||
{
|
|
||||||
_application = application;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<bool> ProcessRequestAsync()
|
|
||||||
{
|
|
||||||
InitializeContext();
|
|
||||||
|
|
||||||
var context = default(TContext);
|
|
||||||
var success = true;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
context = _application.CreateContext(this);
|
|
||||||
await _application.ProcessRequestAsync(context);
|
|
||||||
// TODO Verification of Response
|
|
||||||
//if (Volatile.Read(ref _requestAborted) == 0)
|
|
||||||
//{
|
|
||||||
// VerifyResponseContentLength();
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ReportApplicationError(ex);
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (!HasResponseStarted && _applicationException == null && _onStarting != null)
|
|
||||||
{
|
|
||||||
await FireOnStarting();
|
|
||||||
// Dispose
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_onCompleted != null)
|
|
||||||
{
|
|
||||||
await FireOnCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Volatile.Read(ref _requestAborted) == 0)
|
|
||||||
{
|
|
||||||
await ProduceEnd();
|
|
||||||
}
|
|
||||||
else if (!HasResponseStarted)
|
|
||||||
{
|
|
||||||
// If the request was aborted and no response was sent, there's no
|
|
||||||
// meaningful status code to log.
|
|
||||||
StatusCode = 0;
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_application.DisposeContext(context, _applicationException);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// TODO Log this
|
|
||||||
_applicationException = _applicationException ?? ex;
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// The app is finished and there should be nobody writing to the response pipe
|
|
||||||
Output.Dispose();
|
|
||||||
|
|
||||||
// The app is finished and there should be nobody reading from the request pipe
|
|
||||||
Input.Reader.Complete();
|
|
||||||
|
|
||||||
Task processBodiesTask;
|
|
||||||
lock (_createReadWriteBodySync)
|
|
||||||
{
|
|
||||||
processBodiesTask = _processBodiesTask;
|
|
||||||
}
|
|
||||||
await processBodiesTask;
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
// 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;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
internal class IISHttpRequestBody : Stream
|
|
||||||
{
|
|
||||||
private readonly IISHttpContext _httpContext;
|
|
||||||
|
|
||||||
public IISHttpRequestBody(IISHttpContext httpContext)
|
|
||||||
{
|
|
||||||
_httpContext = httpContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanRead => true;
|
|
||||||
|
|
||||||
public override bool CanSeek => false;
|
|
||||||
|
|
||||||
public override bool CanWrite => false;
|
|
||||||
|
|
||||||
public override long Length => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
|
|
||||||
|
|
||||||
public override void Flush()
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
return ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override unsafe Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var memory = new Memory<byte>(buffer, offset, count);
|
|
||||||
|
|
||||||
return _httpContext.ReadAsync(memory, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetLength(long value)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe override void Write(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
// 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;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
internal class IISHttpResponseBody : Stream
|
|
||||||
{
|
|
||||||
private readonly IISHttpContext _httpContext;
|
|
||||||
|
|
||||||
public IISHttpResponseBody(IISHttpContext httpContext)
|
|
||||||
{
|
|
||||||
_httpContext = httpContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanRead => false;
|
|
||||||
|
|
||||||
public override bool CanSeek => false;
|
|
||||||
|
|
||||||
public override bool CanWrite => true;
|
|
||||||
|
|
||||||
public override long Length => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
|
|
||||||
|
|
||||||
public override void Flush()
|
|
||||||
{
|
|
||||||
FlushAsync(CancellationToken.None).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 unsafe override void Write(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override unsafe Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return _httpContext.WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return _httpContext.FlushAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,229 +0,0 @@
|
||||||
// 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.Buffers;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
private IISContextFactory _iisContextFactory;
|
|
||||||
private readonly MemoryPool<byte> _memoryPool = new SlabMemoryPool();
|
|
||||||
private GCHandle _httpServerHandle;
|
|
||||||
private readonly IApplicationLifetime _applicationLifetime;
|
|
||||||
private readonly IAuthenticationSchemeProvider _authentication;
|
|
||||||
private readonly IISOptions _options;
|
|
||||||
|
|
||||||
private volatile int _stopping;
|
|
||||||
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;
|
|
||||||
_authentication = authentication;
|
|
||||||
_options = options.Value;
|
|
||||||
|
|
||||||
if (_options.ForwardWindowsAuthentication)
|
|
||||||
{
|
|
||||||
authentication.AddScheme(new AuthenticationScheme(IISDefaults.AuthenticationScheme, _options.AuthenticationDisplayName, typeof(IISServerAuthenticationHandler)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_httpServerHandle = GCHandle.Alloc(this);
|
|
||||||
|
|
||||||
_iisContextFactory = new IISContextFactory<TContext>(_memoryPool, application, _options, this);
|
|
||||||
|
|
||||||
// Start the server by registering the callback
|
|
||||||
NativeMethods.HttpRegisterCallbacks(_requestHandler, _shutdownHandler, _onAsyncCompletion, (IntPtr)_httpServerHandle, (IntPtr)_httpServerHandle);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
void RegisterCancelation()
|
|
||||||
{
|
|
||||||
cancellationToken.Register(() =>
|
|
||||||
{
|
|
||||||
NativeMethods.HttpStopCallsIntoManaged();
|
|
||||||
_shutdownSignal.TrySetResult(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (Interlocked.Exchange(ref _stopping, 1) == 1)
|
|
||||||
{
|
|
||||||
RegisterCancelation();
|
|
||||||
|
|
||||||
return _shutdownSignal.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First call back into native saying "DON'T SEND ME ANY MORE REQUESTS"
|
|
||||||
NativeMethods.HttpStopIncomingRequests();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Wait for active requests to drain
|
|
||||||
if (_outstandingRequests > 0)
|
|
||||||
{
|
|
||||||
RegisterCancelation();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We have drained all requests. Block any callbacks into managed at this point.
|
|
||||||
NativeMethods.HttpStopCallsIntoManaged();
|
|
||||||
_shutdownSignal.TrySetResult(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_shutdownSignal.TrySetException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _shutdownSignal.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_stopping = 1;
|
|
||||||
|
|
||||||
// Block any more calls into managed from native as we are unloading.
|
|
||||||
NativeMethods.HttpStopCallsIntoManaged();
|
|
||||||
_shutdownSignal.TrySetResult(null);
|
|
||||||
|
|
||||||
if (_httpServerHandle.IsAllocated)
|
|
||||||
{
|
|
||||||
_httpServerHandle.Free();
|
|
||||||
}
|
|
||||||
|
|
||||||
_memoryPool.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NativeMethods.REQUEST_NOTIFICATION_STATUS HandleRequest(IntPtr pInProcessHandler, IntPtr pvRequestContext)
|
|
||||||
{
|
|
||||||
// Unwrap the server so we can create an http context and process the request
|
|
||||||
var server = (IISHttpServer)GCHandle.FromIntPtr(pvRequestContext).Target;
|
|
||||||
Interlocked.Increment(ref server._outstandingRequests);
|
|
||||||
|
|
||||||
var context = server._iisContextFactory.CreateHttpContext(pInProcessHandler);
|
|
||||||
|
|
||||||
ThreadPool.QueueUserWorkItem(state => _ = HandleRequest((IISHttpContext)state), context);
|
|
||||||
|
|
||||||
return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_PENDING;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task HandleRequest(IISHttpContext context)
|
|
||||||
{
|
|
||||||
var result = await context.ProcessRequestAsync();
|
|
||||||
CompleteRequest(context, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool HandleShutdown(IntPtr pvRequestContext)
|
|
||||||
{
|
|
||||||
var server = (IISHttpServer)GCHandle.FromIntPtr(pvRequestContext).Target;
|
|
||||||
server._applicationLifetime.StopApplication();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NativeMethods.REQUEST_NOTIFICATION_STATUS OnAsyncCompletion(IntPtr pvManagedHttpContext, int hr, int bytes)
|
|
||||||
{
|
|
||||||
var context = (IISHttpContext)GCHandle.FromIntPtr(pvManagedHttpContext).Target;
|
|
||||||
context.OnAsyncCompletion(hr, bytes);
|
|
||||||
return NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_PENDING;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CompleteRequest(IISHttpContext context, bool result)
|
|
||||||
{
|
|
||||||
// Post completion after completing the request to resume the state machine
|
|
||||||
context.PostCompletion(ConvertRequestCompletionResults(result));
|
|
||||||
|
|
||||||
if (Interlocked.Decrement(ref context.Server._outstandingRequests) == 0 && context.Server.Stopping)
|
|
||||||
{
|
|
||||||
// All requests have been drained.
|
|
||||||
NativeMethods.HttpStopCallsIntoManaged();
|
|
||||||
context.Server._shutdownSignal.TrySetResult(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispose the context
|
|
||||||
context.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NativeMethods.REQUEST_NOTIFICATION_STATUS ConvertRequestCompletionResults(bool success)
|
|
||||||
{
|
|
||||||
return success ? NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_CONTINUE
|
|
||||||
: NativeMethods.REQUEST_NOTIFICATION_STATUS.RQ_NOTIFICATION_FINISH_REQUEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class IISContextFactory<T> : IISContextFactory
|
|
||||||
{
|
|
||||||
private readonly IHttpApplication<T> _application;
|
|
||||||
private readonly MemoryPool<byte> _memoryPool;
|
|
||||||
private readonly IISOptions _options;
|
|
||||||
private readonly IISHttpServer _server;
|
|
||||||
|
|
||||||
public IISContextFactory(MemoryPool<byte> memoryPool, IHttpApplication<T> application, IISOptions options, IISHttpServer server)
|
|
||||||
{
|
|
||||||
_application = application;
|
|
||||||
_memoryPool = memoryPool;
|
|
||||||
_options = options;
|
|
||||||
_server = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IISHttpContext CreateHttpContext(IntPtr pInProcessHandler)
|
|
||||||
{
|
|
||||||
return new IISHttpContextOfT<T>(_memoryPool, _application, pInProcessHandler, _options, _server);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~IISHttpServer()
|
|
||||||
{
|
|
||||||
// If this finalize is invoked, try our best to block all calls into managed.
|
|
||||||
NativeMethods.HttpStopCallsIntoManaged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Over engineering to avoid allocations...
|
|
||||||
internal interface IISContextFactory
|
|
||||||
{
|
|
||||||
IISHttpContext CreateHttpContext(IntPtr pInProcessHandler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
// 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;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
public class IISServerAuthenticationHandler : IAuthenticationHandler
|
|
||||||
{
|
|
||||||
private HttpContext _context;
|
|
||||||
private IISHttpContext _iisHttpContext;
|
|
||||||
|
|
||||||
internal AuthenticationScheme Scheme { get; private set; }
|
|
||||||
|
|
||||||
public Task<AuthenticateResult> AuthenticateAsync()
|
|
||||||
{
|
|
||||||
var user = _iisHttpContext.WindowsUser;
|
|
||||||
if (user != null && user.Identity != null && user.Identity.IsAuthenticated)
|
|
||||||
{
|
|
||||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(user, Scheme.Name)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Task.FromResult(AuthenticateResult.NoResult());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task ChallengeAsync(AuthenticationProperties properties)
|
|
||||||
{
|
|
||||||
// We would normally set the www-authenticate header here, but IIS does that for us.
|
|
||||||
_context.Response.StatusCode = 401;
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task ForbidAsync(AuthenticationProperties properties)
|
|
||||||
{
|
|
||||||
_context.Response.StatusCode = 403;
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
|
|
||||||
{
|
|
||||||
_iisHttpContext = context.Features.Get<IISHttpContext>();
|
|
||||||
if (_iisHttpContext == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("No IISHttpContext found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Scheme = scheme;
|
|
||||||
_context = context;
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
// 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.IISIntegration
|
|
||||||
{
|
|
||||||
internal static class IISServerConstants
|
|
||||||
{
|
|
||||||
internal const int HResultCancelIO = -2147023901;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
// 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.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
internal class IISServerSetupFilter : IStartupFilter
|
|
||||||
{
|
|
||||||
private string _virtualPath;
|
|
||||||
|
|
||||||
public IISServerSetupFilter(string virtualPath)
|
|
||||||
{
|
|
||||||
_virtualPath = virtualPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
|
|
||||||
{
|
|
||||||
return app =>
|
|
||||||
{
|
|
||||||
var server = app.ApplicationServices.GetService<IServer>();
|
|
||||||
if (server?.GetType() != typeof(IISHttpServer))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Application is running inside IIS process but is not configured to use IIS server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UsePathBase(_virtualPath);
|
|
||||||
next(app);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,149 +0,0 @@
|
||||||
// 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.Buffers;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration
|
|
||||||
{
|
|
||||||
internal class OutputProducer
|
|
||||||
{
|
|
||||||
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
|
|
||||||
|
|
||||||
// This locks access to to all of the below fields
|
|
||||||
private readonly object _contextLock = new object();
|
|
||||||
|
|
||||||
private ValueTask<FlushResult> _flushTask;
|
|
||||||
private bool _completed = false;
|
|
||||||
|
|
||||||
private readonly Pipe _pipe;
|
|
||||||
|
|
||||||
// https://github.com/dotnet/corefxlab/issues/1334
|
|
||||||
// Pipelines don't support multiple awaiters on flush
|
|
||||||
// this is temporary until it does
|
|
||||||
private TaskCompletionSource<object> _flushTcs;
|
|
||||||
private readonly object _flushLock = new object();
|
|
||||||
private Action _flushCompleted;
|
|
||||||
|
|
||||||
public OutputProducer(Pipe pipe)
|
|
||||||
{
|
|
||||||
_pipe = pipe;
|
|
||||||
_flushCompleted = OnFlushCompleted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PipeReader Reader => _pipe.Reader;
|
|
||||||
|
|
||||||
public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
|
|
||||||
{
|
|
||||||
return WriteAsync(_emptyData, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
lock (_contextLock)
|
|
||||||
{
|
|
||||||
if (_completed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_completed = true;
|
|
||||||
_pipe.Writer.Complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Abort(Exception error)
|
|
||||||
{
|
|
||||||
lock (_contextLock)
|
|
||||||
{
|
|
||||||
if (_completed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_completed = true;
|
|
||||||
|
|
||||||
_pipe.Reader.CancelPendingRead();
|
|
||||||
_pipe.Writer.Complete(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task WriteAsync(
|
|
||||||
ReadOnlyMemory<byte> buffer,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
lock (_contextLock)
|
|
||||||
{
|
|
||||||
if (_completed)
|
|
||||||
{
|
|
||||||
throw new ObjectDisposedException("Response is already completed");
|
|
||||||
}
|
|
||||||
|
|
||||||
_pipe.Writer.Write(buffer.Span);
|
|
||||||
}
|
|
||||||
|
|
||||||
return FlushAsync(_pipe.Writer, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task FlushAsync(PipeWriter pipeWriter,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var awaitable = pipeWriter.FlushAsync(cancellationToken);
|
|
||||||
if (awaitable.IsCompleted)
|
|
||||||
{
|
|
||||||
// The flush task can't fail today
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
return FlushAsyncAwaited(awaitable, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task FlushAsyncAwaited(ValueTask<FlushResult> awaitable, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
// https://github.com/dotnet/corefxlab/issues/1334
|
|
||||||
// Since the flush awaitable doesn't currently support multiple awaiters
|
|
||||||
// we need to use a task to track the callbacks.
|
|
||||||
// All awaiters get the same task
|
|
||||||
lock (_flushLock)
|
|
||||||
{
|
|
||||||
_flushTask = awaitable;
|
|
||||||
if (_flushTcs == null || _flushTcs.Task.IsCompleted)
|
|
||||||
{
|
|
||||||
_flushTcs = new TaskCompletionSource<object>();
|
|
||||||
|
|
||||||
_flushTask.GetAwaiter().OnCompleted(_flushCompleted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _flushTcs.Task;
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_completed = true;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFlushCompleted()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_flushTask.GetAwaiter().GetResult();
|
|
||||||
_flushTcs.TrySetResult(null);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_flushTcs.TrySetResult(exception);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_flushTask = default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -40,13 +40,6 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
return hostBuilder;
|
return hostBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if in process
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && NativeMethods.IsAspNetCoreModuleLoaded())
|
|
||||||
{
|
|
||||||
|
|
||||||
return SetupInProcessServer(hostBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
var port = hostBuilder.GetSetting(ServerPort) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPort}");
|
var port = hostBuilder.GetSetting(ServerPort) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPort}");
|
||||||
var path = hostBuilder.GetSetting(ServerPath) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPath}");
|
var path = hostBuilder.GetSetting(ServerPath) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPath}");
|
||||||
var pairingToken = hostBuilder.GetSetting(PairingToken) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{PairingToken}");
|
var pairingToken = hostBuilder.GetSetting(PairingToken) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{PairingToken}");
|
||||||
|
|
@ -107,23 +100,5 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
|
|
||||||
return hostBuilder;
|
return hostBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IWebHostBuilder SetupInProcessServer(IWebHostBuilder hostBuilder)
|
|
||||||
{
|
|
||||||
hostBuilder.UseSetting(nameof(UseIISIntegration), "true");
|
|
||||||
hostBuilder.CaptureStartupErrors(true);
|
|
||||||
|
|
||||||
var iisConfigData = NativeMethods.HttpGetApplicationProperties();
|
|
||||||
hostBuilder.UseContentRoot(iisConfigData.pwzFullApplicationPath);
|
|
||||||
return hostBuilder.ConfigureServices(
|
|
||||||
services => {
|
|
||||||
services.AddSingleton<IServer, IISHttpServer>();
|
|
||||||
services.AddSingleton<IStartupFilter>(new IISServerSetupFilter(iisConfigData.pwzVirtualApplicationPath));
|
|
||||||
services.AddAuthenticationCore();
|
|
||||||
services.Configure<IISOptions>(
|
|
||||||
options => { options.ForwardWindowsAuthentication = iisConfigData.fWindowsAuthEnabled || iisConfigData.fBasicAuthEnabled; }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
#if NET461
|
|
||||||
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|
||||||
{
|
|
||||||
[Collection(IISTestSiteCollection.Name)]
|
|
||||||
public class AuthenticationTests
|
|
||||||
{
|
|
||||||
private readonly IISTestSiteFixture _fixture;
|
|
||||||
public AuthenticationTests(IISTestSiteFixture fixture)
|
|
||||||
{
|
|
||||||
_fixture = fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task Authentication_InProcess_IISExpressAsync()
|
|
||||||
{
|
|
||||||
var response = await _fixture.Client.GetAsync("/AuthenticationAnonymous");
|
|
||||||
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
||||||
Assert.Equal("Anonymous?True", responseText);
|
|
||||||
|
|
||||||
response = await _fixture.Client.GetAsync("/AuthenticationRestricted");
|
|
||||||
responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
|
||||||
Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString());
|
|
||||||
Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString());
|
|
||||||
|
|
||||||
response = await _fixture.Client.GetAsync("/AuthenticationRestrictedNTLM");
|
|
||||||
responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
|
||||||
Assert.Contains("NTLM", response.Headers.WwwAuthenticate.ToString());
|
|
||||||
// Note we can't restrict a challenge to a specific auth type, the native auth modules always add themselves.
|
|
||||||
Assert.Contains("Negotiate", response.Headers.WwwAuthenticate.ToString());
|
|
||||||
|
|
||||||
response = await _fixture.Client.GetAsync("/AuthenticationForbidden");
|
|
||||||
responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
|
|
||||||
|
|
||||||
var httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true };
|
|
||||||
var httpClient = _fixture.DeploymentResult.CreateHttpClient(httpClientHandler);
|
|
||||||
|
|
||||||
response = await httpClient.GetAsync("/AuthenticationAnonymous");
|
|
||||||
responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
||||||
Assert.Equal("Anonymous?True", responseText);
|
|
||||||
|
|
||||||
response = await httpClient.GetAsync("/AuthenticationRestricted");
|
|
||||||
responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
||||||
Assert.NotEmpty(responseText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#elif NETCOREAPP2_0 || NETCOREAPP2_1
|
|
||||||
#else
|
|
||||||
#error Target frameworks need to be updated
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
// 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;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|
||||||
{
|
|
||||||
[Collection(IISTestSiteCollection.Name)]
|
|
||||||
public class EnvironmentVariableTests
|
|
||||||
{
|
|
||||||
private readonly IISTestSiteFixture _fixture;
|
|
||||||
|
|
||||||
public EnvironmentVariableTests(IISTestSiteFixture fixture)
|
|
||||||
{
|
|
||||||
_fixture = fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task GetUniqueEnvironmentVariable()
|
|
||||||
{
|
|
||||||
Assert.Equal("foobar", await _fixture.Client.GetStringAsync("/CheckEnvironmentVariable"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task GetLongEnvironmentVariable()
|
|
||||||
{
|
|
||||||
Assert.Equal(
|
|
||||||
"AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
|
|
||||||
"AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
|
|
||||||
"AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
|
|
||||||
"AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
|
|
||||||
"AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative" +
|
|
||||||
"AReallyLongValueThatIsGreaterThan300CharactersToForceResizeInNative",
|
|
||||||
await _fixture.Client.GetStringAsync("/CheckEnvironmentLongValueVariable"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task GetExistingEnvironmentVariable()
|
|
||||||
{
|
|
||||||
Assert.Contains(";foobarbaz", await _fixture.Client.GetStringAsync("/CheckAppendedEnvironmentVariable"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task AuthHeaderEnvironmentVariableRemoved()
|
|
||||||
{
|
|
||||||
Assert.DoesNotContain("shouldberemoved", await _fixture.Client.GetStringAsync("/CheckRemoveAuthEnvironmentVariable"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
// 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;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|
||||||
{
|
|
||||||
[Collection(IISTestSiteCollection.Name)]
|
|
||||||
public class FeatureCollectionTest
|
|
||||||
{
|
|
||||||
private readonly IISTestSiteFixture _fixture;
|
|
||||||
|
|
||||||
public FeatureCollectionTest(IISTestSiteFixture fixture)
|
|
||||||
{
|
|
||||||
_fixture = fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
[InlineData("FeatureCollectionSetRequestFeatures")]
|
|
||||||
[InlineData("FeatureCollectionSetResponseFeatures")]
|
|
||||||
[InlineData("FeatureCollectionSetConnectionFeatures")]
|
|
||||||
public async Task FeatureCollectionTest_SetHttpContextFeatures(string path)
|
|
||||||
{
|
|
||||||
Assert.Equal("Success", await _fixture.Client.GetStringAsync(path + "/path" + "?query"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
// 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;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|
||||||
{
|
|
||||||
[Collection(IISTestSiteCollection.Name)]
|
|
||||||
public class HelloWorldInProcessTests
|
|
||||||
{
|
|
||||||
private readonly IISTestSiteFixture _fixture;
|
|
||||||
|
|
||||||
public HelloWorldInProcessTests(IISTestSiteFixture fixture)
|
|
||||||
{
|
|
||||||
_fixture = fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task HelloWorld_InProcess_IISExpress_CoreClr_X64_Portable()
|
|
||||||
{
|
|
||||||
Assert.Equal("Hello World", await _fixture.Client.GetStringAsync("/HelloWorld"));
|
|
||||||
|
|
||||||
Assert.Equal("/Path??", await _fixture.Client.GetStringAsync("/HelloWorld/Path%3F%3F?query"));
|
|
||||||
|
|
||||||
Assert.Equal("?query", await _fixture.Client.GetStringAsync("/HelloWorld/Query%3F%3F?query"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
// 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.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|
||||||
{
|
|
||||||
[Collection(IISTestSiteCollection.Name)]
|
|
||||||
public class InvalidReadWriteOperationTests
|
|
||||||
{
|
|
||||||
private readonly IISTestSiteFixture _fixture;
|
|
||||||
|
|
||||||
public InvalidReadWriteOperationTests(IISTestSiteFixture fixture)
|
|
||||||
{
|
|
||||||
_fixture = fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task TestReadOffsetWorks()
|
|
||||||
{
|
|
||||||
var result = await _fixture.Client.PostAsync($"/TestReadOffsetWorks", new StringContent("Hello World"));
|
|
||||||
Assert.Equal("Hello World", await result.Content.ReadAsStringAsync());
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
[InlineData("/InvalidOffsetSmall")]
|
|
||||||
[InlineData("/InvalidOffsetLarge")]
|
|
||||||
[InlineData("/InvalidCountSmall")]
|
|
||||||
[InlineData("/InvalidCountLarge")]
|
|
||||||
[InlineData("/InvalidCountWithOffset")]
|
|
||||||
public async Task TestInvalidReadOperations(string operation)
|
|
||||||
{
|
|
||||||
var result = await _fixture.Client.GetStringAsync($"/TestInvalidReadOperations{operation}");
|
|
||||||
Assert.Equal("Success", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
[InlineData("/NullBuffer")]
|
|
||||||
[InlineData("/InvalidCountZeroRead")]
|
|
||||||
public async Task TestValidReadOperations(string operation)
|
|
||||||
{
|
|
||||||
var result = await _fixture.Client.GetStringAsync($"/TestValidReadOperations{operation}");
|
|
||||||
Assert.Equal("Success", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
[InlineData("/NullBufferPost")]
|
|
||||||
[InlineData("/InvalidCountZeroReadPost")]
|
|
||||||
public async Task TestValidReadOperationsPost(string operation)
|
|
||||||
{
|
|
||||||
var result = await _fixture.Client.PostAsync($"/TestValidReadOperations{operation}", new StringContent("hello"));
|
|
||||||
Assert.Equal("Success", await result.Content.ReadAsStringAsync());
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
[InlineData("/InvalidOffsetSmall")]
|
|
||||||
[InlineData("/InvalidOffsetLarge")]
|
|
||||||
[InlineData("/InvalidCountSmall")]
|
|
||||||
[InlineData("/InvalidCountLarge")]
|
|
||||||
[InlineData("/InvalidCountWithOffset")]
|
|
||||||
public async Task TestInvalidWriteOperations(string operation)
|
|
||||||
{
|
|
||||||
var result = await _fixture.Client.GetStringAsync($"/TestInvalidWriteOperations{operation}");
|
|
||||||
Assert.Equal("Success", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task TestValidWriteOperations()
|
|
||||||
{
|
|
||||||
var result = await _fixture.Client.GetStringAsync($"/TestValidWriteOperations/NullBuffer");
|
|
||||||
Assert.Equal("Success", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task TestValidWriteOperationsPost()
|
|
||||||
{
|
|
||||||
var result = await _fixture.Client.PostAsync($"/TestValidWriteOperations/NullBufferPost", new StringContent("hello"));
|
|
||||||
Assert.Equal("Success", await result.Content.ReadAsStringAsync());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
// 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;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|
||||||
{
|
|
||||||
[Collection(IISTestSiteCollection.Name)]
|
|
||||||
public class LargeResponseBodyTests
|
|
||||||
{
|
|
||||||
private readonly IISTestSiteFixture _fixture;
|
|
||||||
|
|
||||||
public LargeResponseBodyTests(IISTestSiteFixture fixture)
|
|
||||||
{
|
|
||||||
_fixture = fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
[InlineData(65000)]
|
|
||||||
[InlineData(1000000)]
|
|
||||||
[InlineData(10000000)]
|
|
||||||
[InlineData(100000000)]
|
|
||||||
public async Task LargeResponseBodyTest_CheckAllResponseBodyBytesWritten(int query)
|
|
||||||
{
|
|
||||||
Assert.Equal(new string('a', query), await _fixture.Client.GetStringAsync($"/LargeResponseBody?length={query}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task LargeResponseBodyFromFile_CheckAllResponseBodyBytesWritten()
|
|
||||||
{
|
|
||||||
Assert.Equal(200000000, (await _fixture.Client.GetStringAsync($"/LargeResponseFile")).Length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
// 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.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|
||||||
{
|
|
||||||
[Collection(IISTestSiteCollection.Name)]
|
|
||||||
|
|
||||||
public class ResponseHeaders
|
|
||||||
{
|
|
||||||
private readonly IISTestSiteFixture _fixture;
|
|
||||||
|
|
||||||
public ResponseHeaders(IISTestSiteFixture fixture)
|
|
||||||
{
|
|
||||||
_fixture = fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task AddResponseHeaders_HeaderValuesAreSetCorrectly()
|
|
||||||
{
|
|
||||||
var response = await _fixture.Client.GetAsync("ResponseHeaders");
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
||||||
Assert.Equal("Request Complete", responseText);
|
|
||||||
|
|
||||||
Assert.True(response.Headers.TryGetValues("UnknownHeader", out var headerValues));
|
|
||||||
Assert.Equal("test123=foo", headerValues.First());
|
|
||||||
|
|
||||||
Assert.True(response.Content.Headers.TryGetValues(HeaderNames.ContentType, out headerValues));
|
|
||||||
Assert.Equal("text/plain", headerValues.First());
|
|
||||||
|
|
||||||
Assert.True(response.Headers.TryGetValues("MultiHeader", out headerValues));
|
|
||||||
Assert.Equal(2, headerValues.Count());
|
|
||||||
Assert.Equal("1", headerValues.First());
|
|
||||||
Assert.Equal("2", headerValues.Last());
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task ErrorCodeIsSetForExceptionDuringRequest()
|
|
||||||
{
|
|
||||||
var response = await _fixture.Client.GetAsync("Throw");
|
|
||||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
|
||||||
Assert.Equal("Internal Server Error", response.ReasonPhrase);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
[InlineData(200, "custom", "custom")]
|
|
||||||
[InlineData(500, "", "Internal Server Error")]
|
|
||||||
[InlineData(999, "", "")]
|
|
||||||
public async Task CustomErrorCodeWorks(int code, string reason, string expectedReason)
|
|
||||||
{
|
|
||||||
var response = await _fixture.Client.GetAsync($"SetCustomErorCode?code={code}&reason={reason}");
|
|
||||||
Assert.Equal((HttpStatusCode)code, response.StatusCode);
|
|
||||||
Assert.Equal(expectedReason, response.ReasonPhrase);
|
|
||||||
Assert.Equal("Body", await response.Content.ReadAsStringAsync());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
// 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;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|
||||||
{
|
|
||||||
[Collection(IISTestSiteCollection.Name)]
|
|
||||||
public class ResponseInvalidOrderingTest
|
|
||||||
{
|
|
||||||
private readonly IISTestSiteFixture _fixture;
|
|
||||||
|
|
||||||
public ResponseInvalidOrderingTest(IISTestSiteFixture fixture)
|
|
||||||
{
|
|
||||||
_fixture = fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalTheory]
|
|
||||||
[InlineData("SetStatusCodeAfterWrite")]
|
|
||||||
[InlineData("SetHeaderAfterWrite")]
|
|
||||||
public async Task ResponseInvalidOrderingTests_ExpectFailure(string path)
|
|
||||||
{
|
|
||||||
Assert.Equal($"Started_{path}Threw_Finished", await _fixture.Client.GetStringAsync("/ResponseInvalidOrdering/" + path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
// 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;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|
||||||
{
|
|
||||||
[Collection(IISTestSiteCollection.Name)]
|
|
||||||
public class ServerVariablesTest
|
|
||||||
{
|
|
||||||
private readonly IISTestSiteFixture _fixture;
|
|
||||||
|
|
||||||
public ServerVariablesTest(IISTestSiteFixture fixture)
|
|
||||||
{
|
|
||||||
_fixture = fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task ProvidesAccessToServerVariables()
|
|
||||||
{
|
|
||||||
var port = _fixture.Client.BaseAddress.Port;
|
|
||||||
Assert.Equal("SERVER_PORT: " + port, await _fixture.Client.GetStringAsync("/ServerVariable?q=SERVER_PORT"));
|
|
||||||
Assert.Equal("QUERY_STRING: q=QUERY_STRING", await _fixture.Client.GetStringAsync("/ServerVariable?q=QUERY_STRING"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task ReturnsNullForUndefinedServerVariable()
|
|
||||||
{
|
|
||||||
Assert.Equal("THIS_VAR_IS_UNDEFINED: (null)", await _fixture.Client.GetStringAsync("/ServerVariable?q=THIS_VAR_IS_UNDEFINED"));
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task BasePathIsNotPrefixedBySlashSlashQuestionMark()
|
|
||||||
{
|
|
||||||
Assert.DoesNotContain(@"\\?\", await _fixture.Client.GetStringAsync("/BasePath"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,234 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
using Microsoft.AspNetCore.Server.IntegrationTesting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Logging.Testing;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
using Xunit.Sdk;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|
||||||
{
|
|
||||||
public class StartupTests : LoggedTest
|
|
||||||
{
|
|
||||||
public StartupTests(ITestOutputHelper output) : base(output)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task ExpandEnvironmentVariableInWebConfig()
|
|
||||||
{
|
|
||||||
var architecture = RuntimeArchitecture.x64;
|
|
||||||
var dotnetLocation = $"%USERPROFILE%\\.dotnet\\{architecture.ToString()}\\dotnet.exe";
|
|
||||||
using (StartLog(out var loggerFactory))
|
|
||||||
{
|
|
||||||
var logger = loggerFactory.CreateLogger("HelloWorldTest");
|
|
||||||
|
|
||||||
var deploymentParameters = GetBaseDeploymentParameters();
|
|
||||||
|
|
||||||
// Point to dotnet installed in user profile.
|
|
||||||
deploymentParameters.EnvironmentVariables["DotnetPath"] = Environment.ExpandEnvironmentVariables(dotnetLocation); // Path to dotnet.
|
|
||||||
|
|
||||||
using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory))
|
|
||||||
{
|
|
||||||
var deploymentResult = await deployer.DeployAsync();
|
|
||||||
|
|
||||||
Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", "%DotnetPath%");
|
|
||||||
|
|
||||||
// Request to base address and check if various parts of the body are rendered & measure the cold startup time.
|
|
||||||
var response = await RetryHelper.RetryRequest(() =>
|
|
||||||
{
|
|
||||||
return deploymentResult.HttpClient.GetAsync("HelloWorld");
|
|
||||||
}, logger, deploymentResult.HostShutdownToken, retryCount: 30);
|
|
||||||
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Assert.Equal("Hello World", responseText);
|
|
||||||
}
|
|
||||||
catch (XunitException)
|
|
||||||
{
|
|
||||||
logger.LogWarning(response.ToString());
|
|
||||||
logger.LogWarning(responseText);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task InvalidProcessPath_ExpectServerError()
|
|
||||||
{
|
|
||||||
var dotnetLocation = "bogus";
|
|
||||||
using (StartLog(out var loggerFactory))
|
|
||||||
{
|
|
||||||
var logger = loggerFactory.CreateLogger("HelloWorldTest");
|
|
||||||
var deploymentParameters = GetBaseDeploymentParameters();
|
|
||||||
|
|
||||||
// Point to dotnet installed in user profile.
|
|
||||||
deploymentParameters.EnvironmentVariables["DotnetPath"] = Environment.ExpandEnvironmentVariables(dotnetLocation); // Path to dotnet.
|
|
||||||
|
|
||||||
using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory))
|
|
||||||
{
|
|
||||||
var deploymentResult = await deployer.DeployAsync();
|
|
||||||
|
|
||||||
Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", "%DotnetPath%");
|
|
||||||
|
|
||||||
// Request to base address and check if various parts of the body are rendered & measure the cold startup time.
|
|
||||||
var response = await RetryHelper.RetryRequest(() =>
|
|
||||||
{
|
|
||||||
return deploymentResult.HttpClient.GetAsync("HelloWorld");
|
|
||||||
}, logger, deploymentResult.HostShutdownToken, retryCount: 30);
|
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NETCOREAPP2_0 || NETCOREAPP2_1
|
|
||||||
|
|
||||||
[Fact] // Consistently fails on CI for net461
|
|
||||||
public async Task StandaloneApplication_ExpectCorrectPublish()
|
|
||||||
{
|
|
||||||
using (StartLog(out var loggerFactory))
|
|
||||||
{
|
|
||||||
var logger = loggerFactory.CreateLogger("HelloWorldTest");
|
|
||||||
|
|
||||||
var deploymentParameters = GetBaseDeploymentParameters();
|
|
||||||
|
|
||||||
using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory))
|
|
||||||
{
|
|
||||||
var deploymentResult = await deployer.DeployAsync();
|
|
||||||
deploymentParameters.ApplicationType = ApplicationType.Standalone;
|
|
||||||
|
|
||||||
// Request to base address and check if various parts of the body are rendered & measure the cold startup time.
|
|
||||||
var response = await RetryHelper.RetryRequest(() =>
|
|
||||||
{
|
|
||||||
return deploymentResult.HttpClient.GetAsync("HelloWorld");
|
|
||||||
}, logger, deploymentResult.HostShutdownToken, retryCount: 30);
|
|
||||||
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Assert.Equal("Hello World", responseText);
|
|
||||||
}
|
|
||||||
catch (XunitException)
|
|
||||||
{
|
|
||||||
logger.LogWarning(response.ToString());
|
|
||||||
logger.LogWarning(responseText);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact] // Consistently fails on CI for net461
|
|
||||||
public async Task StandaloneApplication_AbsolutePathToExe_ExpectCorrectPublish()
|
|
||||||
{
|
|
||||||
using (StartLog(out var loggerFactory))
|
|
||||||
{
|
|
||||||
var logger = loggerFactory.CreateLogger("HelloWorldTest");
|
|
||||||
|
|
||||||
var deploymentParameters = GetBaseDeploymentParameters();
|
|
||||||
deploymentParameters.ApplicationType = ApplicationType.Standalone;
|
|
||||||
|
|
||||||
using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory))
|
|
||||||
{
|
|
||||||
var deploymentResult = await deployer.DeployAsync();
|
|
||||||
|
|
||||||
Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", $"{deploymentResult.ContentRoot}\\InProcessWebSite.exe");
|
|
||||||
|
|
||||||
// Request to base address and check if various parts of the body are rendered & measure the cold startup time.
|
|
||||||
var response = await RetryHelper.RetryRequest(() =>
|
|
||||||
{
|
|
||||||
return deploymentResult.HttpClient.GetAsync("HelloWorld");
|
|
||||||
}, logger, deploymentResult.HostShutdownToken, retryCount: 30);
|
|
||||||
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Assert.Equal("Hello World", responseText);
|
|
||||||
}
|
|
||||||
catch (XunitException)
|
|
||||||
{
|
|
||||||
logger.LogWarning(response.ToString());
|
|
||||||
logger.LogWarning(responseText);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif NET461
|
|
||||||
#else
|
|
||||||
#error Target frameworks need to be updated
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task DetectsOveriddenServer()
|
|
||||||
{
|
|
||||||
var testSink = new TestSink();
|
|
||||||
using (StartLog(out var loggerFactory))
|
|
||||||
{
|
|
||||||
var testLoggerFactory = new TestLoggerFactory(testSink, true);
|
|
||||||
loggerFactory.AddProvider(new TestLoggerProvider(testLoggerFactory));
|
|
||||||
|
|
||||||
using (var deployer = ApplicationDeployerFactory.Create(GetBaseDeploymentParameters("OverriddenServerWebSite"), loggerFactory))
|
|
||||||
{
|
|
||||||
var deploymentResult = await deployer.DeployAsync();
|
|
||||||
var response = await deploymentResult.HttpClient.GetAsync("/");
|
|
||||||
Assert.False(response.IsSuccessStatusCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Assert.Contains(testSink.Writes, context => context.State.ToString().Contains("Application is running inside IIS process but is not configured to use IIS server"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeploymentParameters GetBaseDeploymentParameters(string site = null)
|
|
||||||
{
|
|
||||||
return new DeploymentParameters(Helpers.GetTestWebSitePath(site ?? "InProcessWebSite"), ServerType.IISExpress, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64)
|
|
||||||
{
|
|
||||||
ServerConfigTemplateContent = File.ReadAllText("AppHostConfig/Http.config"),
|
|
||||||
SiteName = "HttpTestSite", // This is configured in the Http.config
|
|
||||||
TargetFramework = "netcoreapp2.1",
|
|
||||||
ApplicationType = ApplicationType.Portable,
|
|
||||||
Configuration = GetCurrentConfiguration()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetCurrentConfiguration()
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
return "Debug";
|
|
||||||
#else
|
|
||||||
return "Release";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestLoggerProvider : ILoggerProvider
|
|
||||||
{
|
|
||||||
private readonly TestLoggerFactory _loggerFactory;
|
|
||||||
|
|
||||||
public TestLoggerProvider(TestLoggerFactory loggerFactory)
|
|
||||||
{
|
|
||||||
_loggerFactory = loggerFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ILogger CreateLogger(string categoryName)
|
|
||||||
{
|
|
||||||
return _loggerFactory.CreateLogger(categoryName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
// 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;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|
||||||
{
|
|
||||||
[Collection(IISTestSiteCollection.Name)]
|
|
||||||
public class SynchronousReadAndWriteTests
|
|
||||||
{
|
|
||||||
private readonly IISTestSiteFixture _fixture;
|
|
||||||
|
|
||||||
public SynchronousReadAndWriteTests(IISTestSiteFixture fixture)
|
|
||||||
{
|
|
||||||
_fixture = fixture;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task ReadAndWriteSynchronously()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 100; i++)
|
|
||||||
{
|
|
||||||
var content = new StringContent(new string('a', 100000));
|
|
||||||
var response = await _fixture.Client.PostAsync("ReadAndWriteSynchronously", content);
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
Assert.Equal(expected: 110000, actual: responseText.Length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task ReadAndWriteEcho()
|
|
||||||
{
|
|
||||||
var body = new string('a', 100000);
|
|
||||||
var content = new StringContent(body);
|
|
||||||
var response = await _fixture.Client.PostAsync("ReadAndWriteEcho", content);
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
Assert.Equal(body, responseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task ReadAndWriteCopyToAsync()
|
|
||||||
{
|
|
||||||
var body = new string('a', 100000);
|
|
||||||
var content = new StringContent(body);
|
|
||||||
var response = await _fixture.Client.PostAsync("ReadAndWriteCopyToAsync", content);
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
Assert.Equal(body, responseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public async Task ReadAndWriteEchoTwice()
|
|
||||||
{
|
|
||||||
var requestBody = new string('a', 10000);
|
|
||||||
var content = new StringContent(requestBody);
|
|
||||||
var response = await _fixture.Client.PostAsync("ReadAndWriteEchoTwice", content);
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
Assert.Equal(requestBody.Length * 2, responseText.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ConditionalFact]
|
|
||||||
public void ReadAndWriteSlowConnection()
|
|
||||||
{
|
|
||||||
var ipHostEntry = Dns.GetHostEntry(_fixture.Client.BaseAddress.Host);
|
|
||||||
|
|
||||||
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
|
|
||||||
{
|
|
||||||
foreach (var hostEntry in ipHostEntry.AddressList)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
socket.Connect(hostEntry, _fixture.Client.BaseAddress.Port);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// Exceptions can be thrown based on ipv6 support
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.True(socket.Connected);
|
|
||||||
|
|
||||||
var testString = "hello world";
|
|
||||||
var request = $"POST /ReadAndWriteSlowConnection HTTP/1.0\r\n" +
|
|
||||||
$"Content-Length: {testString.Length}\r\n" +
|
|
||||||
"Host: " + "localhost\r\n" +
|
|
||||||
"\r\n";
|
|
||||||
var bytes = 0;
|
|
||||||
var requestStringBytes = Encoding.ASCII.GetBytes(request);
|
|
||||||
var testStringBytes = Encoding.ASCII.GetBytes(testString);
|
|
||||||
|
|
||||||
while ((bytes += socket.Send(requestStringBytes, bytes, 1, SocketFlags.None)) < requestStringBytes.Length)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes = 0;
|
|
||||||
while ((bytes += socket.Send(testStringBytes, bytes, 1, SocketFlags.None)) < testStringBytes.Length)
|
|
||||||
{
|
|
||||||
Thread.Sleep(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
var stringBuilder = new StringBuilder();
|
|
||||||
var buffer = new byte[4096];
|
|
||||||
int size;
|
|
||||||
while ((size = socket.Receive(buffer, buffer.Length, SocketFlags.None)) != 0)
|
|
||||||
{
|
|
||||||
stringBuilder.Append(Encoding.ASCII.GetString(buffer, 0, size));
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Contains(new StringBuilder().Insert(0, "hello world", 100).ToString(), stringBuilder.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -22,26 +22,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public Task UpgradeFeatureDetectionEnabled_InProcess_IISExpress_CoreClr_x64_Portable()
|
|
||||||
{
|
|
||||||
return UpgradeFeatureDetectionDeployer(RuntimeFlavor.CoreClr,
|
|
||||||
ApplicationType.Portable,
|
|
||||||
"AppHostConfig/WebsocketsNotSupported.config",
|
|
||||||
Helpers.GetInProcessTestSitesPath(),
|
|
||||||
"Disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public Task UpgradeFeatureDetectionDisabled_InProcess_IISExpress_CoreClr_x64_Portable()
|
|
||||||
{
|
|
||||||
return UpgradeFeatureDetectionDeployer(RuntimeFlavor.CoreClr,
|
|
||||||
ApplicationType.Portable,
|
|
||||||
"AppHostConfig/Http.config",
|
|
||||||
Helpers.GetInProcessTestSitesPath(),
|
|
||||||
_isWebsocketsSupported);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public Task UpgradeFeatureDetectionEnabled_OutOfProcess_IISExpress_CoreClr_x64_Portable()
|
public Task UpgradeFeatureDetectionEnabled_OutOfProcess_IISExpress_CoreClr_x64_Portable()
|
||||||
{
|
{
|
||||||
|
|
@ -75,7 +55,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
||||||
{
|
{
|
||||||
var logger = loggerFactory.CreateLogger("HelloWorldTest");
|
var logger = loggerFactory.CreateLogger("HelloWorldTest");
|
||||||
|
|
||||||
var deploymentParameters = new DeploymentParameters(Helpers.GetInProcessTestSitesPath(), serverType, runtimeFlavor, architecture)
|
var deploymentParameters = new DeploymentParameters(sitePath, serverType, runtimeFlavor, architecture)
|
||||||
{
|
{
|
||||||
EnvironmentName = "UpgradeFeatureDetection", // Will pick the Start class named 'StartupHelloWorld',
|
EnvironmentName = "UpgradeFeatureDetection", // Will pick the Start class named 'StartupHelloWorld',
|
||||||
ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText(configPath) : null,
|
ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText(configPath) : null,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue