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 7e1352ee1a0..1ac5b3fd9ce 100644 --- a/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelFactory.cs +++ b/src/System.Private.ServiceModel/src/System/ServiceModel/Channels/HttpChannelFactory.cs @@ -50,6 +50,7 @@ internal class HttpChannelFactory private bool _useDefaultWebProxy; private Lazy _webSocketSoapContentType; private SHA512 _hashAlgorithm; + private bool _keepAliveEnabled; internal HttpChannelFactory(HttpTransportBindingElement bindingElement, BindingContext context) : base(bindingElement, context, HttpTransportDefaults.GetDefaultMessageEncoderFactory()) @@ -102,6 +103,7 @@ internal HttpChannelFactory(HttpTransportBindingElement bindingElement, BindingC _authenticationScheme = bindingElement.AuthenticationScheme; _maxBufferSize = bindingElement.MaxBufferSize; _transferMode = bindingElement.TransferMode; + _keepAliveEnabled = bindingElement.KeepAliveEnabled; if (bindingElement.ProxyAddress != null) { @@ -331,6 +333,9 @@ internal async Task GetHttpClientAsync(EndpointAddress to, httpClient = new HttpClient(clientHandler); + if(!_keepAliveEnabled) + httpClient.DefaultRequestHeaders.ConnectionClose = true; + // We provide our own CancellationToken for each request. Setting HttpClient.Timeout to -1 // prevents a call to CancellationToken.CancelAfter that HttpClient does internally which // causes TimerQueue contention at high load. diff --git a/src/System.Private.ServiceModel/tests/Common/Scenarios/ServiceInterfaces.cs b/src/System.Private.ServiceModel/tests/Common/Scenarios/ServiceInterfaces.cs index 21302a59c0e..4325f565da9 100644 --- a/src/System.Private.ServiceModel/tests/Common/Scenarios/ServiceInterfaces.cs +++ b/src/System.Private.ServiceModel/tests/Common/Scenarios/ServiceInterfaces.cs @@ -83,6 +83,9 @@ public interface IWcfService [OperationContract] void ReturnContentType(string contentType); + + [OperationContract] + bool IsHttpKeepAliveDisabled(); } [ServiceContract] 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 8fe2d91f3a0..7f8fbd079fd 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 @@ -42,4 +42,41 @@ public static void DefaultSettings_Echo_RoundTrips_String() ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory); } } + + [WcfFact] + [OuterLoop] + public static void HttpKeepAliveDisabled_Echo_RoundTrips_True() + { + ChannelFactory factory = null; + IWcfService serviceProxy = null; + Binding binding = null; + CustomBinding customBinding = null; + + try + { + // *** SETUP *** \\ + binding = new BasicHttpBinding(BasicHttpSecurityMode.None); + customBinding = new CustomBinding(binding); + var httpElement = customBinding.Elements.Find(); + httpElement.KeepAliveEnabled = false; + + factory = new ChannelFactory(customBinding, new EndpointAddress(Endpoints.HttpBaseAddress_Basic)); + serviceProxy = factory.CreateChannel(); + + // *** EXECUTE *** \\ + bool result = serviceProxy.IsHttpKeepAliveDisabled(); + + // *** VALIDATE *** \\ + Assert.True(result, "Error: expected response from service: 'true' Actual was: 'false'"); + + // *** CLEANUP *** \\ + factory.Close(); + ((ICommunicationObject)serviceProxy).Close(); + } + finally + { + // *** ENSURE CLEANUP *** \\ + ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory); + } + } } diff --git a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/IWcfService.cs b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/IWcfService.cs index 4bf660bea35..4fd840a5863 100644 --- a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/IWcfService.cs +++ b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/IWcfService.cs @@ -155,5 +155,8 @@ public interface IWcfService ReplyAction = "http://www.contoso.com/IXmlMessageContarctTestService/EchoMessageResquestWithMessageHeaderResponse")] [XmlSerializerFormat(SupportFaults = true)] XmlMessageContractTestResponse EchoMessageResquestWithMessageHeader(XmlMessageContractTestRequestWithMessageHeader request); + + [OperationContract] + bool IsHttpKeepAliveDisabled(); } } diff --git a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/WcfService.cs b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/WcfService.cs index 6360171ae13..33bcc8cb490 100644 --- a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/WcfService.cs +++ b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/WcfService.cs @@ -424,6 +424,15 @@ public string EchoTimeAndSetCookie(string name) return value; } + public bool IsHttpKeepAliveDisabled() + { + MessageProperties properties = new MessageProperties(OperationContext.Current.IncomingMessageProperties); + var property = (HttpRequestMessageProperty)properties[HttpRequestMessageProperty.Name]; + WebHeaderCollection collection = property.Headers; + string connectionValue = collection.Get(Enum.GetName(typeof(HttpRequestHeader), HttpRequestHeader.Connection)); + return connectionValue.Equals("Close", StringComparison.OrdinalIgnoreCase); + } + private static string StreamToString(Stream stream) { var reader = new StreamReader(stream, Encoding.UTF8); diff --git a/src/System.ServiceModel.Http/ref/System.ServiceModel.Http.cs b/src/System.ServiceModel.Http/ref/System.ServiceModel.Http.cs index 6fd87cb36fb..d921a42be4e 100644 --- a/src/System.ServiceModel.Http/ref/System.ServiceModel.Http.cs +++ b/src/System.ServiceModel.Http/ref/System.ServiceModel.Http.cs @@ -186,6 +186,8 @@ protected HttpTransportBindingElement(System.ServiceModel.Channels.HttpTransport public System.ServiceModel.Channels.WebSocketTransportSettings WebSocketSettings { get { return default(System.ServiceModel.Channels.WebSocketTransportSettings); } set { } } [System.ComponentModel.DefaultValue(true)] public bool UseDefaultWebProxy { get { return default(bool); } set { } } + [System.ComponentModel.DefaultValue(true)] + public bool KeepAliveEnabled { get { return default(bool); } set { } } public override System.ServiceModel.Channels.IChannelFactory BuildChannelFactory(System.ServiceModel.Channels.BindingContext context) { return default(System.ServiceModel.Channels.IChannelFactory); } public override bool CanBuildChannelFactory(System.ServiceModel.Channels.BindingContext context) { return default(bool); } public override System.ServiceModel.Channels.BindingElement Clone() { return default(System.ServiceModel.Channels.BindingElement); }