Wednesday, March 6, 2013

System.Web.HttpException: The remote host closed the connection. The error code is 0x80072746

Today we got an error when we are downloading huge file like in 1GB+ via http. Existing code was working fine for small sized file.

Exception:

System.Web.HttpException: The remote host closed the connection. The error code is 0x80072746
 at System.Web.Hosting.ISAPIWorkerRequestInProcForIIS6.FlushCore(Byte[] status, Byte[] header, Int32 keepConnected, Int32 totalBodySize, Int32 numBodyFragments, IntPtr[] bodyFragments, Int32[] bodyFragmentLengths, Int32 doneWithSession, Int32 finalStatus, Boolean& async)
   at System.Web.Hosting.ISAPIWorkerRequest.FlushCachedResponse(Boolean isFinal)
   at System.Web.Hosting.ISAPIWorkerRequest.FlushResponse(Boolean finalFlush)
   at System.Web.HttpResponse.Flush(Boolean finalFlush)
   at System.Web.HttpResponse.Flush()


This exception generally happens when you have download functionality in your Asp.net applications and when user starts the download process, but, the download process does not complete for one of the following reasons:
  • User cancels the download
  • User navigates to another page
  • User closes the page
  • The connection closes


We have a file download page that allows the user to download files of different sizes varying from few KBs to GBs. We also have multiple files bundled into a single .zip file.


Root Cause:

Default value of Response.Buffer  property is set to "true" and hence the Asp.net process writes data/output to the buffer, instead of the output stream. When the page is finished processing, the buffered output is flushed to the output stream and hence, sent to the browser. This is usually done to avoid multiple data transfer process over the network and hence optimize performance by sending the overall page output in a single Response.

But, when a page writes a file content (Or, a file) in the Response, usually, we used to flush the buffer immediately after we write the file content in the output stream, so that, the file content is sent to browser immediately, instead of waiting for the page to complete its' execution process process. In general, following is the code that we usually use.

Response.OutputStream.Write(buffer,0,buffer.length);
Response.Flush();
Response.End();

Now :

  • User started to download the file from the download prompt (As a result of the Response.OutputStream.Write()), data is stored in buffer and the buffer is overflown and a stream of binary data started to flow from server to client.
  • As file is large, based on executiontime configured in web.config, response timeout or the download process is cancelled.
  • Meanwhile, the Asp.net process tries to Flush() the buffer content to the output stream, that is already closed by the client.
  • Exception occurs.



Solution:

Buffering response at server may cause system degradation which can be worsen when multiple users are trying to hit files simultaneously. Hence just set

Response.Buffer = false;

Further, if you have large file like in GB, it is good idea not to buffer it for better performance. Just open the stream and start writing to response stream. As we are not buffering at server level, it directly opens to client and file downloaded started. Till now, I've successfully downloaded 1.5 GB file from server to client but I'm sure, this will work for jombo files too.


var resp = HttpContext.Current.Response;
        resp.Buffer = false;
 resp.AddHeader("Content-Disposition", "attachment; filename=test.txt");
            resp.ContentType = "application/zip";
            string strShowMsg = string.Empty;
             long start=0;

     using (FileStream rstream = new FileStream(@"C:\DownloadHuge.txt", FileMode.Open))
             {
                 int totalbytes = 0;

                 byte[] buffer = new byte[256];
                 int x = rstream.Read(buffer, 0, 256);
                 while (x > 0)
                 {
                     resp.OutputStream.Write(buffer, 0, x);                     
                     totalbytes += x;
                     x = rstream.Read(buffer, 0, 256);
                 }
            
             }

             resp.Flush();
             resp.End();

Although, this solution seems simple but it took a lot of time to figure out this issue.