// 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.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.IdentityModel.Claims;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Hosting;
using System.Web.Http.Routing;
using System.Web.Http.SelfHost.Channels;
using System.Web.Http.SelfHost.Properties;
using System.Web.Http.SelfHost.ServiceModel.Channels;
namespace System.Web.Http.SelfHost
{
///
/// Implementation of an which listens directly to HTTP.
///
public sealed class HttpSelfHostServer : HttpServer
{
internal const string SecurityKey = "Security";
private static readonly AsyncCallback _onCloseListenerComplete = new AsyncCallback(OnCloseListenerComplete);
private static readonly AsyncCallback _onCloseChannelComplete = new AsyncCallback(OnCloseChannelComplete);
private static readonly TimeSpan _acceptTimeout = TimeSpan.MaxValue;
private static readonly TimeSpan _receiveTimeout = TimeSpan.MaxValue;
private static readonly Func _retrieveClientCertificate = new Func(RetrieveClientCertificate);
// Window size gets increased if the ratio of outstanding requests to the window size is greater than IncreaseWindowSizeRatio
// Window size gets decreased if the ratio of outstanding requests to the window size is less than DecreaseWindowsSizeRatio
private const double IncreaseWindowSizeRatio = .8;
private const double DecreaseWindowSizeRatio = .2;
private const int InitialWindowSizeMultiplier = 8;
private const int MinimumWindowSizeMultiplier = 1;
private static readonly int InitialWindowSize = HttpSelfHostConfiguration.MultiplyByProcessorCount(InitialWindowSizeMultiplier);
private static readonly int MinimumWindowSize = HttpSelfHostConfiguration.MultiplyByProcessorCount(MinimumWindowSizeMultiplier);
private AsyncCallback _onOpenListenerComplete;
private AsyncCallback _onAcceptChannelComplete;
private AsyncCallback _onOpenChannelComplete;
private AsyncCallback _onReceiveRequestContextComplete;
private AsyncCallback _onReplyComplete;
private ConcurrentBag _channels = new ConcurrentBag();
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed;
private HttpSelfHostConfiguration _configuration;
private IChannelListener _listener;
private TaskCompletionSource _openTaskCompletionSource;
private TaskCompletionSource _closeTaskCompletionSource;
// State: 0 = new, 1 = open, 2 = closed
private int _state;
private int _requestsOutstanding;
private int _windowSize;
private readonly object _windowSizeLock = new object();
///
/// Initializes a new instance of the class.
///
/// The configuration.
public HttpSelfHostServer(HttpSelfHostConfiguration configuration)
: base(configuration)
{
if (configuration == null)
{
throw Error.ArgumentNull("configuration");
}
_configuration = configuration;
InitializeCallbacks();
}
///
/// Initializes a new instance of the class.
///
/// The configuration.
/// The dispatcher.
public HttpSelfHostServer(HttpSelfHostConfiguration configuration, HttpMessageHandler dispatcher)
: base(configuration, dispatcher)
{
if (configuration == null)
{
throw Error.ArgumentNull("configuration");
}
_configuration = configuration;
InitializeCallbacks();
}
///
/// Initialize async callbacks.
///
private void InitializeCallbacks()
{
_onOpenListenerComplete = new AsyncCallback(OnOpenListenerComplete);
_onAcceptChannelComplete = new AsyncCallback(OnAcceptChannelComplete);
_onOpenChannelComplete = new AsyncCallback(OnOpenChannelComplete);
_onReceiveRequestContextComplete = new AsyncCallback(OnReceiveRequestContextComplete);
_onReplyComplete = new AsyncCallback(OnReplyComplete);
}
///
/// Opens the current instance.
///
/// A representing the asynchronous open operation. Once this task completes successfully the server is running.
public Task OpenAsync()
{
if (Interlocked.CompareExchange(ref _state, 1, 0) == 1)
{
throw Error.InvalidOperation(SRResources.HttpServerAlreadyRunning, typeof(HttpSelfHostServer).Name);
}
_openTaskCompletionSource = new TaskCompletionSource();
BeginOpenListener(this);
return _openTaskCompletionSource.Task;
}
///
/// Closes the current instance.
///
/// A representing the asynchronous close operation.
public Task CloseAsync()
{
if (Interlocked.CompareExchange(ref _state, 2, 1) != 1)
{
return TaskHelpers.Completed();
}
_closeTaskCompletionSource = new TaskCompletionSource();
// Cancel requests currently being processed
_cancellationTokenSource.Cancel();
BeginCloseListener(this);
return _closeTaskCompletionSource.Task;
}
///
/// 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 (_cancellationTokenSource != null)
{
_cancellationTokenSource.Dispose();
_cancellationTokenSource = null;
}
}
base.Dispose(disposing);
}
// async void is OK here. This is a fire and forget method and any exceptions that occur will be turned into
// HTTP responses that get sent back to clients.
private async void ProcessRequestContext(ChannelContext channelContext, RequestContext requestContext)
{
Contract.Assert(channelContext != null);
Contract.Assert(requestContext != null);
HttpResponseMessage response = await SendAsync(channelContext, requestContext);
Message reply = response.ToMessage();
BeginReply(new ReplyContext(channelContext, requestContext, reply));
}
private async Task SendAsync(ChannelContext channelContext, RequestContext requestContext)
{
HttpRequestMessage request = null;
try
{
request = CreateHttpRequestMessage(requestContext);
}
catch
{
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
// Submit request up the stack
try
{
HttpResponseMessage response = await channelContext.Server.SendAsync(request, channelContext.Server._cancellationTokenSource.Token);
if (response == null)
{
response = request.CreateResponse(HttpStatusCode.InternalServerError);
}
return response;
}
catch (OperationCanceledException operationCanceledException)
{
return request.CreateErrorResponse(HttpStatusCode.ServiceUnavailable, SRResources.RequestCancelled, operationCanceledException);
}
}
private HttpRequestMessage CreateHttpRequestMessage(RequestContext requestContext)
{
// Get the HTTP request from the WCF Message
HttpRequestMessage request = requestContext.RequestMessage.ToHttpRequestMessage();
if (request == null)
{
throw Error.InvalidOperation(SRResources.HttpMessageHandlerInvalidMessage, requestContext.RequestMessage.GetType());
}
// create principal information and add it the request for the windows auth case
SetCurrentPrincipal(request);
HttpRequestContext httpRequestContext = new SelfHostHttpRequestContext(requestContext, _configuration,
request);
request.SetRequestContext(httpRequestContext);
// The following two properties are set for backwards compatibility only. The request context controls the
// behavior for all cases except when accessing the property directly by key.
// Add the retrieve client certificate delegate to the property bag to enable lookup later on
request.Properties.Add(HttpPropertyKeys.RetrieveClientCertificateDelegateKey, _retrieveClientCertificate);
// Add information about whether the request is local or not
request.Properties.Add(HttpPropertyKeys.IsLocalKey, new Lazy(() => IsLocal(requestContext.RequestMessage)));
return request;
}
internal static bool IsLocal(Message message)
{
RemoteEndpointMessageProperty remoteEndpointProperty;
if (message.Properties.TryGetValue(RemoteEndpointMessageProperty.Name, out remoteEndpointProperty))
{
IPAddress remoteAddress;
if (IPAddress.TryParse(remoteEndpointProperty.Address, out remoteAddress))
{
return IPAddress.IsLoopback(remoteAddress);
}
}
return false;
}
private static void SetCurrentPrincipal(HttpRequestMessage request)
{
SecurityMessageProperty property = request.GetSecurityMessageProperty();
if (property != null)
{
ServiceSecurityContext context = property.ServiceSecurityContext;
if (context != null && context.PrimaryIdentity != null)
{
WindowsIdentity windowsIdentity = context.PrimaryIdentity as WindowsIdentity;
if (windowsIdentity != null)
{
Thread.CurrentPrincipal = new WindowsPrincipal(windowsIdentity);
}
}
}
}
internal static X509Certificate2 RetrieveClientCertificate(HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
SecurityMessageProperty property = request.GetSecurityMessageProperty();
X509Certificate2 result = null;
if (property != null && property.ServiceSecurityContext != null && property.ServiceSecurityContext.AuthorizationContext != null)
{
X509CertificateClaimSet certClaimSet = null;
foreach (ClaimSet claimSet in property.ServiceSecurityContext.AuthorizationContext.ClaimSets)
{
certClaimSet = claimSet as X509CertificateClaimSet;
if (certClaimSet != null)
{
result = certClaimSet.X509Certificate;
break;
}
}
}
return result;
}
private static void CancelTask(TaskCompletionSource taskCompletionSource)
{
Contract.Assert(taskCompletionSource != null);
taskCompletionSource.TrySetCanceled();
}
private static void FaultTask(TaskCompletionSource taskCompletionSource, Exception exception)
{
Contract.Assert(taskCompletionSource != null);
taskCompletionSource.TrySetException(exception);
}
private static void CompleteTask(TaskCompletionSource taskCompletionSource)
{
Contract.Assert(taskCompletionSource != null);
taskCompletionSource.TrySetResult(true);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private void BeginOpenListener(HttpSelfHostServer server)
{
Contract.Assert(server != null);
try
{
// Create WCF HTTP transport channel
HttpBinding binding = new HttpBinding();
// Get it configured
BindingParameterCollection bindingParameters = server._configuration.ConfigureBinding(binding);
if (bindingParameters == null)
{
bindingParameters = new BindingParameterCollection();
}
// Build channel listener
server._listener = binding.BuildChannelListener(server._configuration.BaseAddress, bindingParameters);
if (server._listener == null)
{
throw Error.InvalidOperation(SRResources.InvalidChannelListener, typeof(IChannelListener).Name, typeof(IReplyChannel).Name);
}
IAsyncResult result = server._listener.BeginOpen(_onOpenListenerComplete, server);
if (result.CompletedSynchronously)
{
OpenListenerComplete(result);
}
}
catch (Exception e)
{
FaultTask(server._openTaskCompletionSource, e);
}
}
private void OnOpenListenerComplete(IAsyncResult result)
{
Contract.Assert(result != null);
if (result.CompletedSynchronously)
{
return;
}
OpenListenerComplete(result);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private void OpenListenerComplete(IAsyncResult result)
{
HttpSelfHostServer server = (HttpSelfHostServer)result.AsyncState;
Contract.Assert(server != null);
Contract.Assert(server._listener != null);
try
{
server._listener.EndOpen(result);
// Start accepting channel
BeginAcceptChannel(server);
}
catch (Exception e)
{
FaultTask(server._openTaskCompletionSource, e);
}
}
private void BeginAcceptChannel(HttpSelfHostServer server)
{
Contract.Assert(server != null);
Contract.Assert(server._listener != null);
IAsyncResult result = BeginTryAcceptChannel(server, _onAcceptChannelComplete);
if (result.CompletedSynchronously)
{
AcceptChannelComplete(result);
}
}
private void OnAcceptChannelComplete(IAsyncResult result)
{
if (result.CompletedSynchronously)
{
return;
}
AcceptChannelComplete(result);
}
private void AcceptChannelComplete(IAsyncResult result)
{
Contract.Assert(result != null);
HttpSelfHostServer server = (HttpSelfHostServer)result.AsyncState;
Contract.Assert(server != null, "host cannot be null");
Contract.Assert(server._listener != null, "host.listener cannot be null");
IReplyChannel channel;
if (EndTryAcceptChannel(result, out channel))
{
// If we didn't get a channel then we stop accepting new channels
if (channel != null)
{
server._channels.Add(channel);
BeginOpenChannel(new ChannelContext(server, channel));
}
else
{
CancelTask(server._openTaskCompletionSource);
}
}
else
{
// Start accepting next channel
BeginAcceptChannel(server);
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private static IAsyncResult BeginTryAcceptChannel(HttpSelfHostServer server, AsyncCallback callback)
{
Contract.Assert(server != null);
Contract.Assert(server._listener != null);
Contract.Assert(callback != null);
try
{
return server._listener.BeginAcceptChannel(_acceptTimeout, callback, server);
}
catch (CommunicationObjectAbortedException)
{
return new CompletedAsyncResult(true, callback, server);
}
catch (CommunicationObjectFaultedException)
{
return new CompletedAsyncResult(true, callback, server);
}
catch (TimeoutException)
{
return new CompletedAsyncResult(false, callback, server);
}
catch (CommunicationException)
{
return new CompletedAsyncResult(false, callback, server);
}
catch
{
return new CompletedAsyncResult(false, callback, server);
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private static bool EndTryAcceptChannel(IAsyncResult result, out IReplyChannel channel)
{
Contract.Assert(result != null);
CompletedAsyncResult handlerResult = result as CompletedAsyncResult;
if (handlerResult != null)
{
channel = null;
return CompletedAsyncResult.End(handlerResult);
}
else
{
try
{
HttpSelfHostServer server = (HttpSelfHostServer)result.AsyncState;
Contract.Assert(server != null);
Contract.Assert(server._listener != null);
channel = server._listener.EndAcceptChannel(result);
return true;
}
catch (CommunicationObjectAbortedException)
{
channel = null;
return true;
}
catch (CommunicationObjectFaultedException)
{
channel = null;
return true;
}
catch (TimeoutException)
{
channel = null;
return false;
}
catch (CommunicationException)
{
channel = null;
return false;
}
catch
{
channel = null;
return false;
}
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private void BeginOpenChannel(ChannelContext channelContext)
{
Contract.Assert(channelContext != null);
Contract.Assert(channelContext.Channel != null);
try
{
IAsyncResult result = channelContext.Channel.BeginOpen(_onOpenChannelComplete, channelContext);
if (result.CompletedSynchronously)
{
OpenChannelComplete(result);
}
}
catch (Exception e)
{
FaultTask(channelContext.Server._openTaskCompletionSource, e);
}
}
private void OnOpenChannelComplete(IAsyncResult result)
{
Contract.Assert(result != null);
if (result.CompletedSynchronously)
{
return;
}
OpenChannelComplete(result);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private void OpenChannelComplete(IAsyncResult result)
{
ChannelContext channelContext = (ChannelContext)result.AsyncState;
Contract.Assert(channelContext != null);
Contract.Assert(channelContext.Channel != null);
try
{
channelContext.Channel.EndOpen(result);
// The channel is open and we can complete the open task
CompleteTask(channelContext.Server._openTaskCompletionSource);
// Start pumping messages
int initialWindowSize = Math.Min(InitialWindowSize, _configuration.MaxConcurrentRequests);
Interlocked.Add(ref _windowSize, initialWindowSize);
for (int index = 0; index < initialWindowSize; index++)
{
BeginReceiveRequestContext(channelContext);
}
// Start accepting next channel
BeginAcceptChannel(channelContext.Server);
}
catch (Exception e)
{
FaultTask(channelContext.Server._openTaskCompletionSource, e);
}
}
private void BeginReceiveRequestContext(ChannelContext context)
{
Contract.Assert(context != null);
if (context.Channel.State != CommunicationState.Opened)
{
return;
}
IAsyncResult result = BeginTryReceiveRequestContext(context, _onReceiveRequestContextComplete);
if (result.CompletedSynchronously)
{
ReceiveRequestContextComplete(result);
}
}
private void OnReceiveRequestContextComplete(IAsyncResult result)
{
if (result.CompletedSynchronously)
{
return;
}
ReceiveRequestContextComplete(result);
}
private void ReceiveRequestContextComplete(IAsyncResult result)
{
Contract.Assert(result != null);
ChannelContext channelContext = (ChannelContext)result.AsyncState;
Contract.Assert(channelContext != null);
System.ServiceModel.Channels.RequestContext requestContext;
if (EndTryReceiveRequestContext(result, out requestContext))
{
if (requestContext != null)
{
Interlocked.Increment(ref _requestsOutstanding);
if (TryIncreaseWindowSize())
{
// Spin off an additional BeginReceiveRequest to increase the window size by 1
BeginReceiveRequestContext(channelContext);
}
ProcessRequestContext(channelContext, requestContext);
}
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private static IAsyncResult BeginTryReceiveRequestContext(ChannelContext channelContext, AsyncCallback callback)
{
Contract.Assert(channelContext != null);
Contract.Assert(callback != null);
try
{
return channelContext.Channel.BeginTryReceiveRequest(_receiveTimeout, callback, channelContext);
}
catch (CommunicationObjectAbortedException)
{
return new CompletedAsyncResult(true, callback, channelContext);
}
catch (CommunicationObjectFaultedException)
{
return new CompletedAsyncResult(true, callback, channelContext);
}
catch (CommunicationException)
{
return new CompletedAsyncResult(false, callback, channelContext);
}
catch (TimeoutException)
{
return new CompletedAsyncResult(false, callback, channelContext);
}
catch
{
return new CompletedAsyncResult(false, callback, channelContext);
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private static bool EndTryReceiveRequestContext(IAsyncResult result, out System.ServiceModel.Channels.RequestContext requestContext)
{
Contract.Assert(result != null);
CompletedAsyncResult handlerResult = result as CompletedAsyncResult;
if (handlerResult != null)
{
requestContext = null;
return CompletedAsyncResult.End(handlerResult);
}
else
{
try
{
ChannelContext channelContext = (ChannelContext)result.AsyncState;
Contract.Assert(channelContext != null, "context cannot be null");
return channelContext.Channel.EndTryReceiveRequest(result, out requestContext);
}
catch (CommunicationObjectAbortedException)
{
requestContext = null;
return true;
}
catch (CommunicationObjectFaultedException)
{
requestContext = null;
return true;
}
catch (CommunicationException)
{
requestContext = null;
return false;
}
catch (TimeoutException)
{
requestContext = null;
return false;
}
catch
{
requestContext = null;
return false;
}
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private void BeginReply(ReplyContext replyContext)
{
Contract.Assert(replyContext != null);
try
{
IAsyncResult result = replyContext.RequestContext.BeginReply(replyContext.Reply, _onReplyComplete, replyContext);
if (result.CompletedSynchronously)
{
ReplyComplete(result);
}
}
catch
{
Interlocked.Decrement(ref _requestsOutstanding);
BeginNextRequest(replyContext.ChannelContext);
replyContext.Dispose();
}
}
private void OnReplyComplete(IAsyncResult result)
{
Contract.Assert(result != null);
if (result.CompletedSynchronously)
{
return;
}
ReplyComplete(result);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private void ReplyComplete(IAsyncResult result)
{
ReplyContext replyContext = (ReplyContext)result.AsyncState;
Contract.Assert(replyContext != null);
try
{
replyContext.RequestContext.EndReply(result);
}
catch
{
}
finally
{
Interlocked.Decrement(ref _requestsOutstanding);
BeginNextRequest(replyContext.ChannelContext);
replyContext.Dispose();
}
}
private void BeginNextRequest(ChannelContext context)
{
if (TryDecreaseWindowSize())
{
// Decrease the window size by 1 by avoiding calling BeginReceiveRequest
return;
}
BeginReceiveRequestContext(context);
}
private bool TryIncreaseWindowSize()
{
if (ShouldIncreaseWindowSize())
{
// If we can't get the lock, just keep the window size the same
// It's better to keep the window size the same than risk affecting performance by waiting on a lock
// And if the lock is taken, some other thread is already updating the window size to a better value
if (Monitor.TryEnter(_windowSizeLock))
{
try
{
// Recheck that we should increase the window size to guard for changes between the time we take the lock and the time we increase the window size
if (ShouldIncreaseWindowSize())
{
// Increase Window Size
_windowSize++;
return true;
}
}
finally
{
Monitor.Exit(_windowSizeLock);
}
}
}
return false;
}
private bool TryDecreaseWindowSize()
{
if (ShouldDecreaseWindowSize())
{
// If we can't get the lock, just keep the window size the same
// It's better to keep the window size the same than risk affecting performance by waiting on a lock
// And if the lock is taken, some other thread is already updating the window size to a better value
if (Monitor.TryEnter(_windowSizeLock))
{
try
{
// Recheck that we should decrease the window size to guard for changes between the time we take the lock and the time we increase the window size
if (ShouldDecreaseWindowSize())
{
_windowSize--;
return true;
}
}
finally
{
Monitor.Exit(_windowSizeLock);
}
}
}
return false;
}
private bool ShouldIncreaseWindowSize()
{
return _windowSize < _configuration.MaxConcurrentRequests && _requestsOutstanding > _windowSize * IncreaseWindowSizeRatio;
}
private bool ShouldDecreaseWindowSize()
{
return _windowSize > MinimumWindowSize && _requestsOutstanding < _windowSize * DecreaseWindowSizeRatio;
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private static void BeginCloseListener(HttpSelfHostServer server)
{
Contract.Assert(server != null);
try
{
if (server._listener != null)
{
IAsyncResult result = server._listener.BeginClose(_onCloseListenerComplete, server);
if (result.CompletedSynchronously)
{
CloseListenerComplete(result);
}
}
}
catch
{
CloseNextChannel(server);
}
}
private static void OnCloseListenerComplete(IAsyncResult result)
{
Contract.Assert(result != null);
if (result.CompletedSynchronously)
{
return;
}
CloseListenerComplete(result);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private static void CloseListenerComplete(IAsyncResult result)
{
HttpSelfHostServer server = (HttpSelfHostServer)result.AsyncState;
Contract.Assert(server != null);
Contract.Assert(server._listener != null);
try
{
server._listener.EndClose(result);
}
catch
{
}
finally
{
CloseNextChannel(server);
}
}
private static void CloseNextChannel(HttpSelfHostServer server)
{
Contract.Assert(server != null);
IReplyChannel channel;
if (server._channels.TryTake(out channel))
{
BeginCloseChannel(new ChannelContext(server, channel));
}
else
{
CompleteTask(server._closeTaskCompletionSource);
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private static void BeginCloseChannel(ChannelContext channelContext)
{
Contract.Assert(channelContext != null);
try
{
IAsyncResult result = channelContext.Channel.BeginClose(_onCloseChannelComplete, channelContext);
if (result.CompletedSynchronously)
{
CloseChannelComplete(result);
}
}
catch
{
CloseNextChannel(channelContext.Server);
}
}
private static void OnCloseChannelComplete(IAsyncResult result)
{
Contract.Assert(result != null);
if (result.CompletedSynchronously)
{
return;
}
CloseChannelComplete(result);
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
private static void CloseChannelComplete(IAsyncResult result)
{
ChannelContext channelContext = (ChannelContext)result.AsyncState;
Contract.Assert(channelContext != null);
try
{
channelContext.Channel.EndClose(result);
}
catch
{
}
finally
{
CloseNextChannel(channelContext.Server);
}
}
///
/// Provides context for receiving an instance asynchronously.
///
private class ChannelContext
{
///
/// Initializes a new instance of the class.
///
/// The host to associate with this context.
/// The channel to associate with this channel.
public ChannelContext(HttpSelfHostServer server, IReplyChannel channel)
{
Contract.Assert(server != null);
Contract.Assert(channel != null);
Server = server;
Channel = channel;
}
///
/// Gets the instance.
///
///
/// The instance.
///
public HttpSelfHostServer Server { get; private set; }
///
/// Gets the instance.
///
///
/// The instance.
///
public IReplyChannel Channel { get; private set; }
}
///
/// Provides context for sending a instance asynchronously in response
/// to a request.
///
private class ReplyContext : IDisposable
{
private bool _disposed;
///
/// Initializes a new instance of the class.
///
/// The channel context to associate with this reply context.
/// The request context to associate with this reply context.
/// The reply to associate with this reply context.
public ReplyContext(ChannelContext channelContext, RequestContext requestContext, Message reply)
{
Contract.Assert(channelContext != null);
Contract.Assert(requestContext != null);
Contract.Assert(reply != null);
ChannelContext = channelContext;
RequestContext = requestContext;
Reply = reply;
}
///
/// Gets the instance.
///
///
/// The instance.
///
public ChannelContext ChannelContext { get; private set; }
///
/// Gets the instance.
///
///
/// The instance.
///
public RequestContext RequestContext { get; private set; }
///
/// Gets the reply instance.
///
///
/// The reply instance.
///
public Message Reply { get; private set; }
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged SRResources.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases unmanaged and - optionally - managed resources
///
/// true to release both managed and unmanaged resources; false to release only unmanaged SRResources.
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// RequestContext.Close can throw if the client disconnects before it finishes receiving the response
// Catch here to avoid throwing in a Dispose method
try
{
RequestContext.Close();
}
catch
{
}
// HttpMessage.Close can throw if the request message throws in its Dispose implementation
// Catch here to avoid throwing in a Dispose method
try
{
Reply.Close();
}
catch
{
}
}
_disposed = true;
}
}
}
}
}