GZipStream обрезает последнюю часть XML

Я создал метод расширения под названием AddGZip, который выглядит следующим образом:

public static void AddGZip(this HttpResponse response)
{
    response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
    response.AppendHeader("Content-Encoding", "gzip");
}

Это очень урезанная версия кода:

var response = HttpContext.Current.Response;
var request = HttpContext.Current.Request;
var result = File.ReadAllText(path);
if (request.SupportsGZip)
{
  response.AddGZip();
}
response.Write(result);
response.Flush();

Когда вы просматриваете ответ в веб-браузере с поддержкой GZip, вы получаете сообщение об ошибке:

"Ошибка синтаксического анализа XML: нераскрытый токен Расположение: http://webserver1/1234.xml Строка № 78, столбец 1:"

Когда я смотрю на источник, он в основном пропустил последний > с конца файла XML. Итак, 1 или 2 байта.

Если я закомментирую строку AddGZip, она будет работать нормально. Однако я действительно хочу поддержать GZip, так как XML может быть довольно большим.

У кого-нибудь есть предложения для меня? Я пытался проверить много блогов, но, похоже, нет решения для этого типа ошибки.

Дейв

2 ответа

Решение

Существует проблема (или, возможно, действительно умная функция, которую я нигде не видел оправданной) с DeflateStream (GZipStream основывается на DeflateStream и наследует проблему *), где сброс может потерять данные.

Response.Flush() промоет фильтр. Решение состоит в том, чтобы использовать обертку, которая знает как о застежке-молнии, так и о нижележащей раковине, и только сбрасывает последнюю:

public enum CompressionType
{
    Deflate,
    GZip
}
/// <summary>
/// Provides GZip or Deflate compression, with further handling for the fact that
/// .NETs GZip and Deflate filters don't play nicely with chunked encoding (when
/// Response.Flush() is called or buffering is off.
/// </summary>
public class WebCompressionFilter : Stream
{
    private Stream _compSink;
    private Stream _finalSink;
    public WebCompressionFilter(Stream stm, CompressionType comp)
    {
        switch(comp)
        {
            case CompressionType.Deflate:
                _compSink = new DeflateStream((_finalSink = stm), CompressionMode.Compress);
                break;
            case CompressionType.GZip:
                _compSink = new GZipStream((_finalSink = stm), CompressionMode.Compress);
                break;
        }
    }
    public override bool CanRead
    {
        get
        {
            return false;
        }
    }
    public override bool CanSeek
    {
        get
        {
            return false;
        }
    }
    public override bool CanWrite
    {
        get
        {
            return true;
        }
    }
    public override long Length
    {
        get
        {
            throw new NotSupportedException();
        }
    }
    public override long Position
    {
        get
        {
            throw new NotSupportedException();
        }
        set
        {
            throw new NotSupportedException();
        }
    }
    public override void Flush()
    {
        //We do not flush the compression stream. At best this does nothing, at worse it
        //loses a few bytes. We do however flush the underlying stream to send bytes down the
        //wire.
        _finalSink.Flush();
    }
    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotSupportedException();
    }
    public override void SetLength(long value)
    {
        throw new NotSupportedException();
    }
    public override int Read(byte[] buffer, int offset, int count)
    {
        throw new NotSupportedException();
    }
    public override void Write(byte[] buffer, int offset, int count)
    {
        _compSink.Write(buffer, offset, count);
    }
    public override void WriteByte(byte value)
    {
        _compSink.WriteByte(value);
    }
    public override void Close()
    {
        _compSink.Close();
        _finalSink.Close();
        base.Close();
    }
    protected override void Dispose(bool disposing)
    {
        if(disposing)
        {
            _compSink.Dispose();
            _finalSink.Dispose();
        }
        base.Dispose(disposing);
    }
}

Стоит также отметить, что большинство пользовательских агентов, поддерживающих gzip-кодирование, также поддерживают deflate-кодирование. Хотя улучшение размера с помощью deflate незначительно (буквально несколько байтов), некоторые библиотеки в некоторых архитектурах справляются с deflate значительно лучше (это касается как сжатия, так и распаковки), поэтому всегда лучше отдавать предпочтение deflate, чем gzip со сжатием HTTP.

Вы пытались добавить gzip через IIS? Есть вопрос по этому поводу, так что посмотрите, что это такое. По сути, IIS делает все сжатие, поэтому вам не нужно.

Другие вопросы по тегам