首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

C/C++ 解決百度 HTTP 響應 gzip 解壓 Z_DATA_ERROR:Chunked 編碼與 zlib 正確使用

     編輯:程式設計綜合問答

問題根因

響應頭包含 Transfer-Encoding: chunked,**數據體為分塊傳輸編碼**,而非直接的 gzip 流。直接將 chunked 原始數據餵給 zlib 會導致 Z_DATA_ERROR。

HTTP 響應結構分層


HTTP/1.1 200 OK
Content-Encoding: gzip          ← 第 1 層:內容編碼 (gzip 壓縮)
Transfer-Encoding: chunked      ← 第 2 層:傳輸編碼 (分塊傳輸)
                                 ↓
[Chunk Size][CRLF][Chunk Data][CRLF]...[0][CRLF][CRLF]  ← 原始 Socket 讀取數據
                                 ↓
          去除 Chunk 封裝 → 純 gzip 壓縮流
                                 ↓
          zlib 解壓 → 原始 HTML/JSON

正確處理流程

  1. 讀取完整響應:根據 Content-Length 或 Transfer-Encoding: chunked 讀取完整 Body。
  2. 解碼 Chunked:解析 Size\r\nData\r\n 格式,拼接成連續字節流。
  3. 檢查 Content-Encoding:若為 gzip/deflate,調用 zlib 解壓。
  4. 處理多重編碼:罕見但合法 Content-Encoding: gzip, deflate,按順序解碼。

關鍵代碼片段 (C + zlib)


// 1. Chunked 解碼 (簡化版)
char* decode_chunked(const char* src, size_t len, size_t* out_len) {
    char* buf = malloc(len); // 最大不超過原長
    size_t pos = 0, w = 0;
    while (pos < len) {
        // 讀取十六進制尺寸
        char* end = strstr(src + pos, "\r\n");
        if (!end) break;
        size_t chunk_len = strtoul(src + pos, NULL, 16);
        pos = (end - src) + 2; // 跳過 \r\n
        if (chunk_len == 0) break; // 最後一塊
        memcpy(buf + w, src + pos, chunk_len);
        w += chunk_len;
        pos += chunk_len + 2; // 跳過 Data + \r\n
    }
    *out_len = w;
    return buf;
}

// 2. zlib 解壓 gzip (需設置 windowBits = 31)
int decompress_gzip(const char* src, size_t src_len, char** dst, size_t* dst_len) {
    z_stream strm = {0};
    strm.next_in = (Bytef*)src;
    strm.avail_in = src_len;
    // windowBits = 15 + 16 = 31 表示 gzip 封裝 (含 header/trailer)
    if (inflateInit2(&strm, 31) != Z_OK) return -1;
    
    char* out = malloc(src_len * 4); // 預估擴大 4 倍
    strm.next_out = (Bytef*)out;
    strm.avail_out = src_len * 4;
    
    int ret = inflate(&strm, Z_FINISH);
    if (ret != Z_STREAM_END) { inflateEnd(&strm); free(out); return -1; }
    
    *dst = out;
    *dst_len = strm.total_out;
    inflateEnd(&strm);
    return 0;
}

參考 Firefox 源碼 (nsHTTPCompressConv.cpp)

Firefox 在 nsHTTPCompressConv.cpp 中處理順序:
1. 接收原始 Socket 數據 → 2. nsIHttpChunkedDecoder 解碼 Chunked → 3. nsIContentEncodingDecoder (gzip/deflate) 解壓 → 4. 交給 HTML 解析器。
這證明必須先去 Chunked 再解壓。

常見坑

inflateInit2(&strm, 15) 而非 31
15 為原始 deflate (zlib 封裝),31 為 gzip 封裝。百度返回 gzip,需用 31。
未處理 Content-Encoding: deflate (無 gzip header)
部分站點返回裸 deflate,需 inflateInit2(&strm, -15) (wbits 負值表示無 header)。
忽略 Transfer-Encoding: identity 或無該頭
直接是 gzip 流,可跳過 Chunked 解碼。

工具驗證

# 1. 保存原始響應體 (含 chunked)
curl -v -H "Accept-Encoding: gzip" https://www.baidu.com --output raw.bin

# 2. 手動去 chunked (Python)
python3 -c "
import sys, re
data = open('raw.bin','rb').read()
# 簡易去 chunked
body = b''
i = 0
while i < len(data):
    nl = data.find(b'\r\n', i)
    size = int(data[i:nl], 16)
    if size == 0: break
    i = nl + 2
    body += data[i:i+size]
    i += size + 2
open('ungz.bin','wb').write(body)
"

# 3. 解壓驗證
gzip -dc ungz.bin | head -c 200
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved