From 4f7a18174dc34b990efe19c6d7db5260ec2b1d95 Mon Sep 17 00:00:00 2001 From: Matt Connew Date: Wed, 31 Jan 2018 18:12:36 -0800 Subject: [PATCH] Add the ability to replace or customize the HttpMessageHandler --- .../Channels/HttpChannelFactory.cs | 10 +- .../Http/BasicHttpBindingTests.4.0.0.cs | 97 +++++++++++++++++++ .../Binding/Http/HttpBindingTestHelpers.cs | 64 ++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 src/System.Private.ServiceModel/tests/Scenarios/Binding/Http/HttpBindingTestHelpers.cs diff --git a/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelFactory.cs b/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelFactory.cs index 37232364bd..ef86b1ccd2 100644 --- a/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelFactory.cs +++ b/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelFactory.cs @@ -46,6 +46,7 @@ internal class HttpChannelFactory private SecurityTokenManager _securityTokenManager; private TransferMode _transferMode; private ISecurityCapabilities _securityCapabilities; + private Func _httpMessageHandlerFactory; private WebSocketTransportSettings _webSocketSettings; private Lazy _webSocketSoapContentType; private SHA512 _hashAlgorithm; @@ -130,6 +131,7 @@ internal HttpChannelFactory(HttpTransportBindingElement bindingElement, BindingC _channelCredentials = context.BindingParameters.Find(); _securityCapabilities = bindingElement.GetProperty(context); + _httpMessageHandlerFactory = context.BindingParameters.Find>(); _webSocketSettings = WebSocketHelper.GetRuntimeWebSocketSettings(bindingElement.WebSocketSettings); _clientWebSocketFactory = ClientWebSocketFactory.GetFactory(); @@ -331,7 +333,13 @@ internal async Task GetHttpClientAsync(EndpointAddress to, clientHandler.Credentials = credential; } - httpClient = new HttpClient(clientHandler); + HttpMessageHandler handler = clientHandler; + if(_httpMessageHandlerFactory!= null) + { + handler = _httpMessageHandlerFactory(clientHandler); + } + + httpClient = new HttpClient(handler); if(!_keepAliveEnabled) httpClient.DefaultRequestHeaders.ConnectionClose = true; diff --git a/src/System.Private.ServiceModel/tests/Scenarios/Binding/Http/BasicHttpBindingTests.4.0.0.cs b/src/System.Private.ServiceModel/tests/Scenarios/Binding/Http/BasicHttpBindingTests.4.0.0.cs index 7f8fbd079f..7845271219 100644 --- a/src/System.Private.ServiceModel/tests/Scenarios/Binding/Http/BasicHttpBindingTests.4.0.0.cs +++ b/src/System.Private.ServiceModel/tests/Scenarios/Binding/Http/BasicHttpBindingTests.4.0.0.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Net.Http; using System.ServiceModel; using System.ServiceModel.Channels; using Infrastructure.Common; @@ -79,4 +80,100 @@ public static void HttpKeepAliveDisabled_Echo_RoundTrips_True() ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory); } } + + [WcfFact] + [OuterLoop] + public static void HttpMessageHandlerFactory_Success() + { + ChannelFactory factory = null; + IWcfService serviceProxy = null; + string testString = "Hello"; + Binding binding = null; + + try + { + // *** SETUP *** \\ + binding = new BasicHttpBinding(BasicHttpSecurityMode.None); + factory = new ChannelFactory(binding, new EndpointAddress(Endpoints.HttpBaseAddress_Basic)); + var handlerFactoryBehavior = new HttpMessageHandlerBehavior(); + bool handlerCalled = false; + handlerFactoryBehavior.OnSending = (message, token) => + { + handlerCalled = true; + return null; + }; + factory.Endpoint.Behaviors.Add(handlerFactoryBehavior); + serviceProxy = factory.CreateChannel(); + + // *** EXECUTE *** \\ + string result = serviceProxy.Echo("Hello"); + + // *** VALIDATE *** \\ + Assert.True(handlerCalled, "Error: expected client to call intercepting handler"); + Assert.True(result == testString, String.Format("Error: expected response from service: '{0}' Actual was: '{1}'", testString, result)); + + // *** CLEANUP *** \\ + factory.Close(); + ((ICommunicationObject)serviceProxy).Close(); + } + finally + { + // *** ENSURE CLEANUP *** \\ + ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory); + } + } + + [WcfFact] + [OuterLoop] + public static void HttpMessageHandlerFactory_ModifyContent_Success() + { + ChannelFactory factory = null; + IWcfService serviceProxy = null; + string testString = "Hello"; + string substituteString = "World"; + Binding binding = null; + + try + { + // *** SETUP *** \\ + binding = new BasicHttpBinding(BasicHttpSecurityMode.None); + factory = new ChannelFactory(binding, new EndpointAddress(Endpoints.HttpBaseAddress_Basic)); + var handlerFactoryBehavior = new HttpMessageHandlerBehavior(); + handlerFactoryBehavior.OnSending = (message, token) => + { + var oldContent = message.Content; + string requestMessageBody = oldContent.ReadAsStringAsync().Result; + requestMessageBody = requestMessageBody.Replace(testString, substituteString); + message.Content = new StringContent(requestMessageBody); + foreach (var header in oldContent.Headers) + { + if (!header.Key.Equals("Content-Length") && message.Content.Headers.Contains(header.Key)) + { + message.Content.Headers.Remove(header.Key); + } + + message.Content.Headers.Add(header.Key, header.Value); + } + + return null; + }; + factory.Endpoint.Behaviors.Add(handlerFactoryBehavior); + serviceProxy = factory.CreateChannel(); + + // *** EXECUTE *** \\ + string result = serviceProxy.Echo("Hello"); + + // *** VALIDATE *** \\ + Assert.True(result == substituteString, String.Format("Error: expected response from service: '{0}' Actual was: '{1}'", testString, result)); + + // *** CLEANUP *** \\ + factory.Close(); + ((ICommunicationObject)serviceProxy).Close(); + } + finally + { + // *** ENSURE CLEANUP *** \\ + ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory); + } + } } diff --git a/src/System.Private.ServiceModel/tests/Scenarios/Binding/Http/HttpBindingTestHelpers.cs b/src/System.Private.ServiceModel/tests/Scenarios/Binding/Http/HttpBindingTestHelpers.cs new file mode 100644 index 0000000000..35f8edcd57 --- /dev/null +++ b/src/System.Private.ServiceModel/tests/Scenarios/Binding/Http/HttpBindingTestHelpers.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.ServiceModel.Channels; +using System.ServiceModel.Description; +using System.ServiceModel.Dispatcher; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +public class HttpMessageHandlerBehavior : IEndpointBehavior +{ + public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) + { + bindingParameters.Add(new Func(GetHttpMessageHandler)); + } + + public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { } + + public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } + + public void Validate(ServiceEndpoint endpoint) { } + + public HttpMessageHandler GetHttpMessageHandler(HttpClientHandler httpClientHandler) + { + return new InterceptingHttpMessageHandler(httpClientHandler, this); + } + + public Func OnSending { get; set; } + public Func OnSent { get; set; } + +} + +public class InterceptingHttpMessageHandler : DelegatingHandler +{ + private readonly HttpMessageHandlerBehavior _parent; + + public InterceptingHttpMessageHandler(HttpMessageHandler innerHandler, HttpMessageHandlerBehavior parent) + { + InnerHandler = innerHandler; + _parent = parent; + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + HttpResponseMessage response; + if (_parent.OnSending != null) + { + response = _parent.OnSending(request, cancellationToken); + if (response != null) + return response; + } + + response = await base.SendAsync(request, cancellationToken); + + if (_parent.OnSent != null) + { + return _parent.OnSent(response, cancellationToken); + } + + return response; + } +}