New IServer design with IHttpApplication added #395
This commit is contained in:
parent
f600604140
commit
8c256a0d87
|
|
@ -0,0 +1,35 @@
|
|||
// 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.AspNet.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNet.Hosting.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an HttpApplication.
|
||||
/// </summary>
|
||||
public interface IHttpApplication<TContext>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a TContext given a collection of HTTP features.
|
||||
/// </summary>
|
||||
/// <param name="contextFeatures">A collection of HTTP features to be used for creating the TContext.</param>
|
||||
/// <returns>The created TContext.</returns>
|
||||
TContext CreateContext(IFeatureCollection contextFeatures);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously processes an TContext.
|
||||
/// </summary>
|
||||
/// <param name="context">The TContext that the operation will process.</param>
|
||||
Task ProcessRequestAsync(TContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Dispose a given TContext.
|
||||
/// </summary>
|
||||
/// <param name="context">The TContext to be disposed.</param>
|
||||
/// <param name="exception">The Exception thrown when processing did not complete successfully, otherwise null.</param>
|
||||
void DisposeContext(TContext context, Exception exception);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNet.Hosting.Server
|
||||
|
|
@ -18,9 +17,9 @@ namespace Microsoft.AspNet.Hosting.Server
|
|||
IFeatureCollection Features { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Start the server with the given function that processes an HTTP request.
|
||||
/// Start the server with an HttpApplication.
|
||||
/// </summary>
|
||||
/// <param name="requestDelegate">A function that processes an HTTP request.</param>
|
||||
void Start(RequestDelegate requestDelegate);
|
||||
/// <param name="application">An instance of <see cref="IHttpApplication"/>.</param>
|
||||
void Start<TContext>(IHttpApplication<TContext> application);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Hosting.Server;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Hosting.Internal
|
||||
{
|
||||
public class HostingApplication : IHttpApplication<HostingApplication.Context>
|
||||
{
|
||||
private readonly RequestDelegate _application;
|
||||
private readonly ILogger _logger;
|
||||
private readonly DiagnosticSource _diagnosticSource;
|
||||
private readonly IHttpContextFactory _httpContextFactory;
|
||||
|
||||
public HostingApplication(
|
||||
RequestDelegate application,
|
||||
ILogger logger,
|
||||
DiagnosticSource diagnosticSource,
|
||||
IHttpContextFactory httpContextFactory)
|
||||
{
|
||||
_application = application;
|
||||
_logger = logger;
|
||||
_diagnosticSource = diagnosticSource;
|
||||
_httpContextFactory = httpContextFactory;
|
||||
}
|
||||
|
||||
public Context CreateContext(IFeatureCollection contextFeatures)
|
||||
{
|
||||
var httpContext = _httpContextFactory.Create(contextFeatures);
|
||||
var startTick = Environment.TickCount;
|
||||
|
||||
var scope = _logger.RequestScope(httpContext);
|
||||
_logger.RequestStarting(httpContext);
|
||||
if (_diagnosticSource.IsEnabled("Microsoft.AspNet.Hosting.BeginRequest"))
|
||||
{
|
||||
_diagnosticSource.Write("Microsoft.AspNet.Hosting.BeginRequest", new { httpContext = httpContext, tickCount = startTick });
|
||||
}
|
||||
|
||||
return new Context
|
||||
{
|
||||
HttpContext = httpContext,
|
||||
Scope = scope,
|
||||
StartTick = startTick,
|
||||
};
|
||||
}
|
||||
|
||||
public void DisposeContext(Context context, Exception exception)
|
||||
{
|
||||
var httpContext = context.HttpContext;
|
||||
var currentTick = Environment.TickCount;
|
||||
_logger.RequestFinished(httpContext, context.StartTick, currentTick);
|
||||
|
||||
if (exception == null)
|
||||
{
|
||||
if (_diagnosticSource.IsEnabled("Microsoft.AspNet.Hosting.EndRequest"))
|
||||
{
|
||||
_diagnosticSource.Write("Microsoft.AspNet.Hosting.EndRequest", new { httpContext = httpContext, tickCount = currentTick });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_diagnosticSource.IsEnabled("Microsoft.AspNet.Hosting.UnhandledException"))
|
||||
{
|
||||
_diagnosticSource.Write("Microsoft.AspNet.Hosting.UnhandledException", new { httpContext = httpContext, tickCount = currentTick, exception = exception });
|
||||
}
|
||||
}
|
||||
|
||||
context.Scope.Dispose();
|
||||
|
||||
_httpContextFactory.Dispose(httpContext);
|
||||
}
|
||||
|
||||
public async Task ProcessRequestAsync(Context context)
|
||||
{
|
||||
await _application(context.HttpContext);
|
||||
}
|
||||
|
||||
public struct Context
|
||||
{
|
||||
public HttpContext HttpContext { get; set; }
|
||||
public IDisposable Scope { get; set; }
|
||||
public int StartTick { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Hosting.Builder;
|
||||
using Microsoft.AspNet.Hosting.Server;
|
||||
using Microsoft.AspNet.Hosting.Startup;
|
||||
|
|
@ -90,45 +89,11 @@ namespace Microsoft.AspNet.Hosting.Internal
|
|||
|
||||
var logger = _applicationServices.GetRequiredService<ILogger<HostingEngine>>();
|
||||
var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticSource>();
|
||||
var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
|
||||
|
||||
logger.Starting();
|
||||
|
||||
Server.Start(
|
||||
async httpContext =>
|
||||
{
|
||||
if (diagnosticSource.IsEnabled("Microsoft.AspNet.Hosting.BeginRequest"))
|
||||
{
|
||||
diagnosticSource.Write("Microsoft.AspNet.Hosting.BeginRequest", new { httpContext = httpContext });
|
||||
}
|
||||
|
||||
using (logger.RequestScope(httpContext))
|
||||
{
|
||||
int startTime = 0;
|
||||
try
|
||||
{
|
||||
logger.RequestStarting(httpContext);
|
||||
|
||||
startTime = Environment.TickCount;
|
||||
await application(httpContext);
|
||||
|
||||
logger.RequestFinished(httpContext, startTime);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.RequestFailed(httpContext, startTime);
|
||||
|
||||
if (diagnosticSource.IsEnabled("Microsoft.AspNet.Hosting.UnhandledException"))
|
||||
{
|
||||
diagnosticSource.Write("Microsoft.AspNet.Hosting.UnhandledException", new { httpContext = httpContext, exception = ex });
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
if (diagnosticSource.IsEnabled("Microsoft.AspNet.Hosting.EndRequest"))
|
||||
{
|
||||
diagnosticSource.Write("Microsoft.AspNet.Hosting.EndRequest", new { httpContext = httpContext });
|
||||
}
|
||||
});
|
||||
Server.Start(new HostingApplication(application, logger, diagnosticSource, httpContextFactory));
|
||||
|
||||
_applicationLifetime.NotifyStarted();
|
||||
logger.Started();
|
||||
|
|
|
|||
|
|
@ -30,11 +30,14 @@ namespace Microsoft.AspNet.Hosting.Internal
|
|||
}
|
||||
}
|
||||
|
||||
public static void RequestFinished(this ILogger logger, HttpContext httpContext, int startTimeInTicks)
|
||||
public static void RequestFinished(this ILogger logger, HttpContext httpContext, int startTimeInTicks, int currentTick)
|
||||
{
|
||||
if (logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
var elapsed = new TimeSpan(TicksPerMillisecond * (Environment.TickCount - startTimeInTicks));
|
||||
var elapsed = new TimeSpan(TicksPerMillisecond * (currentTick < startTimeInTicks ?
|
||||
(int.MaxValue - startTimeInTicks) + (currentTick - int.MinValue) :
|
||||
currentTick - startTimeInTicks));
|
||||
|
||||
logger.Log(
|
||||
logLevel: LogLevel.Information,
|
||||
eventId: LoggerEventIds.RequestFinished,
|
||||
|
|
@ -44,20 +47,6 @@ namespace Microsoft.AspNet.Hosting.Internal
|
|||
}
|
||||
}
|
||||
|
||||
public static void RequestFailed(this ILogger logger, HttpContext httpContext, int startTimeInTicks)
|
||||
{
|
||||
if (logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
var elapsed = new TimeSpan(TicksPerMillisecond * (Environment.TickCount - startTimeInTicks));
|
||||
logger.Log(
|
||||
logLevel: LogLevel.Information,
|
||||
eventId: LoggerEventIds.RequestFailed,
|
||||
state: new HostingRequestFailed(httpContext, elapsed),
|
||||
exception: null,
|
||||
formatter: HostingRequestFailed.Callback);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ApplicationError(this ILogger logger, Exception exception)
|
||||
{
|
||||
logger.LogError(
|
||||
|
|
@ -221,48 +210,6 @@ namespace Microsoft.AspNet.Hosting.Internal
|
|||
return _cachedGetValues;
|
||||
}
|
||||
}
|
||||
|
||||
private class HostingRequestFailed
|
||||
{
|
||||
internal static readonly Func<object, Exception, string> Callback = (state, exception) => ((HostingRequestFailed)state).ToString();
|
||||
|
||||
private readonly HttpContext _httpContext;
|
||||
private readonly TimeSpan _elapsed;
|
||||
|
||||
private IEnumerable<KeyValuePair<string, object>> _cachedGetValues;
|
||||
private string _cachedToString;
|
||||
|
||||
public HostingRequestFailed(HttpContext httpContext, TimeSpan elapsed)
|
||||
{
|
||||
_httpContext = httpContext;
|
||||
_elapsed = elapsed;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (_cachedToString == null)
|
||||
{
|
||||
_cachedToString = $"Request finished in {_elapsed.TotalMilliseconds}ms 500";
|
||||
}
|
||||
|
||||
return _cachedToString;
|
||||
}
|
||||
|
||||
public IEnumerable<KeyValuePair<string, object>> GetValues()
|
||||
{
|
||||
if (_cachedGetValues == null)
|
||||
{
|
||||
_cachedGetValues = new[]
|
||||
{
|
||||
new KeyValuePair<string, object>("ElapsedMilliseconds", _elapsed.TotalMilliseconds),
|
||||
new KeyValuePair<string, object>("StatusCode", 500),
|
||||
new KeyValuePair<string, object>("ContentType", null),
|
||||
};
|
||||
}
|
||||
|
||||
return _cachedGetValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ namespace Microsoft.AspNet.Hosting.Internal
|
|||
{
|
||||
public const int RequestStarting = 1;
|
||||
public const int RequestFinished = 2;
|
||||
public const int RequestFailed = 3;
|
||||
public const int Starting = 4;
|
||||
public const int Started = 5;
|
||||
public const int Shutdown = 6;
|
||||
public const int ApplicationStartupException = 7;
|
||||
public const int Starting = 3;
|
||||
public const int Started = 4;
|
||||
public const int Shutdown = 5;
|
||||
public const int ApplicationStartupException = 6;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ using Microsoft.AspNet.Hosting.Server;
|
|||
using Microsoft.AspNet.Hosting.Startup;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.Extensions.CompilationAbstractions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@ using System.Net;
|
|||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Hosting.Server;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Context = Microsoft.AspNet.Hosting.Internal.HostingApplication.Context;
|
||||
|
||||
namespace Microsoft.AspNet.TestHost
|
||||
{
|
||||
|
|
@ -23,27 +24,21 @@ namespace Microsoft.AspNet.TestHost
|
|||
/// </summary>
|
||||
public class ClientHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IHttpApplication<Context> _application;
|
||||
private readonly PathString _pathBase;
|
||||
private readonly IHttpContextFactory _factory;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new handler.
|
||||
/// </summary>
|
||||
/// <param name="next">The pipeline entry point.</param>
|
||||
public ClientHandler(RequestDelegate next, PathString pathBase, IHttpContextFactory httpContextFactory)
|
||||
public ClientHandler(PathString pathBase, IHttpApplication<Context> application)
|
||||
{
|
||||
if (next == null)
|
||||
if (application == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
throw new ArgumentNullException(nameof(application));
|
||||
}
|
||||
if (httpContextFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContextFactory));
|
||||
}
|
||||
|
||||
_next = next;
|
||||
_factory = httpContextFactory;
|
||||
|
||||
_application = application;
|
||||
|
||||
// PathString.StartsWithSegments that we use below requires the base path to not end in a slash.
|
||||
if (pathBase.HasValue && pathBase.Value.EndsWith("/"))
|
||||
|
|
@ -69,7 +64,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
var state = new RequestState(request, _pathBase, _factory);
|
||||
var state = new RequestState(request, _pathBase, _application);
|
||||
var requestContent = request.Content ?? new StreamContent(Stream.Null);
|
||||
var body = await requestContent.ReadAsStreamAsync();
|
||||
if (body.CanSeek)
|
||||
|
|
@ -77,7 +72,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
// This body may have been consumed before, rewind it.
|
||||
body.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
state.HttpContext.Request.Body = body;
|
||||
state.Context.HttpContext.Request.Body = body;
|
||||
var registration = cancellationToken.Register(state.AbortRequest);
|
||||
|
||||
// Async offload, don't let the test code block the caller.
|
||||
|
|
@ -85,16 +80,17 @@ namespace Microsoft.AspNet.TestHost
|
|||
{
|
||||
try
|
||||
{
|
||||
await _next(state.HttpContext);
|
||||
await _application.ProcessRequestAsync(state.Context);
|
||||
state.CompleteResponse();
|
||||
state.ServerCleanup(exception: null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
state.Abort(ex);
|
||||
state.ServerCleanup(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
state.ServerCleanup();
|
||||
registration.Dispose();
|
||||
}
|
||||
});
|
||||
|
|
@ -105,20 +101,20 @@ namespace Microsoft.AspNet.TestHost
|
|||
private class RequestState
|
||||
{
|
||||
private readonly HttpRequestMessage _request;
|
||||
private readonly IHttpApplication<Context> _application;
|
||||
private TaskCompletionSource<HttpResponseMessage> _responseTcs;
|
||||
private ResponseStream _responseStream;
|
||||
private ResponseFeature _responseFeature;
|
||||
private CancellationTokenSource _requestAbortedSource;
|
||||
private IHttpContextFactory _factory;
|
||||
private bool _pipelineFinished;
|
||||
|
||||
internal RequestState(HttpRequestMessage request, PathString pathBase, IHttpContextFactory factory)
|
||||
internal RequestState(HttpRequestMessage request, PathString pathBase, IHttpApplication<Context> application)
|
||||
{
|
||||
_request = request;
|
||||
_application = application;
|
||||
_responseTcs = new TaskCompletionSource<HttpResponseMessage>();
|
||||
_requestAbortedSource = new CancellationTokenSource();
|
||||
_pipelineFinished = false;
|
||||
_factory = factory;
|
||||
|
||||
if (request.RequestUri.IsDefaultPort)
|
||||
{
|
||||
|
|
@ -129,12 +125,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
request.Headers.Host = request.RequestUri.GetComponents(UriComponents.HostAndPort, UriFormat.UriEscaped);
|
||||
}
|
||||
|
||||
HttpContext = _factory.Create(new FeatureCollection());
|
||||
|
||||
HttpContext.Features.Set<IHttpRequestFeature>(new RequestFeature());
|
||||
Context = application.CreateContext(new FeatureCollection());
|
||||
var httpContext = Context.HttpContext;
|
||||
|
||||
httpContext.Features.Set<IHttpRequestFeature>(new RequestFeature());
|
||||
_responseFeature = new ResponseFeature();
|
||||
HttpContext.Features.Set<IHttpResponseFeature>(_responseFeature);
|
||||
var serverRequest = HttpContext.Request;
|
||||
httpContext.Features.Set<IHttpResponseFeature>(_responseFeature);
|
||||
var serverRequest = httpContext.Request;
|
||||
serverRequest.Protocol = "HTTP/" + request.Version.ToString(2);
|
||||
serverRequest.Scheme = request.RequestUri.Scheme;
|
||||
serverRequest.Method = request.Method.ToString();
|
||||
|
|
@ -168,12 +165,12 @@ namespace Microsoft.AspNet.TestHost
|
|||
}
|
||||
|
||||
_responseStream = new ResponseStream(ReturnResponseMessage, AbortRequest);
|
||||
HttpContext.Response.Body = _responseStream;
|
||||
HttpContext.Response.StatusCode = 200;
|
||||
HttpContext.RequestAborted = _requestAbortedSource.Token;
|
||||
httpContext.Response.Body = _responseStream;
|
||||
httpContext.Response.StatusCode = 200;
|
||||
httpContext.RequestAborted = _requestAbortedSource.Token;
|
||||
}
|
||||
|
||||
public HttpContext HttpContext { get; private set; }
|
||||
public Context Context { get; private set; }
|
||||
|
||||
public Task<HttpResponseMessage> ResponseTask
|
||||
{
|
||||
|
|
@ -212,16 +209,17 @@ namespace Microsoft.AspNet.TestHost
|
|||
private HttpResponseMessage GenerateResponse()
|
||||
{
|
||||
_responseFeature.FireOnSendingHeaders();
|
||||
var httpContext = Context.HttpContext;
|
||||
|
||||
var response = new HttpResponseMessage();
|
||||
response.StatusCode = (HttpStatusCode)HttpContext.Response.StatusCode;
|
||||
response.ReasonPhrase = HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase;
|
||||
response.StatusCode = (HttpStatusCode)httpContext.Response.StatusCode;
|
||||
response.ReasonPhrase = httpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase;
|
||||
response.RequestMessage = _request;
|
||||
// response.Version = owinResponse.Protocol;
|
||||
|
||||
response.Content = new StreamContent(_responseStream);
|
||||
|
||||
foreach (var header in HttpContext.Response.Headers)
|
||||
foreach (var header in httpContext.Response.Headers)
|
||||
{
|
||||
if (!response.Headers.TryAddWithoutValidation(header.Key, (IEnumerable<string>)header.Value))
|
||||
{
|
||||
|
|
@ -239,12 +237,9 @@ namespace Microsoft.AspNet.TestHost
|
|||
_responseTcs.TrySetException(exception);
|
||||
}
|
||||
|
||||
internal void ServerCleanup()
|
||||
internal void ServerCleanup(Exception exception)
|
||||
{
|
||||
if (HttpContext != null)
|
||||
{
|
||||
_factory.Dispose(HttpContext);
|
||||
}
|
||||
_application.DisposeContext(Context, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Microsoft.AspNet.Http;
|
|||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Context = Microsoft.AspNet.Hosting.Internal.HostingApplication.Context;
|
||||
|
||||
namespace Microsoft.AspNet.TestHost
|
||||
{
|
||||
|
|
@ -18,15 +19,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
{
|
||||
private const string DefaultEnvironmentName = "Development";
|
||||
private const string ServerName = nameof(TestServer);
|
||||
private RequestDelegate _appDelegate;
|
||||
private IDisposable _appInstance;
|
||||
private bool _disposed = false;
|
||||
private IHttpContextFactory _httpContextFactory;
|
||||
private IHttpApplication<Context> _application;
|
||||
|
||||
public TestServer(WebHostBuilder builder)
|
||||
{
|
||||
var hostingEngine = builder.UseServer(this).Build();
|
||||
_httpContextFactory = hostingEngine.ApplicationServices.GetService<IHttpContextFactory>();
|
||||
_appInstance = hostingEngine.Start();
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +98,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
public HttpMessageHandler CreateHandler()
|
||||
{
|
||||
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
|
||||
return new ClientHandler(Invoke, pathBase, _httpContextFactory);
|
||||
return new ClientHandler(pathBase, _application);
|
||||
}
|
||||
|
||||
public HttpClient CreateClient()
|
||||
|
|
@ -110,7 +109,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
public WebSocketClient CreateWebSocketClient()
|
||||
{
|
||||
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
|
||||
return new WebSocketClient(Invoke, pathBase, _httpContextFactory);
|
||||
return new WebSocketClient(pathBase, _application);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -123,24 +122,49 @@ namespace Microsoft.AspNet.TestHost
|
|||
return new RequestBuilder(this, path);
|
||||
}
|
||||
|
||||
public Task Invoke(HttpContext context)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
}
|
||||
return _appDelegate(context);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposed = true;
|
||||
_appInstance.Dispose();
|
||||
}
|
||||
|
||||
void IServer.Start(RequestDelegate requestDelegate)
|
||||
void IServer.Start<TContext>(IHttpApplication<TContext> application)
|
||||
{
|
||||
_appDelegate = requestDelegate;
|
||||
_application = new ApplicationWrapper<Context>((IHttpApplication<Context>)application, () =>
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class ApplicationWrapper<TContext> : IHttpApplication<TContext>
|
||||
{
|
||||
IHttpApplication<TContext> _application;
|
||||
Action _preProcessRequestAsync;
|
||||
|
||||
public ApplicationWrapper(IHttpApplication<TContext> application, Action preProcessRequestAsync)
|
||||
{
|
||||
_application = application;
|
||||
_preProcessRequestAsync = preProcessRequestAsync;
|
||||
}
|
||||
|
||||
public TContext CreateContext(IFeatureCollection contextFeatures)
|
||||
{
|
||||
return _application.CreateContext(contextFeatures);
|
||||
}
|
||||
|
||||
public void DisposeContext(TContext context, Exception exception)
|
||||
{
|
||||
_application.DisposeContext(context, exception);
|
||||
}
|
||||
|
||||
public Task ProcessRequestAsync(TContext context)
|
||||
{
|
||||
_preProcessRequestAsync();
|
||||
return _application.ProcessRequestAsync(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,31 +8,26 @@ using System.Net.WebSockets;
|
|||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Hosting.Server;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Context = Microsoft.AspNet.Hosting.Internal.HostingApplication.Context;
|
||||
|
||||
namespace Microsoft.AspNet.TestHost
|
||||
{
|
||||
public class WebSocketClient
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IHttpApplication<Context> _application;
|
||||
private readonly PathString _pathBase;
|
||||
private readonly IHttpContextFactory _httpContextFactory;
|
||||
|
||||
internal WebSocketClient(RequestDelegate next, PathString pathBase, IHttpContextFactory httpContextFactory)
|
||||
internal WebSocketClient(PathString pathBase, IHttpApplication<Context> application)
|
||||
{
|
||||
if (next == null)
|
||||
if (application == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
throw new ArgumentNullException(nameof(application));
|
||||
}
|
||||
if (httpContextFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContextFactory));
|
||||
}
|
||||
|
||||
_next = next;
|
||||
_httpContextFactory = httpContextFactory;
|
||||
|
||||
_application = application;
|
||||
|
||||
// PathString.StartsWithSegments that we use below requires the base path to not end in a slash.
|
||||
if (pathBase.HasValue && pathBase.Value.EndsWith("/"))
|
||||
|
|
@ -58,11 +53,11 @@ namespace Microsoft.AspNet.TestHost
|
|||
|
||||
public async Task<WebSocket> ConnectAsync(Uri uri, CancellationToken cancellationToken)
|
||||
{
|
||||
var state = new RequestState(uri, _pathBase, cancellationToken, _httpContextFactory);
|
||||
var state = new RequestState(uri, _pathBase, cancellationToken, _application);
|
||||
|
||||
if (ConfigureRequest != null)
|
||||
{
|
||||
ConfigureRequest(state.HttpContext.Request);
|
||||
ConfigureRequest(state.Context.HttpContext.Request);
|
||||
}
|
||||
|
||||
// Async offload, don't let the test code block the caller.
|
||||
|
|
@ -70,12 +65,14 @@ namespace Microsoft.AspNet.TestHost
|
|||
{
|
||||
try
|
||||
{
|
||||
await _next(state.HttpContext);
|
||||
await _application.ProcessRequestAsync(state.Context);
|
||||
state.PipelineComplete();
|
||||
state.ServerCleanup(exception: null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
state.PipelineFailed(ex);
|
||||
state.ServerCleanup(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -88,24 +85,25 @@ namespace Microsoft.AspNet.TestHost
|
|||
|
||||
private class RequestState : IDisposable, IHttpWebSocketFeature
|
||||
{
|
||||
private readonly IHttpApplication<Context> _application;
|
||||
private TaskCompletionSource<WebSocket> _clientWebSocketTcs;
|
||||
private WebSocket _serverWebSocket;
|
||||
private IHttpContextFactory _factory;
|
||||
|
||||
public HttpContext HttpContext { get; private set; }
|
||||
public Context Context { get; private set; }
|
||||
public Task<WebSocket> WebSocketTask { get { return _clientWebSocketTcs.Task; } }
|
||||
|
||||
public RequestState(Uri uri, PathString pathBase, CancellationToken cancellationToken, IHttpContextFactory factory)
|
||||
public RequestState(Uri uri, PathString pathBase, CancellationToken cancellationToken, IHttpApplication<Context> application)
|
||||
{
|
||||
_factory = factory;
|
||||
_clientWebSocketTcs = new TaskCompletionSource<WebSocket>();
|
||||
_application = application;
|
||||
|
||||
// HttpContext
|
||||
HttpContext = _factory.Create(new FeatureCollection());
|
||||
Context = _application.CreateContext(new FeatureCollection());
|
||||
var httpContext = Context.HttpContext;
|
||||
|
||||
// Request
|
||||
HttpContext.Features.Set<IHttpRequestFeature>(new RequestFeature());
|
||||
var request = HttpContext.Request;
|
||||
httpContext.Features.Set<IHttpRequestFeature>(new RequestFeature());
|
||||
var request = httpContext.Request;
|
||||
request.Protocol = "HTTP/1.1";
|
||||
var scheme = uri.Scheme;
|
||||
scheme = (scheme == "ws") ? "http" : scheme;
|
||||
|
|
@ -132,18 +130,18 @@ namespace Microsoft.AspNet.TestHost
|
|||
request.Body = Stream.Null;
|
||||
|
||||
// Response
|
||||
HttpContext.Features.Set<IHttpResponseFeature>(new ResponseFeature());
|
||||
var response = HttpContext.Response;
|
||||
httpContext.Features.Set<IHttpResponseFeature>(new ResponseFeature());
|
||||
var response = httpContext.Response;
|
||||
response.Body = Stream.Null;
|
||||
response.StatusCode = 200;
|
||||
|
||||
// WebSocket
|
||||
HttpContext.Features.Set<IHttpWebSocketFeature>(this);
|
||||
httpContext.Features.Set<IHttpWebSocketFeature>(this);
|
||||
}
|
||||
|
||||
public void PipelineComplete()
|
||||
{
|
||||
PipelineFailed(new InvalidOperationException("Incomplete handshake, status code: " + HttpContext.Response.StatusCode));
|
||||
PipelineFailed(new InvalidOperationException("Incomplete handshake, status code: " + Context.HttpContext.Response.StatusCode));
|
||||
}
|
||||
|
||||
public void PipelineFailed(Exception ex)
|
||||
|
|
@ -153,16 +151,17 @@ namespace Microsoft.AspNet.TestHost
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
if (HttpContext != null)
|
||||
{
|
||||
_factory.Dispose(HttpContext);
|
||||
}
|
||||
if (_serverWebSocket != null)
|
||||
{
|
||||
_serverWebSocket.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal void ServerCleanup(Exception exception)
|
||||
{
|
||||
_application.DisposeContext(Context, exception);
|
||||
}
|
||||
|
||||
private string CreateRequestKey()
|
||||
{
|
||||
byte[] data = new byte[16];
|
||||
|
|
@ -181,7 +180,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
|
||||
Task<WebSocket> IHttpWebSocketFeature.AcceptAsync(WebSocketAcceptContext context)
|
||||
{
|
||||
HttpContext.Response.StatusCode = 101; // Switching Protocols
|
||||
Context.HttpContext.Response.StatusCode = 101; // Switching Protocols
|
||||
|
||||
var websockets = TestWebSocket.CreatePair(context.SubProtocol);
|
||||
_clientWebSocketTcs.SetResult(websockets.Item1);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ using Microsoft.AspNet.Hosting.Server;
|
|||
using Microsoft.AspNet.Hosting.Startup;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Server.Features;
|
||||
using Microsoft.AspNet.Testing.xunit;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
|
@ -477,11 +476,21 @@ namespace Microsoft.AspNet.Hosting
|
|||
return new WebHostBuilder(config ?? new ConfigurationBuilder().Build());
|
||||
}
|
||||
|
||||
public void Start(RequestDelegate requestDelegate)
|
||||
public void Start<TContext>(IHttpApplication<TContext> application)
|
||||
{
|
||||
var startInstance = new StartInstance(requestDelegate);
|
||||
var startInstance = new StartInstance();
|
||||
_startInstances.Add(startInstance);
|
||||
requestDelegate(new DefaultHttpContext(Features));
|
||||
var context = application.CreateContext(Features);
|
||||
try
|
||||
{
|
||||
application.ProcessRequestAsync(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
application.DisposeContext(context, ex);
|
||||
throw;
|
||||
}
|
||||
application.DisposeContext(context, null);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -504,13 +513,6 @@ namespace Microsoft.AspNet.Hosting
|
|||
|
||||
private class StartInstance : IDisposable
|
||||
{
|
||||
private readonly RequestDelegate _application;
|
||||
|
||||
public StartInstance(RequestDelegate application)
|
||||
{
|
||||
_application = application;
|
||||
}
|
||||
|
||||
public int DisposeCalls { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -174,9 +175,22 @@ namespace Microsoft.AspNet.Hosting
|
|||
|
||||
}
|
||||
|
||||
public void Start(RequestDelegate requestDelegate)
|
||||
public void Start<TContext>(IHttpApplication<TContext> application)
|
||||
{
|
||||
RequestDelegate = requestDelegate;
|
||||
RequestDelegate = async ctx =>
|
||||
{
|
||||
var httpContext = application.CreateContext(ctx.Features);
|
||||
try
|
||||
{
|
||||
await application.ProcessRequestAsync(httpContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
application.DisposeContext(httpContext, ex);
|
||||
throw;
|
||||
}
|
||||
application.DisposeContext(httpContext, null);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,22 +7,22 @@ using System.Linq;
|
|||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Hosting.Server;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Testing.xunit;
|
||||
using Xunit;
|
||||
using Context = Microsoft.AspNet.Hosting.Internal.HostingApplication.Context;
|
||||
|
||||
namespace Microsoft.AspNet.TestHost
|
||||
{
|
||||
public class ClientHandlerTests
|
||||
{
|
||||
private IHttpContextFactory _httpContextFactory = new HttpContextFactory(new HttpContextAccessor());
|
||||
|
||||
[Fact]
|
||||
public Task ExpectedKeysAreAvailable()
|
||||
{
|
||||
var handler = new ClientHandler(context =>
|
||||
var handler = new ClientHandler(new PathString("/A/Path/"), new DummyApplication(context =>
|
||||
{
|
||||
// TODO: Assert.True(context.RequestAborted.CanBeCanceled);
|
||||
Assert.Equal("HTTP/1.1", context.Request.Protocol);
|
||||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.AspNet.TestHost
|
|||
Assert.Equal("example.com", context.Request.Host.Value);
|
||||
|
||||
return Task.FromResult(0);
|
||||
}, new PathString("/A/Path/"), _httpContextFactory);
|
||||
}));
|
||||
var httpClient = new HttpClient(handler);
|
||||
return httpClient.GetAsync("https://example.com/A/Path/and/file.txt?and=query");
|
||||
}
|
||||
|
|
@ -48,13 +48,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
[Fact]
|
||||
public Task SingleSlashNotMovedToPathBase()
|
||||
{
|
||||
var handler = new ClientHandler(context =>
|
||||
var handler = new ClientHandler(new PathString(""), new DummyApplication(context =>
|
||||
{
|
||||
Assert.Equal("", context.Request.PathBase.Value);
|
||||
Assert.Equal("/", context.Request.Path.Value);
|
||||
|
||||
return Task.FromResult(0);
|
||||
}, new PathString(""), _httpContextFactory);
|
||||
}));
|
||||
var httpClient = new HttpClient(handler);
|
||||
return httpClient.GetAsync("https://example.com/");
|
||||
}
|
||||
|
|
@ -63,14 +63,14 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task ResubmitRequestWorks()
|
||||
{
|
||||
int requestCount = 1;
|
||||
var handler = new ClientHandler(context =>
|
||||
var handler = new ClientHandler(PathString.Empty, new DummyApplication(context =>
|
||||
{
|
||||
int read = context.Request.Body.Read(new byte[100], 0, 100);
|
||||
Assert.Equal(11, read);
|
||||
|
||||
context.Response.Headers["TestHeader"] = "TestValue:" + requestCount++;
|
||||
return Task.FromResult(0);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
}));
|
||||
|
||||
HttpMessageInvoker invoker = new HttpMessageInvoker(handler);
|
||||
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, "https://example.com/");
|
||||
|
|
@ -86,11 +86,11 @@ namespace Microsoft.AspNet.TestHost
|
|||
[Fact]
|
||||
public async Task MiddlewareOnlySetsHeaders()
|
||||
{
|
||||
var handler = new ClientHandler(context =>
|
||||
var handler = new ClientHandler(PathString.Empty, new DummyApplication(context =>
|
||||
{
|
||||
context.Response.Headers["TestHeader"] = "TestValue";
|
||||
return Task.FromResult(0);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
}));
|
||||
var httpClient = new HttpClient(handler);
|
||||
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/");
|
||||
Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
|
||||
|
|
@ -100,11 +100,11 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task BlockingMiddlewareShouldNotBlockClient()
|
||||
{
|
||||
ManualResetEvent block = new ManualResetEvent(false);
|
||||
var handler = new ClientHandler(context =>
|
||||
var handler = new ClientHandler(PathString.Empty, new DummyApplication(context =>
|
||||
{
|
||||
block.WaitOne();
|
||||
return Task.FromResult(0);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
}));
|
||||
var httpClient = new HttpClient(handler);
|
||||
Task<HttpResponseMessage> task = httpClient.GetAsync("https://example.com/");
|
||||
Assert.False(task.IsCompleted);
|
||||
|
|
@ -117,13 +117,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task HeadersAvailableBeforeBodyFinished()
|
||||
{
|
||||
ManualResetEvent block = new ManualResetEvent(false);
|
||||
var handler = new ClientHandler(async context =>
|
||||
var handler = new ClientHandler(PathString.Empty, new DummyApplication(async context =>
|
||||
{
|
||||
context.Response.Headers["TestHeader"] = "TestValue";
|
||||
await context.Response.WriteAsync("BodyStarted,");
|
||||
block.WaitOne();
|
||||
await context.Response.WriteAsync("BodyFinished");
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
}));
|
||||
var httpClient = new HttpClient(handler);
|
||||
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||
HttpCompletionOption.ResponseHeadersRead);
|
||||
|
|
@ -136,13 +136,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task FlushSendsHeaders()
|
||||
{
|
||||
ManualResetEvent block = new ManualResetEvent(false);
|
||||
var handler = new ClientHandler(async context =>
|
||||
var handler = new ClientHandler(PathString.Empty, new DummyApplication(async context =>
|
||||
{
|
||||
context.Response.Headers["TestHeader"] = "TestValue";
|
||||
context.Response.Body.Flush();
|
||||
block.WaitOne();
|
||||
await context.Response.WriteAsync("BodyFinished");
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
}));
|
||||
var httpClient = new HttpClient(handler);
|
||||
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||
HttpCompletionOption.ResponseHeadersRead);
|
||||
|
|
@ -155,13 +155,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task ClientDisposalCloses()
|
||||
{
|
||||
ManualResetEvent block = new ManualResetEvent(false);
|
||||
var handler = new ClientHandler(context =>
|
||||
var handler = new ClientHandler(PathString.Empty, new DummyApplication(context =>
|
||||
{
|
||||
context.Response.Headers["TestHeader"] = "TestValue";
|
||||
context.Response.Body.Flush();
|
||||
block.WaitOne();
|
||||
return Task.FromResult(0);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
}));
|
||||
var httpClient = new HttpClient(handler);
|
||||
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||
HttpCompletionOption.ResponseHeadersRead);
|
||||
|
|
@ -180,13 +180,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task ClientCancellationAborts()
|
||||
{
|
||||
ManualResetEvent block = new ManualResetEvent(false);
|
||||
var handler = new ClientHandler(context =>
|
||||
var handler = new ClientHandler(PathString.Empty, new DummyApplication(context =>
|
||||
{
|
||||
context.Response.Headers["TestHeader"] = "TestValue";
|
||||
context.Response.Body.Flush();
|
||||
block.WaitOne();
|
||||
return Task.FromResult(0);
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
}));
|
||||
var httpClient = new HttpClient(handler);
|
||||
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||
HttpCompletionOption.ResponseHeadersRead);
|
||||
|
|
@ -205,10 +205,10 @@ namespace Microsoft.AspNet.TestHost
|
|||
[Fact]
|
||||
public Task ExceptionBeforeFirstWriteIsReported()
|
||||
{
|
||||
var handler = new ClientHandler(context =>
|
||||
var handler = new ClientHandler(PathString.Empty, new DummyApplication(context =>
|
||||
{
|
||||
throw new InvalidOperationException("Test Exception");
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
}));
|
||||
var httpClient = new HttpClient(handler);
|
||||
return Assert.ThrowsAsync<InvalidOperationException>(() => httpClient.GetAsync("https://example.com/",
|
||||
HttpCompletionOption.ResponseHeadersRead));
|
||||
|
|
@ -219,13 +219,13 @@ namespace Microsoft.AspNet.TestHost
|
|||
public async Task ExceptionAfterFirstWriteIsReported()
|
||||
{
|
||||
ManualResetEvent block = new ManualResetEvent(false);
|
||||
var handler = new ClientHandler(async context =>
|
||||
var handler = new ClientHandler(PathString.Empty, new DummyApplication(async context =>
|
||||
{
|
||||
context.Response.Headers["TestHeader"] = "TestValue";
|
||||
await context.Response.WriteAsync("BodyStarted");
|
||||
block.WaitOne();
|
||||
throw new InvalidOperationException("Test Exception");
|
||||
}, PathString.Empty, _httpContextFactory);
|
||||
}));
|
||||
var httpClient = new HttpClient(handler);
|
||||
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||
HttpCompletionOption.ResponseHeadersRead);
|
||||
|
|
@ -234,5 +234,33 @@ namespace Microsoft.AspNet.TestHost
|
|||
var ex = await Assert.ThrowsAsync<HttpRequestException>(() => response.Content.ReadAsStringAsync());
|
||||
Assert.IsType<InvalidOperationException>(ex.GetBaseException());
|
||||
}
|
||||
|
||||
private class DummyApplication : IHttpApplication<Context>
|
||||
{
|
||||
RequestDelegate _application;
|
||||
|
||||
public DummyApplication(RequestDelegate application)
|
||||
{
|
||||
_application = application;
|
||||
}
|
||||
|
||||
public Context CreateContext(IFeatureCollection contextFeatures)
|
||||
{
|
||||
return new Context()
|
||||
{
|
||||
HttpContext = new DefaultHttpContext(contextFeatures)
|
||||
};
|
||||
}
|
||||
|
||||
public void DisposeContext(Context context, Exception exception)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Task ProcessRequestAsync(Context context)
|
||||
{
|
||||
return _application(context.HttpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Hosting.Startup;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Features;
|
||||
using Microsoft.AspNet.Http.Features.Internal;
|
||||
|
|
@ -393,6 +392,9 @@ namespace Microsoft.AspNet.TestHost
|
|||
diagnosticListener.SubscribeWithAdapter(listener);
|
||||
var result = await server.CreateClient().GetStringAsync("/path");
|
||||
|
||||
// This ensures that all diagnostics are completely written to the diagnostic listener
|
||||
Thread.Sleep(1000);
|
||||
|
||||
Assert.Equal("Hello World", result);
|
||||
Assert.NotNull(listener.BeginRequest?.HttpContext);
|
||||
Assert.NotNull(listener.EndRequest?.HttpContext);
|
||||
|
|
@ -414,6 +416,9 @@ namespace Microsoft.AspNet.TestHost
|
|||
var listener = new TestDiagnosticListener();
|
||||
diagnosticListener.SubscribeWithAdapter(listener);
|
||||
await Assert.ThrowsAsync<Exception>(() => server.CreateClient().GetAsync("/path"));
|
||||
|
||||
// This ensures that all diagnostics are completely written to the diagnostic listener
|
||||
Thread.Sleep(1000);
|
||||
|
||||
Assert.NotNull(listener.BeginRequest?.HttpContext);
|
||||
Assert.Null(listener.EndRequest?.HttpContext);
|
||||
|
|
|
|||
Loading…
Reference in New Issue