Реализации Python: Inflate и Deflate
Я взаимодействую с сервером, который требует, чтобы переданные ему данные были сжаты с помощью алгоритма Deflate (кодировка Хаффмана + LZ77), а также отправляет данные, которые мне нужно надуть.
Я знаю, что Python включает Zlib и что библиотеки C в Zlib поддерживают вызовы Inflate и Deflate, но они, по-видимому, не предусмотрены модулем Python Zlib. Он обеспечивает сжатие и декомпрессию, но когда я звоню, например, следующее:
result_data = zlib.decompress( base64_decoded_compressed_string )
Я получаю следующую ошибку:
Error -3 while decompressing data: incorrect header check
Gzip не лучше; при совершении вызова, например:
result_data = gzip.GzipFile( fileobj = StringIO.StringIO( base64_decoded_compressed_string ) ).read()
Я получаю сообщение об ошибке:
IOError: Not a gzipped file
что имеет смысл, поскольку данные являются дефлированным файлом, а не истинным файлом Gzipped.
Теперь я знаю, что есть доступная реализация Deflate (Pyflate), но я не знаю о внедрении Inflate.
Кажется, что есть несколько вариантов:
- Найти существующую реализацию (идеальную) Inflate и Deflate в Python
- Напишите мое собственное расширение Python для библиотеки zlib c, которая включает в себя Inflate и Deflate
- Вызовите что-нибудь еще, которое может быть выполнено из командной строки (например, Ruby script, поскольку вызовы Inflate/Deflate в zlib полностью завернуты в Ruby)
Я ищу решение, но без решения я буду благодарен за идеи, конструктивные мнения и идеи.
Дополнительная информация:
Результат дефляции (и кодирования) строки должен для тех целей, которые мне нужны, дать тот же результат, что и следующий фрагмент кода С#, где входным параметром является массив байтов UTF, соответствующий сжатым данным:
public static string DeflateAndEncodeBase64(byte[] data)
{
if (null == data || data.Length < 1) return null;
string compressedBase64 = "";
//write into a new memory stream wrapped by a deflate stream
using (MemoryStream ms = new MemoryStream())
{
using (DeflateStream deflateStream = new DeflateStream(ms, CompressionMode.Compress, true))
{
//write byte buffer into memorystream
deflateStream.Write(data, 0, data.Length);
deflateStream.Close();
//rewind memory stream and write to base 64 string
byte[] compressedBytes = new byte[ms.Length];
ms.Seek(0, SeekOrigin.Begin);
ms.Read(compressedBytes, 0, (int)ms.Length);
compressedBase64 = Convert.ToBase64String(compressedBytes);
}
}
return compressedBase64;
}
Запуск этого .NET-кода для строки "deflate and encode me" дает результат
7b0HYBxJliUmL23Ke39K9UrX4HShCIBgEyTYkEAQ7MGIzeaS7B1pRyMpqyqBymVWZV1mFkDM7Z28995777333nvvvfe6O51OJ/ff/z9cZmQBbPbOStrJniGAqsgfP358Hz8iZvl5mbV5mi1nab6cVrM8XeT/Dw==
Когда "deflate and encode me" запускается через Python Zlib.compress(), а затем закодирован base64, результатом является "eJxLSU3LSSxJVUjMS1FIzUvOT0lVyE0FAFXHB6k =".
Понятно, что zlib.compress() не является реализацией того же алгоритма, что и стандартный алгоритм Deflate.
Дополнительная информация:
Первые 2 байта данных дефляции .NET( "7b0HY..." ) после декодирования b64 являются 0xEDBD, что не соответствует данным Gzip (0x1f8b), данным BZip2 (0x425A) или Zlib (0x789C) данных.
Первые 2 байта сжатых данных Python ( "eJxLS..." ) после декодирования b64 равны 0x789C. Это заголовок Zlib.
решаемые
Чтобы обрабатывать сырые дефлаты и раздувать, без заголовка и контрольной суммы, необходимо следующее:
В режиме deflate/compress: разделите первые два байта (заголовок) и последние четыре байта (контрольная сумма).
При раздувании/распаковке: есть второй аргумент для размера окна. Если это значение отрицательное, оно подавляет заголовки. вот мои методы в настоящее время, в том числе кодирование/декодирование base64 - и работают нормально:
import zlib
import base64
def decode_base64_and_inflate( b64string ):
decoded_data = base64.b64decode( b64string )
return zlib.decompress( decoded_data , -15)
def deflate_and_base64_encode( string_val ):
zlibbed_str = zlib.compress( string_val )
compressed_string = zlibbed_str[2:-4]
return base64.b64encode( compressed_string )
Ответы
Ответ 1
Это дополнение к ответу MizardX, дающее некоторые объяснения и предысторию.
См. http://www.chiramattel.com/george/blog/2007/09/09/deflatestream-block-length-does-not-match.html
Согласно RFC 1950, поток zlib, построенный по умолчанию, состоит из:
- 2-байтовый заголовок (например, 0x78 0x9C)
- поток спускания - см. RFC 1951
- контрольная сумма Adler-32 несжатых данных (4 байта)
С# DeflateStream
работает над (вы уже догадались) потоком спускания. Код MizardX сообщает модулю zlib, что данные являются сырым потоком спускания.
Наблюдения: (1) Можно надеяться, что метод С# "дефляция", производящий более длинную строку, происходит только с коротким входом (2) Используя поток сыпучего дефлята без контрольной суммы Adler-32? Бит рискован, если не заменить что-то лучше.
Обновление
сообщение об ошибке Block length does not match with its complement
Если вы пытаетесь раздуть сжатые данные с помощью С# DeflateStream
, и вы получите это сообщение, то вполне возможно, что вы даете ему поток zlib, а не поток спускания.
См. Как вы используете DeflateStream для части файла?
Также скопируйте/вставьте сообщение об ошибке в поиск Google, и вы получите многочисленные хиты (в том числе и тот, который стоит перед этим ответом), говорящий примерно то же самое.
Java Deflater
... используемый "сайтом"... С# DeflateStream "довольно прост и протестирован против реализации Java". Каким из следующих возможных конструкторов Java Deflater является веб-сайт с использованием?
public Deflater(int level, boolean nowrap)
Создает новый компрессор с использованием указанного уровня сжатия. Если "nowrap" истинно, поля заголовка ZLIB и контрольной суммы не будут использоваться для поддержки формата сжатия, используемого как в GZIP, так и в PKZIP.
public Deflater(int level)
Создает новый компрессор с использованием указанного уровня сжатия. Сжатые данные будут сгенерированы в формате ZLIB.
public Deflater()
Создает новый компрессор с уровнем сжатия по умолчанию. Сжатые данные будут сгенерированы в формате ZLIB.
Однострочный дефлатер после выброса 2-байтового заголовка zlib и 4-байтовой контрольной суммы:
uncompressed_string.encode('zlib')[2:-4] # does not work in Python 3.x
или
zlib.compress(uncompressed_string)[2:-4]
Ответ 2
Вы можете использовать модуль zlib
для раздувания/дефляции данных. Модуль gzip
использует его внутренне, но добавляет заголовок файла, чтобы превратить его в gzip файл. Глядя на файл gzip.py
, что-то вроде этого может работать:
import zlib
def deflate(data, compresslevel=9):
compress = zlib.compressobj(
compresslevel, # level: 0-9
zlib.DEFLATED, # method: must be DEFLATED
-zlib.MAX_WBITS, # window size in bits:
# -15..-8: negate, suppress header
# 8..15: normal
# 16..30: subtract 16, gzip header
zlib.DEF_MEM_LEVEL, # mem level: 1..8/9
0 # strategy:
# 0 = Z_DEFAULT_STRATEGY
# 1 = Z_FILTERED
# 2 = Z_HUFFMAN_ONLY
# 3 = Z_RLE
# 4 = Z_FIXED
)
deflated = compress.compress(data)
deflated += compress.flush()
return deflated
def inflate(data):
decompress = zlib.decompressobj(
-zlib.MAX_WBITS # see above
)
inflated = decompress.decompress(data)
inflated += decompress.flush()
return inflated
Я не знаю, соответствует ли это точно тому, что требуется вашему серверу, но эти две функции могут округлять любые данные, которые я пытался.
Параметры сопоставляются непосредственно с тем, что передается библиотечным функциям zlib.
Python → C
zlib.compressobj(...)
→ deflateInit(...)
compressobj.compress(...)
→ deflate(...)
zlib.decompressobj(...)
→ inflateInit(...)
decompressobj.decompress(...)
→ inflate(...)
Конструкторы создают структуру и заполняют ее значениями по умолчанию и передают их в init-functions.
Методы compress
/decompress
обновляют структуру и передают ее в inflate
/deflate
.