7

I'm writing a simple web application using Nancy. At least one request results in a stream of unknown length, so I can't provide Content-Length. I'd like to use Transfer-Encoding: chunked, or (equally acceptable in this case, Connection: close).

I've had a quick hack on the Nancy source code, and I've add Response.BufferOutput, and code to set HttpContext.Response.BufferOutput to false. You can see that here:

public class HomeModule : NancyModule
{
    public HomeModule()
    {
        Get["/slow"] = _ => new SlowStreamResponse();
    }

    private class SlowStreamResponse : Response
    {
        public SlowStreamResponse()
        {
            ContentType = "text/plain";
            BufferOutput = false;
            Contents = s => {
                byte[] bytes = Encoding.UTF8.GetBytes("Hello World\n");
                for (int i = 0; i < 10; ++i)
                {
                    s.Write(bytes, 0, bytes.Length);
                    Thread.Sleep(500);
                }
            };
        }
    }

It doesn't seem to have any effect. The response turns up all at once, after 5 seconds. I've tested this a simple WebRequest-based client.

How do I get chunked output to work in Nancy? I'm using the ASP.NET hosting, but I'd be interested in answers for the other hosting options.

If I write a simple server using HttpListener, I can set SendChunked to true, and it sends chunked output, which my simple client correctly receives in chunks.

Roger Lipscombe
  • 89,048
  • 55
  • 235
  • 380

3 Answers3

6

During my experimentation, I discovered that I needed the following configuration. First, set up your web.config file as documented in the Nancy Wiki. It is worth noting that in order to set the disableoutputbuffer value (which is what we want), it appears that you currently need to also specify a bootstrapper. Creating a class in your assembly that inherits from Nancy.Hosting.Aspnet.DefaultNancyAspNetBootstrapper and specifying it in the configuration file appears to work.

<configSections>
  <section name="nancyFx" type="Nancy.Hosting.Aspnet.NancyFxSection" />
</configSections>
<nancyFx>
  <bootstrapper assembly="YourAssembly" type="YourBootstrapper"/>
  <disableoutputbuffer value="true" />
</nancyFx>

Afterwards, you should not set the Transfer-Encoding header. Rather, the following route definition appears to correctly stream the results from my IIS Express development server to Chrome:

Get["/chunked"] = _ =>
{
  var response = new Response();
  response.ContentType = "text/plain";
  response.Contents = s =>
  {
    byte[] bytes = System.Text.Encoding.UTF8.GetBytes("Hello World ");
    for (int i = 0; i < 10; ++i)
    {
      for (var j = 0; j < 86; j++)
      {
        s.Write(bytes, 0, bytes.Length);
      }
      s.WriteByte(10);
      s.Flush();
      System.Threading.Thread.Sleep(500);
    }
  };

  return response;
};

I specified more content per chunk than the previous example due to the minimum sizes prior to first render documented in other StackOverflow questions

Community
  • 1
  • 1
erdomke
  • 4,980
  • 1
  • 24
  • 30
4

You have to call Flush() after each Write(), otherwise the response is buffered anyway. Moreover, Google Chrome doesn't render the output until it's all received.

I discovered this by writing a simple client application that logged what it was reading from the response stream as it arrived.

Roger Lipscombe
  • 89,048
  • 55
  • 235
  • 380
  • 1
    Not really. The 'BufferOutput' change isn't in core Nancy, and it only really works for the ASP.NET hosting. For WCF, you have to change the WebHttpBinding, I can't get self-hosting to work correctly, and I can't see any way to do it at all with the OWIN hosting. – Roger Lipscombe Jul 18 '12 at 08:42
  • @RogerLipscombe did you ever get anywhere with this? Im trying to get this working with NancyFx + ASP.NET – RPM1984 May 15 '15 at 01:57
0

If using .NET 4.5+, you can alternatively use Stream.CopyTo instead of flush.

Get["/chunked"] = _ =>
{
  var response = new Response();
  response.ContentType = "text/plain";
  response.Contents = s =>
  {
    using(var helloStream = new MemoryStream(Encoding.UTF8.GetBytes("Hello World ")))
      helloStream.CopyTo(s);
  }
  return response;
}
TamusJRoyce
  • 817
  • 1
  • 12
  • 25