TCP Connection Reuse Not Working with CustomBinding in WCF
Describe the bug I am using a WCF client with a CustomBinding that includes an HttpTransportBindingElement. Despite setting KeepAliveEnabled to true, the TCP connection is not being reused, and the Connection: Keep-Alive header is not present in the HTTP request. Additionally, the WCF client is terminating the session after each request. My main goal is to reuse TLS connections, but in the Wireshark traces, I see that the WCF client terminates the TCP connections after an HTTP 200 OK response is received.
I also added the Keep-Alive header with a timeout of 50 seconds, but the problem still persists. Additionally, I am caching the ChannelFactory objects.
To Reproduce Steps to reproduce the behavior:
- Create a CustomBinding with an HttpTransportBindingElement and set KeepAliveEnabled to true.
- Implement a ChannelFactory with this binding and an endpoint address.
- Add a Message Inspector to ensure the Connection: Keep-Alive header is set.
- Add the Keep-Alive header with a timeout of 50 seconds.
- Cache the ChannelFactory objects.
- Send a request and observe that the TCP connection is closed and the Connection: Keep-Alive header is missing.
Expected behavior The TCP connection should remain open and be reused for subsequent requests. The HTTP request should include the Connection: Keep-Alive header with a timeout of 50 seconds.
Screenshots
WCF Client terminates the TCP connection after 200 ok was received.
Additional Context .NET Version: .NET 8 OS: Windows 10
I have tried various methods to ensure the Connection: Keep-Alive header is set, but without success. The WCF client terminates the session after each request, preventing TLS connection reuse. In Wireshark traces, it is observed that the WCF client closes the TCP connections after receiving an HTTP 200 OK response. Adding the Keep-Alive header with a timeout of 50 seconds did not resolve the issue. Additionally, I am caching the ChannelFactory objects. Any help in resolving this issue would be greatly appreciated. Would be great to get advice on how to use WCF for this scenario.
HTTP/1.1 keeps the connection alive by default. It's an opt-out behavior, not opt-in. If you set KeepAliveEnabled to false, you will see a Connection: close header which is how you opt out of it. On .NET Framework, HttpWebRequest would add the header unnecessarily.
The only possible consequence of that header being missing vs adding it unnecessarily is if a server implementation incorrectly requires it. In this case, it's the client that's closing the connection, so this is client behavior.
There are a few things which can trigger a client to close the connection, but unless you are directly configuring the connection pooling on HttpClientHandler, I can't think of anything which would trigger the client to initiate closing the connection. The server might send a Connection: close header, but I doubt that's the case though as the server would normally initiate the close in this case. There are some content length mechanisms which makes it impossible to reuse, but again I would expect the server to close the connection in those cases. If the client disposes the response without having read the body, then if there's more than some threshold of bytes left to be received, HttpClientHandler will close the connection.
Can you provide the response headers (don't need anything authentication based so redact/remove those if there are any).
@mconnew thx for your answer.
Here are the headers of the 200 Ok:
HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8
Content-Length: 770
Connection: keep-alive
Some additional context: We're using WCF to connect to ONVIF devices, SOAP based. We're using the "default" ChannelFactory and are caching the specific instances.
var factory =
_cache.GetOrCreate(
address,
() =>
{
var channelFactory = new ChannelFactory<TService>(
/*CustomBinding*/ GetBinding(device, _wsAddressingTypes.Contains(typeof(TService))),
/*EndpointAddress*/ address);
return channelFactory;
},
(_, _) => { });
var channel = factory.CreateChannel(address);
I've seen that HttpTransportBindingElement offers a BuildChannelFactory<TService>. Which is the prefered way when working with SOAP?
Thx
Ok, I found the problem. Pooling timeouts in the SocketsHttpHandler were set to 0 🤦.
Thx for the support!