views:

900

answers:

4

We've got a fairly complex httphandler for handling images. Basically it streams any part of the image at any size that is requested. Some clients use this handler without any problems. But we've got one location that gives us problems, and now it also gives problems on my development environment.

What happens is that the client never receives anything on some requests. So request 1 and 2 are fine, but request 3 and 4 never end.

  • While debugging I can see that the server is ready and has completed the request.
  • The client however is still waiting on a result (debugging with fiddler2 shows that there is no response received)

The code that we use to stream an image is

        if (!context.Response.IsClientConnected)
        {
            imageStream.Close();
            imageStream.Dispose();
            return;
        }

        context.Response.BufferOutput = true;
        context.Response.ContentType = "image/" + imageformat;

        context.Response.AppendHeader("Content-Length", imageStream.Length.ToString());

        if (imageStream != null && imageStream.Length > 0 && context.Response.IsClientConnected)
            context.Response.BinaryWrite(imageStream.ToArray());

        if (context.Response.IsClientConnected)
            context.Response.Flush();

        imageStream.Close();
        imageStream.Dispose();

The imageStream is a MemoryStream with the contents of an image.

After the call to response.Flush() we do some more clean-up and writing summaries to the eventlog.

We also call GC.Collect() after every request, because the objects that we use in-memory become very large. I know that that is not a good practice, but could it give us trouble?

The problems with not returning requests happen at both IIS 5 (Win XP) and IIS 6 (Win 2003), we use .NET framework v2.

+3  A: 

First off, there are better ways of dealing with streams that using an array for the entire thing (i.e. MemoryStream might be unnecessary here).

I would envisage a loop:

const int BUFFER_SIZE = 4096; // pick your poison
bute[] buffer = new byte[BUFFER_SIZE];
int bytesRead;

while((bytesRead = inStream.Read(buffer, 0, BUFFER_SIZE)) > 0)
{
    outStream.Write(buffer, 0, bytesRead);
}

You should also do this with buffering disabled (.Response.BufferOutput = false).

Re the problem, my suspicion is that you haven't written enough data / closed the response (.Response.Close()).

Marc Gravell
It wasn't the solution, but still useful!
Michiel Overeem
If you *do* have a MemoryStream, however, there's a simpler way: MemoryStream.WriteTo(Stream)
Jon Skeet
+2  A: 

Garbage collection shouldn't be the problem. Why are you setting BufferOutput to true though? Given that you just want to write the data directly, I'd have thought it would be more appropriate to set it to false.

I suggest you go one level lower in your diagnostics: use Wireshark to see exactly what's happening at the network level. Fiddler is great for the HTTP level, but sometimes you need even more detail.

Jon Skeet
With buffering enabled, it is entirely possible that the server hasn't sent *anything* yet, especially if the lengths are borked.
Marc Gravell
The length should be okay based on what's being written - and there's a Response.Flush() assuming the client is still connected. It's very odd though.
Jon Skeet
Thanks for the link to Wireshark, useful stuff.
Michiel Overeem
If Buffering is on there is no need to add a Content-Length header, ASP.NET will do that for you
AnthonyWJones
A: 

We also used WebRequests to gather more information. Those were blocking the connections. Putting usings around the WebResponses did the trick.

using (HttpWebResponse test_resp = (HttpWebResponse)test_req.GetResponse())
{
}

I didn't know that those could block other request etc...

Michiel Overeem
+2  A: 

A client will limit the number of simultaneous requests it will make to any one server. Furthermore when requesting from a resource that requires session state (the default) other requests for resources requiring session state will block.

When using HttpWebResponse you must dispose either that object or the stream returned by its GetResponseStream method to complete the connection.

Your code was very confusing. You've turned buffering on, set a content-length and used a flush. This results in some strange HTTP headers. Ordinarily with buffering on you would leave the setting of the Content-Length header to ASP.NET to handle.

When you use flush ASP.NET assumes that you may subsequently send more data. In this case it will use chunked transfer. Once the response is complete a final set of headers is sent for the final chunk, each chunk as its own length header and the total length of content is derived from these. The first chunk should not have a Content-Length header, however your code is adding that header.

If you turn off buffering and pump the bytes into the output stream yourself then you should set the Content-Length header yourself because effectively buffer off means you are taking responsibility for exactly what gets sent to the client. Marc's code is a simple example of such a pump, although I would use a bigger buffer, or on a MemoryStream the WriteTo method would be more efficient.

AnthonyWJones