Фрагментированный сжатый ответ с самостоятельным хостингом NancyFX

У меня есть система, которая должна записывать строки в поток ответов HTTP. Каждая строка в этой системе представляет какое-то событие, поэтому вы можете видеть это как поток уведомлений. Я использую .NET4 в Windows 7, используя самостоятельный хостинг NancyFX и Nancy (0.23). Следующий код является функциональным:

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, что не сработало), либо помощь относительно Нэнси, где она делает что-то, чего я не учитывал. для.


person Deathspike    schedule 17.08.2014    source источник
comment
С самостоятельным хостингом NancyFx вы бы использовали HttpListener, верно? Обычно, если вы опускаете длину содержимого перед записью содержимого, содержимое отправляется в виде фрагментов без дальнейшего вмешательства. Я не знаю, что волшебная Нэнси делает перед тем, как написать, так что это может иметь значение, а может и не иметь.   -  person spender    schedule 17.08.2014


Ответы (3)


Проблема, по-видимому, заключается в реализации фреймворка 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 HostConfiguration{AllowChunkedEncoding = true},new Uri("http://localhost:1234")))
        {

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

Пакет Nuget для SharpZipLib: PM> Install-Package SharpZipLib

person user3473830    schedule 26.08.2014

Похоже, что любой вызов делегата, который вы указали как ChunkedReponse.Contents, никогда не вернется из-за while(true). Это намеренное поведение? Не зная, что этот фреймворк делает с этим делегатом, я не мог догадаться.

На первый взгляд я задавался вопросом, не вернется ли конструктор никогда - что, я думаю, определенно вызовет проблему, - но мне не потребовалось много времени, чтобы заметить, что это лямбда. К счастью.

Редактировать №1:

В документации для GZipStream.Flush() сказано:

Текущая реализация этого метода не имеет функциональности. (Переопределяет Stream.Flush().)

Для меня это подразумевает, что GZipStream ничего не записывает в транспорт, пока он не будет закрыт. Испытываете ли вы другое поведение, если вы не запускаете упомянутый делегат навсегда, а вместо этого в какой-то момент закрываете поток?

person Tom W    schedule 19.08.2014
comment
Предполагается, что он никогда не останавливается. Делегат вызывается Нэнси для записи в поток, что я и делаю порциями, так что вы будете получать примерно одну строку в секунду. Обратите внимание, что первый пример делает это так, как предполагалось. - person Deathspike; 19.08.2014

Я проверил это сам и думаю, что проблема в том, как браузер обрабатывает эти фрагментированные и сжатые ответы. Это то, что я пробовал и что в основном сработало:

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);
                }
            }
        };

Проблема в том, что браузер ждет, пока ответ будет готов, прежде чем отобразить результат. Вероятно, он ждет с распаковкой, пока все данные не будут отправлены, хотя before-unzipping">gzip поддерживает потоковую передачу. Вот первая подсказка, которая поддерживает мой предположение.

person T_D    schedule 25.08.2014