使用 NancyFX 自托管分块压缩响应

Posted

技术标签:

【中文标题】使用 NancyFX 自托管分块压缩响应【英文标题】:Chunked compressed response with NancyFX self hosting 【发布时间】:2014-10-10 13:41:45 【问题描述】:

我有一个系统应该将行写入 HTTP 响应流。该系统中的每一行都代表某种事件,因此您可以将其视为通知流。我在使用 NancyFX 和 Nancy 自托管 (0.23) 的 Windows 7 上使用 .NET4。以下代码是有效的:

using System;
using System.IO;
using System.Threading;
using Nancy;
using Nancy.Hosting.Self;

namespace TestNancy

    public class ChunkedResponse : Response
    
        public ChunkedResponse()
        
            ContentType = "text/html; charset=utf-8";
            Contents = stream =>
            
                using (var streamWriter = new StreamWriter(stream))
                
                    while (true)
                    
                        streamWriter.WriteLine("Hello");
                        streamWriter.Flush();
                        Thread.Sleep(1000);
                    
                
            ;
        
    

    public class HomeModule : NancyModule
    
        public HomeModule()
        
            Get["/"] = args => new ChunkedResponse();
        
    

    public class Program
    
        public static void Main()
        
            using (var host = new NancyHost(new Uri("http://localhost:1234")))
            
                host.Start();
                Console.ReadLine();
            
        
    

现在我想对流添加压缩以压缩带宽量。出于某种原因,在浏览器中进行测试时,我看不到任何结果。我尝试了很多组合来达到预期的效果,但这就是我目前所拥有的:

using System; using System.IO; using System.IO.Compression; using System.Threading; using Nancy; using Nancy.Hosting.Self;

namespace TestNancy 
    public class ChunkedResponse : Response
    
        public ChunkedResponse()
        
            Headers["Content-Encoding"] = "gzip";
            ContentType = "text/html; charset=utf-8";
            Contents = stream =>
            
                using (var gzip = new GZipStream(stream, CompressionMode.Compress))
                using (var streamWriter = new StreamWriter(gzip))
                
                    while (true)
                    
                        streamWriter.WriteLine("Hello");
                        streamWriter.Flush();
                        Thread.Sleep(1000);
                    
                
            ;
        
    

    public class HomeModule : NancyModule
    
        public HomeModule()
        
            Get["/"] = args => new ChunkedResponse();
        
    

    public class Program
    
        public static void Main()
        
            using (var host = new NancyHost(new Uri("http://localhost:1234")))
            
                host.Start();
                Console.ReadLine();
            
        
     

我正在寻求帮助,要么告诉我我在 HTTP 协议方面做错了什么(例如,我尝试按照 HTTP1.1 中的描述添加块长度,但没有奏效),或者关于 Nancy 的帮助没有考虑。

【问题讨论】:

使用 NancyFx 自托管,您会使用 HttpListener 对吗?通常,如果您在写入内容之前省略内容长度,则内容将作为分块发送而无需进一步干预。我不知道南希在写作之前做了什么魔法,所以这可能是相关的,也可能是不相关的。 【参考方案1】:

问题似乎出在框架的 Gzip 实现中,因为它在关闭之前从不写入输出流,

我只是使用了 SharpZiplib,你的代码似乎对我有用,这是我的修改

public class ChunkedResponse : Response

    public ChunkedResponse()
    
        Headers["Transfer-Encoding"] = "chunked";
        Headers["Content-Encoding"] = "gzip";
        ContentType = "text/html; charset=utf-8";
        Contents = stream =>
        
            var gzip = new ICSharpCode.SharpZipLib.GZip.GZipOutputStream(stream);
            using (var streamWriter = new StreamWriter(gzip))
            
                while (true)
                
                    streamWriter.WriteLine("Hello");
                    gzip.Flush();
                    streamWriter.Flush();
                    Thread.Sleep(1000);
                
            

        ;
    



public class HomeModule : NancyModule

    public HomeModule()
    
        Get["/"] = args => new ChunkedResponse();
    


public class Program

    public static void Main()
    
        using (var host = new NancyHost(new HostConfigurationAllowChunkedEncoding = true,new Uri("http://localhost:1234")))
        

            host.Start();
            Console.ReadLine();
        
    

SharpZipLib 的 Nuget 包:PM> Install-Package SharpZipLib

【讨论】:

【参考方案2】:

看起来,由于while(true),您提供为ChunkedReponse.Contents 的任何调用代理都将永远不会返回。这是预期的行为吗?不知道这个框架对那个委托做了什么,我猜不出来。

乍一看,我确实想知道构造函数是否永远不会返回——我想这肯定会导致问题——但没过多久我就注意到它是一个 lambda。幸运。

编辑#1:

GZipStream.Flush() 的文档说:

此方法的当前实现没有功能。 (覆盖 Stream.Flush()。)

暗示 GZipStream 在关闭之前不会向传输中写入任何内容。如果您不永远运行提到的委托,而是在某个时候关闭流,您是否会遇到不同的行为?

【讨论】:

意在永不停止。 Nancy 调用委托来写入流 - 我这样做是分块的,因此您每秒将获得大约一行。请注意,第一个示例按预期执行此操作。【参考方案3】:

我自己测试过,我认为问题在于浏览器如何处理这些分块和压缩的响应。这是我尝试过的,基本上是有效的:

Contents = stream =>
        
            using (var gzip = new GZipStream(stream, CompressionMode.Compress))
            using (var streamWriter = new StreamWriter(gzip))
            
                for (int i = 0; i < 5;i++ )
                
                    string txt = "Hello";
                    streamWriter.WriteLine(txt);
                    streamWriter.Flush();
                    Thread.Sleep(1000);
                
            
        ;

问题是浏览器在显示结果之前等待分块响应准备好。尽管gzip supports streaming,它可能会等待解压缩,直到所有数据都发送完毕。 Here 是支持我假设的第一个提示。

【讨论】:

以上是关于使用 NancyFX 自托管分块压缩响应的主要内容,如果未能解决你的问题,请参考以下文章

使用 angularjs $http 处理分块响应

使用 HttpWebResponse 读取“分块”响应

InternetReadFile 因分块响应而失败

akka-http 分块响应连接

为啥我的自托管 NancyFX 应用程序在通过 HTTPS 运行时会挂起?

禁用传输编码:在 Spring Webflux 响应中分块