Кажется, я не могу понять, как обращаться с данными, закодированными с помощью gzip, в Delphi. Я создал пример приложения:
program GetRequestWithGzip;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.Classes,
System.Net.HttpClient,
System.Net.URLClient,
System.NetConsts;
var
Client: TNetHTTPClient;
Response: IHTTPResponse;
Stream: TMemoryStream;
HttpHeaders: TNetHeaders;
begin
try
Client := TNetHTTPClient.Create(nil);
try
// Set the Accept-Encoding header
SetLength(HttpHeaders, 1);
HttpHeaders[0].Name := 'Accept-Encoding';
HttpHeaders[0].Value := 'gzip, deflate';
// Prepare a stream for the response
Stream := TMemoryStream.Create;
try
// Perform the GET request
Response := Client.Get('https://www.google.com', Stream, HttpHeaders);
// Check if the response was gzip-encoded
if Response.ContentEncoding = 'gzip' then
begin
Writeln('Response is gzip encoded. Please decompress to read.');
// Here, you would add your gzip decompression logic
end
else
begin
// If the response is not encoded, simply output the response
Stream.Position := 0; // Reset stream position to read from the beginning
Writeln('Response received: ', Stream.Size, ' bytes');
// Further processing of Stream as needed
end;
finally
Stream.Free;
end;
finally
Client.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Writeln('Press Enter to exit.');
Readln;
end.
Когда я работаю с этим вот так, я всегда получаю всего 10 байтов, указывающих, что у меня есть заголовок:
unit TGZDecompression;
interface
uses
System.SysUtils, System.Classes, System.ZLib;
function DecompressGzipStream(const Input: TStream): TBytes;
implementation
type
TGzipHeader = packed record
ID1, ID2, CM, FLG: Byte;
MTIME: Cardinal;
XFL, OS: Byte;
end;
TGzipFlag = (FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT);
TGzipFlags = set of TGzipFlag;
procedure SkipGzipHeader(const Stream: TStream);
var
Header: TGzipHeader;
Flags: TGzipFlags;
XLEN: Word;
B: Byte;
begin
Stream.Read(Header, SizeOf(Header));
if (Header.ID1 <> $1F) or (Header.ID2 <> $8B) then
raise Exception.Create('Not a valid gzip stream');
Flags := TGzipFlags(Byte(Header.FLG));
if FEXTRA in Flags then
begin
Stream.Read(XLEN, SizeOf(XLEN));
Stream.Seek(XLEN, soCurrent); // Skip the extra fields
end;
if FNAME in Flags then
repeat
Stream.Read(B, 1);
until B = 0;
if FCOMMENT in Flags then
repeat
Stream.Read(B, 1);
until B = 0;
if FHCRC in Flags then
Stream.Seek(2, soCurrent); // Skip the CRC16 for the header
end;
function DecompressGzipStream(const Input: TStream): TBytes;
var
DecompressionStream: TDecompressionStream;
MemoryStream: TMemoryStream;
begin
SkipGzipHeader(Input);
DecompressionStream := TDecompressionStream.Create(Input, False); // Do not own the stream
MemoryStream := TMemoryStream.Create;
try
MemoryStream.CopyFrom(DecompressionStream, 0);
SetLength(Result, MemoryStream.Size);
MemoryStream.Position := 0;
MemoryStream.Read(Result, MemoryStream.Size);
finally
DecompressionStream.Free;
MemoryStream.Free;
end;
end;
end.
Заголовок имеет следующие значения в окне просмотра:
ID1=31
ID2=139
CM=8
FLG=0
TMIME=0
XFL=2
OS=255
Поскольку флаги не установлены, заголовки не пересылают поток дальше 10 байт. Когда я звоню на линию:
MemoryStream.CopyFrom(DecompressionStream, 0);
который распакует данные, которые я получаю:
Исключение первого шанса по адресу $00007FFFBF9753AC. Класс исключения EZDecompressionError с сообщением «ошибка данных».
Я просмотрел документацию Delphi и не нашел класса данных GZIP, который мог бы мне помочь. Нужно ли мне создавать его в 2024 году?
Хорошее объяснение здесь: stackoverflow.com/a/22311297
Я не слишком хорошо знаком с TNetHTTPClient, но поскольку это всего лишь оболочка для HTTP-клиента, предоставляемого ОС, разве он не поддерживает автоматическую распаковку сжатого ответа?
Возможно, вы могли бы попробовать использовать 7z.dll для распаковки данных. Существует множество заголовков Delphi для 7z.dll.
Похоже, вам не хватает аргумента в TDecompressionStream.Create
. Вы хотите TDecompressionStream.Create(Input. -15, False)
распаковать необработанный сдутый файл. Или вы можете просто исключить всю обработку заголовка gzip и позволить декомпрессору справиться с этим с помощью TDecompressionStream.Create(Input, 15+32, False)
. Это также автоматически распакует поток zlib, если он встречается вместо потока gzip. (Вы принимаете deflate так же, как и gzip.)
Благодаря Марку Адлеру, я просмотрел документацию, но мой поиск не нашел ее, должно быть, я искал не ту справку.
В приведенном ниже коде показан ответ Марка для всех, кто любит его копировать:
function DecompressGzipStream(const Input: TStream): TBytes;
var
DecompressionStream: TDecompressionStream;
MemoryStream: TMemoryStream;
begin
// SkipGzipHeader(Input);
DecompressionStream := TDecompressionStream.Create(Input, 15 + 32, False);
MemoryStream := TMemoryStream.Create;
try
MemoryStream.CopyFrom(DecompressionStream, 0);
SetLength(Result, MemoryStream.Size);
MemoryStream.Position := 0;
MemoryStream.Read(Result, MemoryStream.Size);
finally
DecompressionStream.Free;
MemoryStream.Free;
end;
end;
В справке говорится:
Примечание. При распаковке файлов добавление к 32 указывает на автоматическое определение заголовков форматов
zlib
илиgzip
. Дополнительную информацию см. в разделе «Расширенные функции» Руководства по zlib.
Или через «Справка» > «Справка Delphi» > «Справка системной библиотеки» > System.ZLib.TZDecompressionStream.Create:
"текст помощи" откуда? Пожалуйста, дайте ссылку на него.
@AmigoJack, я обновил для вас ответ по онлайн-ссылке: docwiki.embarcadero.com/Libraries/Athens/en/…
О, это не для меня одного - это для всех, кто готов ответить. Если вы цитируете тексты, обратите внимание на ссылки и форматирование в них, иначе смысл и ссылки могут быть упущены.
Я использую ZLib*.pas TheLazyTomcat вместе с двоичным файлом
zlib.dll
, предоставленным им для zlib 1.2.13 в целом, а также для gzip, который там просто рассматривается как другой тип потока. До сих пор я мог без проблем расшифровать ответы какdeflate
, так иgzip
.