C++使用libcurl+zlib解壓http gzip數據

開發背景

在android ndk環境下使用libcurl發起http請求,不巧的是,當前使用的libcurl編譯時沒有包含zlib模塊,導致無法解壓gzip數據。

考慮到ndk自帶zlib包,可以使用zlib手動解壓gzip數據,避免再次編譯libcurl。

源碼實現

(1)使用zlib解壓gz數據

int gzDecompress(const char *src, int srcLen, const char *dst, int* dstLen){
  z_stream strm;
  strm.zalloc = NULL;
  strm.zfree = NULL;
  strm.opaque = NULL;

  strm.avail_in = srcLen;
  strm.avail_out = *dstLen;
  strm.next_in = (Bytef *)src;
  strm.next_out = (Bytef *)dst;

  int err = -1;
  err = inflateInit2(&strm, MAX_WBITS + 16); /*zlib解壓gz數據*/
  if (err == Z_OK){
    err = inflate(&strm, Z_FINISH);
    if (err == Z_STREAM_END){ 
      *dstLen = strm.total_out; /* 解壓成功 */
    } else{
      inflateEnd(&strm); /* 解壓失敗 */
      return err;
    }
  } else{
    inflateEnd(&strm); /* 解壓初始化失敗 */
    return err;
  }

  inflateEnd(&strm);
  return err;
}

bool tryDecompressGzip(const char *src, int srcLen, string& resData) {
  bool success = false;

  for (int i = 1; i <= 3; i++) {
    int buffSize = (srcLen * 10 + 3000)*i; /* 解壓緩衝區大小,自動擴容 */
    char* buffData = (char*)malloc(buffSize);
    memset(buffData, 0, buffSize);

    int ret = gzDecompress(src, srcLen, buffData, &buffSize);
    if (Z_STREAM_END == ret) { /* 解壓成功 */
      resData = buffData;
      free(buffData);
      success = true;
      break;
    }
    else {
      free(buffData); /* 解壓失敗,擴容後再次嘗試 */
    }
  }
  
  return success;
}

需要注意的地方是buffSize的大小,假設未解壓的大小爲1000,解壓後可能爲3000,也可能爲10000。 考慮到gzip壓縮後的大小可能爲原有大小的70%~10%,分配10倍空間應該是足夠的。

(2)libcurl請求header加上gzip標識

curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip");

(3)libcurl設置讀取響應header的回調函數

int contentEncodingGzip = 0; /* 響應頭是否有gzip */
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, OnHeaderData);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &contentEncodingGzip);

(4)添加讀取響應header的回調函數

/*libcurl收到http header的回調*/
static size_t OnHeaderData(void *ptr, size_t size, size_t nmemb, void *stream)
{
  if (strncmp((char *)ptr, "Content-Encoding: gzip", strlen("Content-Encoding: gzip")) == 0) {
    *((int*)stream) = 1; 
  }

  return size * nmemb;
}

檢測響應header是否包含 "Content-Encoding: gzip"

(5)若返回gzip數據,執行解壓

if (1 == contentEncodingGzip) { /* 響應頭有gzip標識,解壓響應body */
    string decompress;
    if (tryDecompressGzip(strResponse.data(), strResponse.size(), decompress)) {
        strResponse = decompress;
    }
}

其中 strResponse爲解壓前的數據。

至此可以實現用zlib解壓gzip數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章