在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。
下面是我下载网页的代码(写了一个基本的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。