protected function httpRequestCurl($action, array $data, $method = 'POST', $options = [ ]) {
        $url = $this->api_uri . "/" . $action;
        $ch = curl_init();
        $options = array_merge($this->options, $options);
        if ($method == 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            if ($this->fileUpload === true) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
                $this->fileUpload = false;
            } else {
                curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data, '', '&'));
            }
        } else {
            $url .= '?' . http_build_query($data, '', '&');
            if ($method != 'GET') {
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
            }
        }
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
        curl_setopt($ch, CURLOPT_TIMEOUT_MS, $options['timeout']);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $options['connect_timeout']);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->httpHeaders);
        $response = curl_exec($ch);
        $this->lastResponseInfo = curl_getinfo($ch);
        curl_close($ch);

        if (!$response) {
            throw new Sailthru_Client_Exception(
                "Bad response received from $url",
                Sailthru_Client_Exception::CODE_RESPONSE_EMPTY
            );
        }

        // parse headers and body
        $parts = explode("\r\n\r\nHTTP/", $response);
        $parts = (count($parts) > 1 ? 'HTTP/' : '') . array_pop($parts); // deal with HTTP/1.1 100 Continue before other headers
        list($headers, $body) = explode("\r\n\r\n", $parts, 2);
        $this->lastRateLimitInfo[$action][$method] = self::parseRateLimitHeaders($headers);

        return $body;
    }