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.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.IIS;
|
||||
using Microsoft.AspNetCore.Server.IISIntegration;
|
||||
|
||||
namespace NativeIISSample
|
||||
|
|
@ -66,12 +65,6 @@ namespace NativeIISSample
|
|||
// accessing IIS server variables
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
private const int HR_NOT_FOUND = unchecked((int)0x80070490);
|
||||
private const int HR_OK = 0;
|
||||
|
||||
private const string KERNEL32 = "kernel32.dll";
|
||||
|
||||
private const string AspNetCoreModuleDll = "aspnetcorerh.dll";
|
||||
|
||||
[DllImport(KERNEL32, ExactSpelling = true, SetLastError = true)]
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Check if in process
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && NativeMethods.IsAspNetCoreModuleLoaded())
|
||||
{
|
||||
|
||||
return SetupInProcessServer(hostBuilder);
|
||||
}
|
||||
|
||||
var port = hostBuilder.GetSetting(ServerPort) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPort}");
|
||||
var path = hostBuilder.GetSetting(ServerPath) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{ServerPath}");
|
||||
var pairingToken = hostBuilder.GetSetting(PairingToken) ?? Environment.GetEnvironmentVariable($"ASPNETCORE_{PairingToken}");
|
||||
|
|
@ -107,23 +100,5 @@ namespace Microsoft.AspNetCore.Hosting
|
|||
|
||||
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]
|
||||
public Task UpgradeFeatureDetectionEnabled_OutOfProcess_IISExpress_CoreClr_x64_Portable()
|
||||
{
|
||||
|
|
@ -75,7 +55,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
|
|||
{
|
||||
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',
|
||||
ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText(configPath) : null,
|
||||
|
|
|
|||
Loading…
Reference in New Issue