Add support to use externally provided HttpClient/HttpClientHandler #2400
Description
API's involved
There are 2 existing API mechanisms which could be used to achieve this.
BindingParameterCollection
A developer could create a class which implements IEndpointBehavior
. In the AddBindingParameters
method, they would add the relevant instance to the BindingParameterCollection
. This would look like this:
public class UseMyHttpClientEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
HttpClientHandler httpClientHandler = new HttpClientHandler();
// Set any properties on httpClientHandler
bindingParameters.Add(new HttpClient(httpClientHandler));
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
}
This is a naïve implementation, e.g. HttpClient
needs to have timeouts disabled to prevent contention on the global TimerManager, but it shows the general pattern.
MessageProperties
A developer could add an outgoing message property for the relevant instance to the OutgoingMessageProperties
of an OperationContext
. This could be done either with a class which implements IClientMessageInspector
or with the use of OperationContextScope
. The latter would look like this:
HttpClientHandler httpClientHandler = new HttpClientHandler();
// Set any properties on httpClientHandler
HttpClient httpClient = new HttpClient();
using(new OperationContextScope((IContextChannel)myClient))
{
OperationContext.Current.OutgoingMessageProperties.Add("System.Net.Http.HttpClient", httpClient);
myClient.DoWork();
}
Which class(es) to support
We have multiple options about which class(es) we could potentially support being added to a BindingParameterCollection
or OutgoingMessageProperties
, each having their own limitations. We would be unable to modify any parameters on any passed in objects for 2 reasons. 1) Any property we might set could potentially be something which is intended to be overridden. 2) There are use cases where the passed in object might have already been used so is now immutable.
System.Net.Http.HttpClient
WCF sets HttpClient.Timeout
to infinite to prevent a timer being registered for each request. We have logic to coalesce CancellationToken timers to prevent high contention on the global timer queue. Without setting this value, HttpClient will cause a lot of contention.
System.Net.Http.HttpMessageInvoker
This is the base class to HttpClient
. It lacks a lot of the helper api's that HttpClient
provides such as the verb specific request api's, e.g. GetAsync
and PostAsync
. WCF only needs to use SendAsync
so this shouldn't be an issue. Although we don't currently do this, WCF could make use of HttpClient.DefaultRequestHeaders
for the headers which will always be the same for all requests from the same HttpChannelFactory
. If we decide to support the use of a single instance of HttpClient
with multiple ChannelFactory
instances, then we can't use DefaultRequestHeaders
.
System.Net.Http.HttpClientHandler
There are many properties on this class which WCF sets. One of the more important ones is Credentials
. A developer would need to set the credentials on HttpClientHandler
themselves. We would still be able to set any properties on the HttpClient
instance we would create to wrap the HttpClientHandler
. Supporting allowing this class to be provided would limit developers to using client implementations which ship with the framework.
System.Net.Http.HttpMessageHandler
This is the base class for HttpClientHandler
and is the type that the constructor for HttpClient
accepts. This has none of the properties that HttpClientHandler
exposes, but as we can't modify any properties as explained earlier, this does not present any additional problems. This would enable developers to provide any implementation of Http which works with HttpClient.
System.Net.Http.DelegatingHandler
This is a special class which allows to provide an existing HttpMessageHandler
to be wrapped and potentially provide custom behavior. There is a property InnerHandler
which would allow WCF to set the HttpClientHandler
that we create on the DelegatingHandler
. Presuming the constraint of only being able to give WCF a delegating handler which has not been used yet, we would be able to provide an unused HttpClientHandler
to the delegating handler which would be able to modify any properties before first usage. This would allow WCF to set credentials etc on an HttpClientHandler
while also allowing a developer to modify everything as much as they wish.
Decisions to be made
- Which of the extensibility methods do we want to support?
- Which class types do we want to support being provided?
- What constraints are acceptable? E.g. can instances be shared with multiple
ChannelFactory
's?