Enabling compression in IIS for an IIS hosted WCF service results in error

SCENARIO
=========
We have two WCF services hosted in an IIS virtual directory. One acts as a router(Intermediary.svc) and the other as the destination(primary.svc). 
For this scenario, we have both of these hosted in the same virtual directory but in deployment they may be on the same machine or different machines.
The error only occurs when we have dynamic content compression enabled in IIS.
The first service acts as a router service and forwards the request to the second service and gets the response from the second service and forwards it to the client similar to what is discussed in https://msdn.microsoft.com/en-us/magazine/cc500646.aspx
So, the flow is as follows:
Client calls https://localhost/IntermediaryService.svc which acts as a router and routes the request to https://localhost/PrimaryService.svc.

When the failure happens, we get the following error in the client:

Unhandled Exception: System.ServiceModel.CommunicationException: An error occurred while receiving the HTTP response to https://localhost/IntermediaryService.svc .
This could be due to the service endpoint binding not using the HTTP protocol. This could also be due to an HTTP request context being aborted by the server (possibly due to the service shutting down). See server logs for more details. ---> System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a receive. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host

IIS logs show only the POST request to the Primary Service.
2011-09-15 14:51:56 ::1 POST /PrimaryService.svc - 80 - ::1 - 200 0 0 2843

If we uncheck “Enable Dynamic content compression” in IIS, it works fine and we see the following two requests in IIS log file.
2011-09-15 15:19:37 ::1 POST /PrimaryService.svc - 80 - ::1 - 200 0 0 3078
2011-09-15 15:19:40 ::1 POST /IntermediaryService.svc - 80 - ::1 - 200 0 0 6671

We enabled FREB logging in IIS and found that compression fails as the initial response for the Destination WCF (primary.svc)is already compressed
 <RenderingInfo Culture="en-US">
  <Opcode>DYNAMIC_COMPRESSION_NOT_SUCCESS</Opcode>
  <freb:Description Data="Reason">ALREADY_CONTENT_ENCODING</freb:Description>
</RenderingInfo>

CAUSE
======

WCF allows users writing a service to include any custom Http header to the HttpResponse.   This is done by adding the HttpResponseMessageProperty to the response message property.  On client side, the client can inspect the header by extracting this property from the response message properties accordingly.

Since WCF simply passes thru the response header to HttpWebReponse, some of the pre-defined http header may be used to describe the content shape and form (for instance, Transfer-encoding or Content-type).

The communication between Intermediary and Primary service is done under compression/chunked.   By the time intermediary service gets WCF message, the body has already been deflated, de-chunked and ready for consumption.  However, the header (how the message gets here) is still preserved for client to inspect.  Those header such as Transfer-encoding: Chunked indicated that it was sent in Chunked manner from primary to intermediary service.

The issue is the intermediary forwards the message (with those header as is) to the client.  Since the message contains the Transfer-encoding chunked header, the underling response is assuming the body is chuncked and trying to send it in the chunk manner.   This leads to an error (as body is deflated and dechunked).

RESOLUTION
===========

The workaround is, for header that meant for leg-to-leg, should be stripped out before forwarding.  You may add the following codes into Intermediary service.

                  HttpResponseMessageProperty prop = (HttpResponseMessageProperty)responseMessage.Properties[HttpResponseMessageProperty.Name];
                  prop.Headers.Remove("Transfer-Encoding");
                  prop.Headers.Remove("Content-Encoding");
                  return responseMessage;

or simply (if there is no custom header intended for e2e),

                  Message responseMessage = proxy.ProcessMessage(requestMessage);
                  responseMessage.Properties.Remove(HttpResponseMessageProperty.Name);
                  return responseMessage;

Comments

  • Anonymous
    December 18, 2011
    Hello Imtiaz Hussain, thank you very much for your very valuable blog entry. In a scenario with a WCF 4.0 RoutingService in between to apache based Web Service this is also relevant in both IIS and self hosted service.