// 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.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.IdentityModel.Selectors;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.Web.Http.SelfHost.Channels;
using System.Web.Http.SelfHost.Properties;
using System.Web.Http.SelfHost.ServiceModel;
using System.Web.Http.SelfHost.ServiceModel.Channels;
namespace System.Web.Http.SelfHost
{
///
/// The configuration class for Http Services
///
public class HttpSelfHostConfiguration : HttpConfiguration
{
private const int DefaultMaxConcurrentRequests = 100;
private const int DefaultMaxBufferSize = 64 * 1024;
private const int DefaultReceivedMessageSize = 64 * 1024;
private const int MinConcurrentRequests = 1;
private const int MinBufferSize = 1;
private const int MinReceivedMessageSize = 1;
private static readonly TimeSpan DefaultReceiveTimeout = new TimeSpan(0, 10, 0);
private static readonly TimeSpan DefaultSendTimeout = new TimeSpan(0, 1, 0);
private Uri _baseAddress;
private int _maxConcurrentRequests;
private ServiceCredentials _credentials = new ServiceCredentials();
private HttpClientCredentialType _clientCredentialType = HttpClientCredentialType.None;
private TransferMode _transferMode;
private int _maxBufferSize = DefaultMaxBufferSize;
private bool _maxBufferSizeIsInitialized;
private long _maxReceivedMessageSize = DefaultReceivedMessageSize;
private TimeSpan _receiveTimeout = DefaultReceiveTimeout;
private TimeSpan _sendTimeout = DefaultSendTimeout;
private HostNameComparisonMode _hostNameComparisonMode;
///
/// Initializes a new instance of the class.
///
/// The base address.
public HttpSelfHostConfiguration(string baseAddress)
: this(CreateBaseAddress(baseAddress))
{
}
///
/// Initializes a new instance of the class.
///
/// The base address.
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "caller owns object")]
public HttpSelfHostConfiguration(Uri baseAddress)
: base(new HttpRouteCollection(ValidateBaseAddress(baseAddress).AbsolutePath))
{
_baseAddress = ValidateBaseAddress(baseAddress);
_maxConcurrentRequests = MultiplyByProcessorCount(DefaultMaxConcurrentRequests);
_maxBufferSize = TransportDefaults.MaxBufferSize;
_maxReceivedMessageSize = TransportDefaults.MaxReceivedMessageSize;
}
///
/// Gets the base address.
///
///
/// The base address.
///
public Uri BaseAddress
{
get { return _baseAddress; }
}
///
/// Gets or sets the upper limit of how many concurrent instances
/// can be processed at any given time. The default is 100 times the number of CPU cores.
///
///
/// The maximum concurrent instances processed at any given time.
///
public int MaxConcurrentRequests
{
get { return _maxConcurrentRequests; }
set
{
if (value < MinConcurrentRequests)
{
throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, MinConcurrentRequests);
}
_maxConcurrentRequests = value;
}
}
///
/// Gets or sets the transfer mode.
///
///
/// The transfer mode.
///
public TransferMode TransferMode
{
get { return _transferMode; }
set
{
TransferModeHelper.Validate(value, "value");
_transferMode = value;
}
}
///
/// Specifies how the host name should be used in URI comparisons when dispatching an incoming message.
///
public HostNameComparisonMode HostNameComparisonMode
{
get { return _hostNameComparisonMode; }
set
{
HostNameComparisonModeHelper.Validate(value, "value");
_hostNameComparisonMode = value;
}
}
///
/// Gets or sets the size of the max buffer.
///
///
/// The size of the max buffer.
///
public int MaxBufferSize
{
get
{
if (_maxBufferSizeIsInitialized || TransferMode != TransferMode.Buffered)
{
return _maxBufferSize;
}
long maxReceivedMessageSize = MaxReceivedMessageSize;
if (maxReceivedMessageSize > Int32.MaxValue)
{
return Int32.MaxValue;
}
return (int)maxReceivedMessageSize;
}
set
{
if (value < MinBufferSize)
{
throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, MinBufferSize);
}
_maxBufferSizeIsInitialized = true;
_maxBufferSize = value;
}
}
///
/// Gets or sets the size of the max received message.
///
///
/// The size of the max received message.
///
public long MaxReceivedMessageSize
{
get { return _maxReceivedMessageSize; }
set
{
if (value < MinReceivedMessageSize)
{
throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, MinReceivedMessageSize);
}
_maxReceivedMessageSize = value;
}
}
///
/// Gets or sets the interval of time that a connection can remain inactive, during which no application messages are received, before it is dropped.
///
///
/// The interval of time that a connection can remain inactive, during which no application messages are received, before it is dropped.
///
public TimeSpan ReceiveTimeout
{
get { return _receiveTimeout; }
set
{
if (value < TimeSpan.Zero)
{
throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, TimeSpan.Zero);
}
_receiveTimeout = value;
}
}
///
/// Gets or sets the interval of time provided for a write operation to complete before the transport raises an exception.
///
///
/// The interval of time provided for a write operation to complete before the transport raises an exception.
///
public TimeSpan SendTimeout
{
get { return _sendTimeout; }
set
{
if (value < TimeSpan.Zero)
{
throw Error.ArgumentMustBeGreaterThanOrEqualTo("value", value, TimeSpan.Zero);
}
_sendTimeout = value;
}
}
///
/// Gets or sets UserNamePasswordValidator so that it can be used to validate the username and password
/// sent over HTTP or HTTPS
///
///
/// The server certificate.
///
public UserNamePasswordValidator UserNamePasswordValidator
{
get { return _credentials.UserNameAuthentication.CustomUserNamePasswordValidator; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_clientCredentialType = HttpClientCredentialType.Basic;
_credentials.UserNameAuthentication.CustomUserNamePasswordValidator = value;
_credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
}
}
///
/// Gets or sets X509CertificateValidator so that it can be used to validate the client certificate
/// sent over HTTPS
///
///
/// The server certificate.
///
public X509CertificateValidator X509CertificateValidator
{
get { return _credentials.ClientCertificate.Authentication.CustomCertificateValidator; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_clientCredentialType = HttpClientCredentialType.Certificate;
_credentials.ClientCertificate.Authentication.CustomCertificateValidator = value;
_credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
}
}
///
/// Gets/Sets the ClientCredentialType that server is expecting.
///
///
/// The default value is HttpClientCredentialType.None.
///
public HttpClientCredentialType ClientCredentialType
{
get { return _clientCredentialType; }
set { _clientCredentialType = value; }
}
///
/// Internal method called to configure settings.
///
/// Http binding.
/// The to use when building the or null if no binding parameters are present.
internal BindingParameterCollection ConfigureBinding(HttpBinding httpBinding)
{
return OnConfigureBinding(httpBinding);
}
///
/// Called to apply the configuration on the endpoint level.
///
/// Http endpoint.
/// The to use when building the or null if no binding parameters are present.
protected virtual BindingParameterCollection OnConfigureBinding(HttpBinding httpBinding)
{
if (httpBinding == null)
{
throw Error.ArgumentNull("httpBinding");
}
if (_clientCredentialType != HttpClientCredentialType.Basic && _credentials.UserNameAuthentication.CustomUserNamePasswordValidator != null)
{
throw Error.InvalidOperation(SRResources.CannotUseOtherClientCredentialTypeWithUserNamePasswordValidator);
}
if (_clientCredentialType != HttpClientCredentialType.Certificate && _credentials.ClientCertificate.Authentication.CustomCertificateValidator != null)
{
throw Error.InvalidOperation(SRResources.CannotUseOtherClientCredentialTypeWithX509CertificateValidator);
}
httpBinding.MaxBufferSize = MaxBufferSize;
httpBinding.MaxReceivedMessageSize = MaxReceivedMessageSize;
httpBinding.TransferMode = TransferMode;
httpBinding.HostNameComparisonMode = HostNameComparisonMode;
httpBinding.ReceiveTimeout = ReceiveTimeout;
httpBinding.SendTimeout = SendTimeout;
// Set up binding parameters
if (_baseAddress.Scheme == Uri.UriSchemeHttps)
{
// we need to use SSL
httpBinding.Security = new HttpBindingSecurity()
{
Mode = HttpBindingSecurityMode.Transport,
};
}
if (_clientCredentialType != HttpClientCredentialType.None)
{
if (httpBinding.Security == null || httpBinding.Security.Mode == HttpBindingSecurityMode.None)
{
// Basic over HTTP case
httpBinding.Security = new HttpBindingSecurity()
{
Mode = HttpBindingSecurityMode.TransportCredentialOnly,
};
}
httpBinding.Security.Transport.ClientCredentialType = _clientCredentialType;
}
if (UserNamePasswordValidator != null || X509CertificateValidator != null)
{
// those are the only two things that affect service credentials
return AddCredentialsToBindingParameters();
}
else
{
return null;
}
}
private BindingParameterCollection AddCredentialsToBindingParameters()
{
BindingParameterCollection bindingParameters = new BindingParameterCollection();
bindingParameters.Add(_credentials);
return bindingParameters;
}
private static Uri CreateBaseAddress(string baseAddress)
{
if (baseAddress == null)
{
throw Error.ArgumentNull("baseAddress");
}
return new Uri(baseAddress, UriKind.RelativeOrAbsolute);
}
private static Uri ValidateBaseAddress(Uri baseAddress)
{
if (baseAddress == null)
{
throw Error.ArgumentNull("baseAddress");
}
if (!baseAddress.IsAbsoluteUri)
{
throw Error.ArgumentUriNotAbsolute("baseAddress", baseAddress);
}
if (!String.IsNullOrEmpty(baseAddress.Query) || !String.IsNullOrEmpty(baseAddress.Fragment))
{
throw Error.ArgumentUriHasQueryOrFragment("baseAddress", baseAddress);
}
if (!ReferenceEquals(baseAddress.Scheme, Uri.UriSchemeHttp) && !ReferenceEquals(baseAddress.Scheme, Uri.UriSchemeHttps))
{
throw Error.ArgumentUriNotHttpOrHttpsScheme("baseAddress", baseAddress);
}
return baseAddress;
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We never want to fail here so we have to catch all exceptions.")]
internal static int MultiplyByProcessorCount(int value)
{
Contract.Assert(value > 0);
try
{
return Math.Max(Environment.ProcessorCount * value, value);
}
catch
{
return value;
}
}
}
}