天天看點

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資料。