// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Http;
using System.Runtime.ExceptionServices;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using System.Web.Http.ExceptionHandling;
using System.Web.Http.Properties;
namespace System.Web.Http
{
///
/// Defines an implementation of an which dispatches an
/// incoming and creates an as a result.
///
public class HttpServer : DelegatingHandler
{
// _anonymousPrincipal needs thread-safe intialization so use a static field initializer
private static readonly IPrincipal _anonymousPrincipal = new GenericPrincipal(new GenericIdentity(String.Empty), new string[0]);
private readonly HttpConfiguration _configuration;
private readonly HttpMessageHandler _dispatcher;
private IExceptionLogger _exceptionLogger;
private IExceptionHandler _exceptionHandler;
private bool _disposed;
private bool _initialized = false;
private object _initializationLock = new object();
private object _initializationTarget;
///
/// Initializes a new instance of the class with default configuration and dispatcher.
///
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The configuration object is disposed as part of this class.")]
public HttpServer()
: this(new HttpConfiguration())
{
}
///
/// Initializes a new instance of the class with default dispatcher.
///
/// The used to configure this instance.
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The configuration object is disposed as part of this class.")]
public HttpServer(HttpConfiguration configuration)
: this(configuration, new HttpRoutingDispatcher(configuration))
{
}
///
/// Initializes a new instance of the class with a custom dispatcher.
///
/// Http dispatcher responsible for handling incoming requests.
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope",
Justification = "The configuration object is disposed as part of this class.")]
public HttpServer(HttpMessageHandler dispatcher)
: this(new HttpConfiguration(), dispatcher)
{
}
///
/// Initializes a new instance of the class.
///
/// The used to configure this instance.
/// Http dispatcher responsible for handling incoming requests.
[SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "principal",
Justification = "Must access Thread.CurrentPrincipal to work around problem in .NET 4.5")]
public HttpServer(HttpConfiguration configuration, HttpMessageHandler dispatcher)
{
if (configuration == null)
{
throw Error.ArgumentNull("configuration");
}
if (dispatcher == null)
{
throw Error.ArgumentNull("dispatcher");
}
// Read the thread principal to work around a problem up to .NET 4.5.1 that CurrentPrincipal creates a new instance each time it is read in async
// code if it has not been queried from the spawning thread.
IPrincipal principal = Thread.CurrentPrincipal;
_dispatcher = dispatcher;
_configuration = configuration;
}
///
/// Gets the dispatcher.
///
public HttpMessageHandler Dispatcher
{
get { return _dispatcher; }
}
///
/// Gets the .
///
public HttpConfiguration Configuration
{
get { return _configuration; }
}
/// This property is settable only for unit testing purposes.
internal IExceptionLogger ExceptionLogger
{
get
{
if (_exceptionLogger == null)
{
_exceptionLogger = ExceptionServices.GetLogger(_configuration);
}
return _exceptionLogger;
}
set
{
_exceptionLogger = value;
}
}
/// This property is settable only for unit testing purposes.
internal IExceptionHandler ExceptionHandler
{
get
{
if (_exceptionHandler == null)
{
_exceptionHandler = ExceptionServices.GetHandler(_configuration);
}
return _exceptionHandler;
}
set
{
_exceptionHandler = value;
}
}
///
/// Releases unmanaged and - optionally - managed resources
///
/// true to release both managed and unmanaged resources; false to release only unmanaged SRResources.
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
if (disposing)
{
_configuration.Dispose();
}
}
base.Dispose(disposing);
}
///
/// Dispatches an incoming .
///
/// The request to dispatch
/// The token to monitor for cancellation requests.
/// A representing the ongoing operation.
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller becomes owner.")]
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "we are converting exceptions to error responses.")]
protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
if (_disposed)
{
return request.CreateErrorResponse(HttpStatusCode.ServiceUnavailable, SRResources.HttpServerDisposed);
}
// The first request initializes the server
EnsureInitialized();
// Capture current synchronization context and add it as a parameter to the request
SynchronizationContext context = SynchronizationContext.Current;
if (context != null)
{
request.SetSynchronizationContext(context);
}
// Add HttpConfiguration object as a parameter to the request
request.SetConfiguration(_configuration);
// Ensure we have a principal, even if the host didn't give us one
IPrincipal originalPrincipal = Thread.CurrentPrincipal;
if (originalPrincipal == null)
{
Thread.CurrentPrincipal = _anonymousPrincipal;
}
// Ensure we have a principal on the request context (if there is a request context).
HttpRequestContext requestContext = request.GetRequestContext();
if (requestContext == null)
{
requestContext = new RequestBackedHttpRequestContext(request);
// if the host did not set a request context we will also set it back to the request.
request.SetRequestContext(requestContext);
}
try
{
ExceptionDispatchInfo exceptionInfo;
try
{
return await base.SendAsync(request, cancellationToken);
}
catch (OperationCanceledException)
{
// Propogate the canceled task without calling exception loggers or handlers.
throw;
}
catch (HttpResponseException exception)
{
return exception.Response;
}
catch (Exception exception)
{
exceptionInfo = ExceptionDispatchInfo.Capture(exception);
}
Debug.Assert(exceptionInfo.SourceException != null);
ExceptionContext exceptionContext = new ExceptionContext(exceptionInfo.SourceException,
ExceptionCatchBlocks.HttpServer, request);
await ExceptionLogger.LogAsync(exceptionContext, cancellationToken);
HttpResponseMessage response = await ExceptionHandler.HandleAsync(exceptionContext,
cancellationToken);
if (response == null)
{
exceptionInfo.Throw();
}
return response;
}
finally
{
Thread.CurrentPrincipal = originalPrincipal;
}
}
private void EnsureInitialized()
{
LazyInitializer.EnsureInitialized(ref _initializationTarget, ref _initialized, ref _initializationLock, () =>
{
Initialize();
return null;
});
}
///
/// Prepares the server for operation.
///
///
/// This method must be called after all configuration is complete
/// but before the first request is processed.
///
protected virtual void Initialize()
{
// Do final initialization of the configuration.
// It is considered immutable from this point forward.
_configuration.EnsureInitialized();
// Create pipeline
InnerHandler = HttpClientFactory.CreatePipeline(_dispatcher, _configuration.MessageHandlers);
}
private static HttpConfiguration EnsureNonNull(HttpConfiguration configuration)
{
if (configuration == null)
{
throw new ArgumentNullException("configuration");
}
return configuration;
}
}
}