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数据。

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