libcurl

时间:2017-10-26 14:01:06

标签: curl proxy timeout libcurl

情况:上传和下载大文件。

我偶尔会遇到一个代理,它会立即接受完整的请求(以完全的局域网速度),然后通过互联网慢慢转发。它通常是一些未知的公司代理,但今天我可以用BurpSuite代理模拟它。

为处理这种情况的CURL配置超时似乎很困难。 目前,我正在使用低速超时,因为我不知道完整传输需要多长时间(即我不能使用通常的CURLOPT_TIMEOUT)。

这种代理问题,整个请求在开始时发送(平均速度= 1.7e + 07 B / s),然后在十几秒左右后,平均速度降到阈值以下。 。超时!

大型下载情况再次不同。 一个小的GET请求进入代理,然后在将它传递到客户端之前先下载整个响应。我有时在现场看到这种情况,有很长的延迟,然后突然下载很快完成。

知道应该如何正确处理这类事情?

2 个答案:

答案 0 :(得分:0)

除了curl的内置连接超时之外,您还必须编写自定义读取和写入超时。

这是使用curl_multi最简单的方法(您可以通过CURLOPT_PROGRESSFUNCTION使用curl_easy编写解决方案,但我觉得这些东西太脆弱了我不喜欢。)

这是一个“阻塞读取”的代码,使用多接口(curl_data是一个带有东西的自定义结构):

static bool curlPerform(struct curl_data* ctrl) {
  int timeout = ctrl->timeout;

  struct timespec timeoutTimestamp, currentTimestamp;

  clock_gettime(CLOCK_MONOTONIC, &timeoutTimestamp);

  int fdEvents = 0;

  do {
    if (*ctrl->interrupted) {
        return false;
    }

    int running;

    LOG("Invoking perform()");

    CURLMcode result = curl_multi_perform(ctrl->multi, &running);

    if (result) {
        handleMultiError(ctrl, result);
        return true;
    }

    if (checkResult(ctrl)) {
        LOG("Has completed connections, bailing");
        return true;
    }

    if (running == 0) {
        LOG("No running connections, bailing");
        return true;
    }

    if (*ctrl->interrupted) {
        return false;
    }

    long waitTime;
    curl_multi_timeout(ctrl->multi, &waitTime);

    if (timeout < waitTime) {
        waitTime = timeout;
    }

    LOG("Timeout is %d, waiting for %ld", timeout, waitTime);

    if (waitTime > 0) {
        CURLMcode waitResult = curl_multi_wait(ctrl->multi, NULL, 0, waitTime, &fdEvents);

        if (waitResult) {
            handleMultiError(ctrl, waitResult);
            return true;
        }
    }

    LOG("Sockets with events: %d", fdEvents);

    if (fdEvents) {
        timeout = ctrl->timeout;
    } else {
        clock_gettime(CLOCK_MONOTONIC, &currentTimestamp);

        timeout -= ((currentTimestamp.tv_sec - timeoutTimestamp.tv_sec) * 1000);

        int diff_nsec = currentTimestamp.tv_nsec - timeoutTimestamp.tv_nsec;

        if (diff_nsec < 0) {
            timeout += 1000;
            timeout -= ((diff_nsec + 1000000000) / 1000000);
        }

        if (timeout <= 0) {
            LOG("Remaining time is %d, bailing", timeout);
            throwTimeout(ctrl->env, ctrl->headerPairCount);
            return true;
        }

        timeoutTimestamp = currentTimestamp;
    }
  } while (true);
}

上面的代码是this code的精简版,删除了大多数不相关的内容。它基本上在curl_multi之上实现了curl_easy,增加了在错误,连接和读取超时时退出的能力。

为了使代码适应您的需求,您必须实施两项小改动:

  1. 将读取超时与写入超时分开;
  2. 仅在数据的第一个字节(不是标题!)到达后才开始强制执行读取超时。
  3. 您可以提供CURLOPT_WRITEFUNCTION / CURLOPT_READFUNCTION来跟踪请求/响应进度。一旦读取功能上传了所有数据,请停止强制执行写入超时(例如,通过将ctrl-&gt; timeout设置为INT_MAX)。一旦写入功能记录第一个内容字节的到来,就开始强制执行读取超时。

答案 1 :(得分:0)

这是我最终使用的代码,所以我接受了

问题(起初)是大型上传速度非常快,然后在代理转发数据时暂停。这将在代理完成传输之前触发低速超时并返回响应。

因此,下面的代码会在上传完成后禁用低速超时,然后只需等待最终响应(不理想)。

但是,这对通过这个奇怪的代理下载没有帮助。

在完成下载之前,代理会下载但不会转发任何内容(除非每分钟一个字节)。因此,如果下载量大于约20MB,则低速超时会跳闸。

所以最后我完全禁用了所有超时,并且不确定我是否可以做任何更好的事情......

以下是代码,但请再次注意,它对大量下载没有帮助。 如果你将低速超时降低到每分钟大约1个字节或类似的东西,那可能没问题。

int progress_callback(void * clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
   // a pseudo-member function, but its actually a static member for callback
   NetConfig * self = static_cast<NetConfig*>(clientp);

   // Now deal with the low-speed timeout.
   // some proxies accept the whole transfer at once and then forward,
   // giving the appearance of the connection "hung" at the end.
   // We cannot turn off the low-speed limit when we have a proxy configured,
   // because some sites have transparent proxies, so the plain network
   // behaves in this fashion.

   // IF we have finished the transfer, then disable the low speed timeout.

   // we only do this in the Timeout_Slow mode
   if (self->timeout_mode == Timeout_Slow)
   {
      bool want_lowspeed = (dltotal > 0 and dlnow < dltotal)
                        or (ultotal > 0 and ulnow < ultotal);

      if (want_lowspeed != self->lowspeed_limit_enabled)
      {
         if (want_lowspeed)
         {
            curl_easy_setopt(self->handle, CURLOPT_LOW_SPEED_LIMIT, timeout_slow.first);
            curl_easy_setopt(self->handle, CURLOPT_LOW_SPEED_TIME, timeout_slow.second);
         }
         else
         {
            curl_easy_setopt(self->handle, CURLOPT_LOW_SPEED_LIMIT, (long)0L);
            curl_easy_setopt(self->handle, CURLOPT_LOW_SPEED_TIME, (long)0L);
         }
         self->lowspeed_limit_enabled = want_lowspeed;
      }
   }

   return 0;
}