在http响应中解压gzip编码

Decompressing gzip encoding in http response

下面是我下载网页的代码(写了一个基本的wget

HTTP请求:

port = 80
#assume ip is known
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
http_message = "GET /" + path + " HTTP/1.1\r\n"
http_message += "Host: " + website + "\r\n"
http_message += "Accept: text/html\r\n"
http_message += "Accept-Language: en-US,en;q=0.9\r\n"
http_message += "Accept-Encoding: gzip, deflate\r\n"
http_message += "User-Agent: Chrome/92.0.4515.131 Mozilla/5.0 (X11; Linux x86_64)\r\n"
http_message += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n"
http_message += "Connection: keep-alive\r\n"
http_message += "\r\n"

x = sock.send(http_message.encode())

data = sock.recv(262144).decode("utf-8", "ignore")
print(data)

终端上的 HTTP 响应:

HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Tue, 07 Sep 2021 18:52:00 GMT
Content-Type: text/html
Last-Modified: Thu, 31 Oct 2019 09:15:26 GMT
Transfer-Encoding: chunked
Connection: keep-alive
ETag: W/"5dbaa62e-8d0e"
Content-Encoding: gzip

272b
n~}.^~ןO?^kd[%+djj      $s>t7H3o>6Osn>
          D˴?vuv{&oơ;-%Z8jYvNy]ӌ7q<n6Mݣ#g7-ocO
                                              g0M|8vc9ؒ̉mA5MkbH~zӊoE{-FS;۷!Q<iEs-Q4t

8�<O~Ֆ҉lo?`ǖ    6(~Lc7. *7ȳ#l6_#Ai?:m}';k278W9$/2~�dn?n-?/\uS>6]1..vM|:98a`0{^p@/=/)j=
r(=/zP}56HfMK36̍F{cMhI<)ؚ+68 u2
y/
  Ui
5iîBENöL߷mq]t|
:
 =0
   g;tĹ?q}  ЙJ
                  S\'0ZМ2ͦ,ޫOM:Ѳ90]LL('x'O1O?ߝ{>byx$ơsm݉.|H|G(41۩#?َ;{=
                                                                     ҺwGOG'O<T٣Guy9Q
_&?q-7GZJ-urD;scKo*&V]j:    9>"vYs
        -K.0"38үsl>v;Y0vv¨[U^UmQ`N[T
                                    HL6ݵ7(~a^3"w"?#80&B^]"7U~b
                                                              -wJyXx,�aymwo
                                                                           ^ݚBLJ=)JMܶS{/[z3&ØW&g(w)di͍bL S:R$B2֯m[|#XBm^Ei9b|,~D2G*.
                    p7+(ù!u.w ?>t+}M,x!7'Ό9|0/#S/1UbA5Fui0dPO#枽,s˄7 l5$OVin֟eAӋ:YPLsӔm}۩c
             ^sEh6S


mӽwG=X}ΤV*-جk70FI`!jxCr"ϐ+bUo
                             RE[/WR1k|%j    eBB(l3^H6cuP]PM-i[%h

奇怪的输出继续....

以下输出采用 gzip 格式,我无法将其解压缩为 txt 文件。 将奇怪的输出(http 响应除外)从终端复制到 output.txt.gz 使用的 gzip 模块:

import gzip
f=gzip.open('output.txt.gz','rb')
file_content=f.read()
print (file_content)

输出:

gzip.BadGzipFile: Not a gzipped file (b'27')

找不到 gzip 的确切格式..

此外,如果我不解码响应

data = sock.recv(262144)

我得到一个巨大的二进制文件,这可能有助于...Binary Response Image

您没有考虑 HTTP 响应的 Transfer-Encoding: chunked 格式。

事实上,您甚至没有考虑 HTTP 协议本身。您的代码完全忽略了 HTTP 具有其工作方式的结构和规则这一事实。您只是从套接字读取原始字节,使用 UTF-8 将 所有内容 解码为 Unicode(破坏不应解码的所有内容),将 Unicode 打印到终端,然后 copy/pasting 将其转换为扩展名为 .gz 的文本文件。 GZIP 不是文本。

并非所有 HTTP 响应数据都是 GZIP 数据,其中一些是 HTTP 块数据。您需要实际 正确 处理 HTTP 协议。首先读取 ONLY HTTP 响应 headers,然后解析它们以确定响应的格式 body。如果响应是 chunked,则正确读取和解析块,将每个块的数据部分 保存到输出文件中,as-is 作为二进制而不是文本.

有关此内容的更多详细信息,请参阅 my answer to Differ between header and content of http server response (sockets)

例如,f.read() 抱怨的 '27' 来自响应中的初始 272b,它指定存储在中的 GZIP 数据的字节大小 (10027)第一个块。您正在将整个块保存到 .gz 文件,而不仅仅是块的 GZIP 部分。

可以存在 1 个以上的块,因此您必须单独读取和解析每个块。响应数据将以大小为 0 字节的块终止。

有关 HTTP chunked 编码工作原理的更多详细信息,请参阅 RFC 2616 Section 3.6.1 and RFC 7230 Section 4.1