2019-03-01 06:54:03 +01:00
|
|
|
/*
|
|
|
|
* IXHttpClient.cpp
|
|
|
|
* Author: Benjamin Sergeant
|
|
|
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "IXHttpClient.h"
|
2019-09-23 19:25:23 +02:00
|
|
|
|
|
|
|
#include "IXSocketFactory.h"
|
2019-03-01 06:54:03 +01:00
|
|
|
#include "IXUrlParser.h"
|
2019-08-30 21:48:18 +02:00
|
|
|
#include "IXUserAgent.h"
|
2019-03-01 06:54:03 +01:00
|
|
|
#include "IXWebSocketHttpHeaders.h"
|
2019-09-23 19:25:23 +02:00
|
|
|
#include <assert.h>
|
|
|
|
#include <cstring>
|
2019-03-01 06:54:03 +01:00
|
|
|
#include <iomanip>
|
2019-09-23 19:25:23 +02:00
|
|
|
#include <sstream>
|
2019-03-01 06:54:03 +01:00
|
|
|
#include <vector>
|
|
|
|
#include <zlib.h>
|
|
|
|
|
|
|
|
namespace ix
|
|
|
|
{
|
|
|
|
const std::string HttpClient::kPost = "POST";
|
|
|
|
const std::string HttpClient::kGet = "GET";
|
|
|
|
const std::string HttpClient::kHead = "HEAD";
|
2019-06-03 20:38:56 +02:00
|
|
|
const std::string HttpClient::kDel = "DEL";
|
|
|
|
const std::string HttpClient::kPut = "PUT";
|
2019-03-01 06:54:03 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
HttpClient::HttpClient(bool async)
|
|
|
|
: _async(async)
|
|
|
|
, _stop(false)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
2019-06-06 02:04:24 +02:00
|
|
|
if (!_async) return;
|
2019-03-01 06:54:03 +01:00
|
|
|
|
2019-06-06 02:04:24 +02:00
|
|
|
_thread = std::thread(&HttpClient::run, this);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
HttpClient::~HttpClient()
|
|
|
|
{
|
2019-06-06 02:04:24 +02:00
|
|
|
if (!_thread.joinable()) return;
|
2019-03-01 06:54:03 +01:00
|
|
|
|
2019-06-06 02:04:24 +02:00
|
|
|
_stop = true;
|
|
|
|
_condition.notify_one();
|
|
|
|
_thread.join();
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
2019-09-30 06:13:11 +02:00
|
|
|
void HttpClient::setTLSOptions(const SocketTLSOptions& tlsOptions)
|
|
|
|
{
|
|
|
|
_tlsOptions = tlsOptions;
|
|
|
|
}
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url, const std::string& verb)
|
2019-06-06 02:04:24 +02:00
|
|
|
{
|
|
|
|
auto request = std::make_shared<HttpRequestArgs>();
|
|
|
|
request->url = url;
|
|
|
|
request->verb = verb;
|
|
|
|
return request;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HttpClient::performRequest(HttpRequestArgsPtr args,
|
|
|
|
const OnResponseCallback& onResponseCallback)
|
|
|
|
{
|
2019-08-15 04:53:57 +02:00
|
|
|
assert(_async && "HttpClient needs its async parameter set to true "
|
|
|
|
"in order to call performRequest");
|
2019-06-06 02:04:24 +02:00
|
|
|
if (!_async) return false;
|
|
|
|
|
|
|
|
// Enqueue the task
|
|
|
|
{
|
|
|
|
// acquire lock
|
|
|
|
std::unique_lock<std::mutex> lock(_queueMutex);
|
|
|
|
|
|
|
|
// add the task
|
|
|
|
_queue.push(std::make_pair(args, onResponseCallback));
|
|
|
|
} // release lock
|
|
|
|
|
|
|
|
// wake up one thread
|
|
|
|
_condition.notify_one();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpClient::run()
|
|
|
|
{
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
HttpRequestArgsPtr args;
|
|
|
|
OnResponseCallback onResponseCallback;
|
|
|
|
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lock(_queueMutex);
|
|
|
|
|
|
|
|
while (!_stop && _queue.empty())
|
|
|
|
{
|
|
|
|
_condition.wait(lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_stop) return;
|
|
|
|
|
|
|
|
auto p = _queue.front();
|
|
|
|
_queue.pop();
|
|
|
|
|
|
|
|
args = p.first;
|
|
|
|
onResponseCallback = p.second;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_stop) return;
|
|
|
|
|
|
|
|
HttpResponsePtr response = request(args->url, args->verb, args->body, args);
|
|
|
|
onResponseCallback(response);
|
|
|
|
|
|
|
|
if (_stop) return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
HttpResponsePtr HttpClient::request(const std::string& url,
|
|
|
|
const std::string& verb,
|
|
|
|
const std::string& body,
|
|
|
|
HttpRequestArgsPtr args,
|
|
|
|
int redirects)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
2019-06-06 22:48:53 +02:00
|
|
|
// We only have one socket connection, so we cannot
|
2019-06-06 03:47:48 +02:00
|
|
|
// make multiple requests concurrently.
|
|
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
|
|
|
2019-03-01 06:54:03 +01:00
|
|
|
uint64_t uploadSize = 0;
|
|
|
|
uint64_t downloadSize = 0;
|
|
|
|
int code = 0;
|
|
|
|
WebSocketHttpHeaders headers;
|
|
|
|
std::string payload;
|
2019-06-23 23:54:21 +02:00
|
|
|
std::string description;
|
2019-03-01 06:54:03 +01:00
|
|
|
|
|
|
|
std::string protocol, host, path, query;
|
|
|
|
int port;
|
|
|
|
|
2019-05-06 23:45:02 +02:00
|
|
|
if (!UrlParser::parse(url, protocol, host, path, query, port))
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "Cannot parse url: " << url;
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::UrlMalformed,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
ss.str(),
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool tls = protocol == "https";
|
|
|
|
std::string errorMsg;
|
2019-09-30 06:13:11 +02:00
|
|
|
_socket = createSocket(tls, errorMsg, _tlsOptions);
|
2019-03-01 06:54:03 +01:00
|
|
|
|
|
|
|
if (!_socket)
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::CannotCreateSocket,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
errorMsg,
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Build request string
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << verb << " " << path << " HTTP/1.1\r\n";
|
|
|
|
ss << "Host: " << host << "\r\n";
|
|
|
|
|
2019-06-06 02:04:24 +02:00
|
|
|
if (args->compress)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
ss << "Accept-Encoding: gzip"
|
|
|
|
<< "\r\n";
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Append extra headers
|
2019-06-06 02:04:24 +02:00
|
|
|
for (auto&& it : args->extraHeaders)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
ss << it.first << ": " << it.second << "\r\n";
|
|
|
|
}
|
|
|
|
|
2019-06-03 23:02:54 +02:00
|
|
|
// Set a default Accept header if none is present
|
|
|
|
if (headers.find("Accept") == headers.end())
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
ss << "Accept: */*"
|
|
|
|
<< "\r\n";
|
2019-06-03 23:02:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set a default User agent if none is present
|
|
|
|
if (headers.find("User-Agent") == headers.end())
|
|
|
|
{
|
2019-08-30 21:48:18 +02:00
|
|
|
ss << "User-Agent: " << userAgent() << "\r\n";
|
2019-06-03 23:02:54 +02:00
|
|
|
}
|
|
|
|
|
2019-06-04 06:12:39 +02:00
|
|
|
if (verb == kPost || verb == kPut)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
ss << "Content-Length: " << body.size() << "\r\n";
|
|
|
|
|
|
|
|
// Set default Content-Type if unspecified
|
2019-06-06 02:04:24 +02:00
|
|
|
if (args->extraHeaders.find("Content-Type") == args->extraHeaders.end())
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
ss << "Content-Type: application/x-www-form-urlencoded"
|
|
|
|
<< "\r\n";
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
ss << "\r\n";
|
|
|
|
ss << body;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ss << "\r\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string req(ss.str());
|
|
|
|
std::string errMsg;
|
|
|
|
std::atomic<bool> requestInitCancellation(false);
|
|
|
|
|
|
|
|
// Make a cancellation object dealing with connection timeout
|
|
|
|
auto isCancellationRequested =
|
2019-06-06 02:04:24 +02:00
|
|
|
makeCancellationRequestWithTimeout(args->connectTimeout, requestInitCancellation);
|
2019-03-01 06:54:03 +01:00
|
|
|
|
|
|
|
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
|
|
|
if (!success)
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
2019-06-02 02:41:48 +02:00
|
|
|
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::CannotConnect,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
ss.str(),
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make a new cancellation object dealing with transfer timeout
|
|
|
|
isCancellationRequested =
|
2019-06-06 02:04:24 +02:00
|
|
|
makeCancellationRequestWithTimeout(args->transferTimeout, requestInitCancellation);
|
2019-03-01 06:54:03 +01:00
|
|
|
|
2019-06-06 02:04:24 +02:00
|
|
|
if (args->verbose)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "Sending " << verb << " request "
|
|
|
|
<< "to " << host << ":" << port << std::endl
|
|
|
|
<< "request size: " << req.size() << " bytes" << std::endl
|
|
|
|
<< "=============" << std::endl
|
2019-09-23 19:25:23 +02:00
|
|
|
<< req << "=============" << std::endl
|
2019-03-01 06:54:03 +01:00
|
|
|
<< std::endl;
|
|
|
|
|
|
|
|
log(ss.str(), args);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!_socket->writeBytes(req, isCancellationRequested))
|
|
|
|
{
|
|
|
|
std::string errorMsg("Cannot send request");
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::SendError,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
errorMsg,
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
uploadSize = req.size();
|
|
|
|
|
|
|
|
auto lineResult = _socket->readLine(isCancellationRequested);
|
|
|
|
auto lineValid = lineResult.first;
|
|
|
|
auto line = lineResult.second;
|
|
|
|
|
|
|
|
if (!lineValid)
|
|
|
|
{
|
|
|
|
std::string errorMsg("Cannot retrieve status line");
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::CannotReadStatusLine,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
errorMsg,
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
2019-06-06 02:04:24 +02:00
|
|
|
if (args->verbose)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "Status line " << line;
|
|
|
|
log(ss.str(), args);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
|
|
|
|
{
|
|
|
|
std::string errorMsg("Cannot parse response code from status line");
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::MissingStatus,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
errorMsg,
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
|
|
|
auto headersValid = result.first;
|
|
|
|
headers = result.second;
|
|
|
|
|
|
|
|
if (!headersValid)
|
|
|
|
{
|
|
|
|
std::string errorMsg("Cannot parse http headers");
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::HeaderParsingError,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
errorMsg,
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Redirect ?
|
2019-06-06 02:04:24 +02:00
|
|
|
if ((code >= 301 && code <= 308) && args->followRedirects)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
if (headers.find("Location") == headers.end())
|
|
|
|
{
|
|
|
|
std::string errorMsg("Missing location header for redirect");
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::MissingLocation,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
errorMsg,
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
2019-06-06 02:04:24 +02:00
|
|
|
if (redirects >= args->maxRedirects)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "Too many redirects: " << redirects;
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::TooManyRedirects,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
ss.str(),
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Recurse
|
|
|
|
std::string location = headers["Location"];
|
2019-09-23 19:25:23 +02:00
|
|
|
return request(location, verb, body, args, redirects + 1);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (verb == "HEAD")
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::Ok,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
std::string(),
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse response:
|
|
|
|
if (headers.find("Content-Length") != headers.end())
|
|
|
|
{
|
|
|
|
ssize_t contentLength = -1;
|
|
|
|
ss.str("");
|
|
|
|
ss << headers["Content-Length"];
|
|
|
|
ss >> contentLength;
|
|
|
|
|
|
|
|
payload.reserve(contentLength);
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
auto chunkResult = _socket->readBytes(
|
|
|
|
contentLength, args->onProgressCallback, isCancellationRequested);
|
2019-03-02 20:01:51 +01:00
|
|
|
if (!chunkResult.first)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
2019-03-02 20:01:51 +01:00
|
|
|
errorMsg = "Cannot read chunk";
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::ChunkReadError,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
errorMsg,
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
2019-03-02 20:01:51 +01:00
|
|
|
payload += chunkResult.second;
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
else if (headers.find("Transfer-Encoding") != headers.end() &&
|
|
|
|
headers["Transfer-Encoding"] == "chunked")
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
lineResult = _socket->readLine(isCancellationRequested);
|
|
|
|
line = lineResult.second;
|
|
|
|
|
|
|
|
if (!lineResult.first)
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::ChunkReadError,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
errorMsg,
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t chunkSize;
|
|
|
|
ss.str("");
|
|
|
|
ss << std::hex << line;
|
|
|
|
ss >> chunkSize;
|
|
|
|
|
2019-06-06 02:04:24 +02:00
|
|
|
if (args->verbose)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
std::stringstream oss;
|
2019-09-23 19:25:23 +02:00
|
|
|
oss << "Reading " << chunkSize << " bytes" << std::endl;
|
2019-03-01 06:54:03 +01:00
|
|
|
log(oss.str(), args);
|
|
|
|
}
|
|
|
|
|
2019-06-03 05:46:20 +02:00
|
|
|
payload.reserve(payload.size() + (size_t) chunkSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
|
2019-03-02 20:01:51 +01:00
|
|
|
// Read a chunk
|
2019-09-23 19:25:23 +02:00
|
|
|
auto chunkResult = _socket->readBytes(
|
|
|
|
(size_t) chunkSize, args->onProgressCallback, isCancellationRequested);
|
2019-03-02 20:01:51 +01:00
|
|
|
if (!chunkResult.first)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
2019-03-02 20:01:51 +01:00
|
|
|
errorMsg = "Cannot read chunk";
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::ChunkReadError,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
errorMsg,
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
2019-03-02 20:01:51 +01:00
|
|
|
payload += chunkResult.second;
|
2019-03-01 06:54:03 +01:00
|
|
|
|
2019-03-02 20:01:51 +01:00
|
|
|
// Read the line that terminates the chunk (\r\n)
|
2019-03-01 06:54:03 +01:00
|
|
|
lineResult = _socket->readLine(isCancellationRequested);
|
|
|
|
|
|
|
|
if (!lineResult.first)
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::ChunkReadError,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
errorMsg,
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (chunkSize == 0) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (code == 204)
|
|
|
|
{
|
|
|
|
; // 204 is NoContent response code
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::string errorMsg("Cannot read http body");
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::CannotReadBody,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
errorMsg,
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
downloadSize = payload.size();
|
|
|
|
|
|
|
|
// If the content was compressed with gzip, decode it
|
|
|
|
if (headers["Content-Encoding"] == "gzip")
|
|
|
|
{
|
|
|
|
std::string decompressedPayload;
|
|
|
|
if (!gzipInflate(payload, decompressedPayload))
|
|
|
|
{
|
|
|
|
std::string errorMsg("Error decompressing payload");
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::Gzip,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
errorMsg,
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
payload = decompressedPayload;
|
|
|
|
}
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
return std::make_shared<HttpResponse>(code,
|
|
|
|
description,
|
|
|
|
HttpErrorCode::Ok,
|
|
|
|
headers,
|
|
|
|
payload,
|
|
|
|
std::string(),
|
|
|
|
uploadSize,
|
|
|
|
downloadSize);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
HttpResponsePtr HttpClient::get(const std::string& url, HttpRequestArgsPtr args)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
return request(url, kGet, std::string(), args);
|
|
|
|
}
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
HttpResponsePtr HttpClient::head(const std::string& url, HttpRequestArgsPtr args)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
return request(url, kHead, std::string(), args);
|
|
|
|
}
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
HttpResponsePtr HttpClient::del(const std::string& url, HttpRequestArgsPtr args)
|
2019-06-03 20:38:56 +02:00
|
|
|
{
|
|
|
|
return request(url, kDel, std::string(), args);
|
|
|
|
}
|
|
|
|
|
2019-06-06 02:04:24 +02:00
|
|
|
HttpResponsePtr HttpClient::post(const std::string& url,
|
|
|
|
const HttpParameters& httpParameters,
|
|
|
|
HttpRequestArgsPtr args)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
return request(url, kPost, serializeHttpParameters(httpParameters), args);
|
|
|
|
}
|
|
|
|
|
2019-06-06 02:04:24 +02:00
|
|
|
HttpResponsePtr HttpClient::post(const std::string& url,
|
|
|
|
const std::string& body,
|
|
|
|
HttpRequestArgsPtr args)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
return request(url, kPost, body, args);
|
|
|
|
}
|
|
|
|
|
2019-06-06 02:04:24 +02:00
|
|
|
HttpResponsePtr HttpClient::put(const std::string& url,
|
|
|
|
const HttpParameters& httpParameters,
|
|
|
|
HttpRequestArgsPtr args)
|
2019-06-03 20:38:56 +02:00
|
|
|
{
|
|
|
|
return request(url, kPut, serializeHttpParameters(httpParameters), args);
|
|
|
|
}
|
|
|
|
|
2019-06-06 02:04:24 +02:00
|
|
|
HttpResponsePtr HttpClient::put(const std::string& url,
|
|
|
|
const std::string& body,
|
|
|
|
const HttpRequestArgsPtr args)
|
2019-06-03 20:38:56 +02:00
|
|
|
{
|
|
|
|
return request(url, kPut, body, args);
|
|
|
|
}
|
|
|
|
|
2019-03-01 06:54:03 +01:00
|
|
|
std::string HttpClient::urlEncode(const std::string& value)
|
|
|
|
{
|
|
|
|
std::ostringstream escaped;
|
|
|
|
escaped.fill('0');
|
|
|
|
escaped << std::hex;
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
std::string::value_type c = (*i);
|
|
|
|
|
|
|
|
// Keep alphanumeric and other accepted characters intact
|
|
|
|
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
|
|
|
|
{
|
|
|
|
escaped << c;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Any other characters are percent-encoded
|
|
|
|
escaped << std::uppercase;
|
|
|
|
escaped << '%' << std::setw(2) << int((unsigned char) c);
|
|
|
|
escaped << std::nouppercase;
|
|
|
|
}
|
|
|
|
|
|
|
|
return escaped.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string HttpClient::serializeHttpParameters(const HttpParameters& httpParameters)
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
size_t count = httpParameters.size();
|
|
|
|
size_t i = 0;
|
|
|
|
|
|
|
|
for (auto&& it : httpParameters)
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
ss << urlEncode(it.first) << "=" << urlEncode(it.second);
|
2019-03-01 06:54:03 +01:00
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
if (i++ < (count - 1))
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
ss << "&";
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
bool HttpClient::gzipInflate(const std::string& in, std::string& out)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
z_stream inflateState;
|
|
|
|
std::memset(&inflateState, 0, sizeof(inflateState));
|
|
|
|
|
|
|
|
inflateState.zalloc = Z_NULL;
|
|
|
|
inflateState.zfree = Z_NULL;
|
|
|
|
inflateState.opaque = Z_NULL;
|
|
|
|
inflateState.avail_in = 0;
|
|
|
|
inflateState.next_in = Z_NULL;
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
if (inflateInit2(&inflateState, 16 + MAX_WBITS) != Z_OK)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
inflateState.avail_in = (uInt) in.size();
|
2019-09-23 19:25:23 +02:00
|
|
|
inflateState.next_in = (unsigned char*) (const_cast<char*>(in.data()));
|
2019-03-01 06:54:03 +01:00
|
|
|
|
|
|
|
const int kBufferSize = 1 << 14;
|
|
|
|
|
|
|
|
std::unique_ptr<unsigned char[]> compressBuffer =
|
|
|
|
std::make_unique<unsigned char[]>(kBufferSize);
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
inflateState.avail_out = (uInt) kBufferSize;
|
|
|
|
inflateState.next_out = compressBuffer.get();
|
|
|
|
|
|
|
|
int ret = inflate(&inflateState, Z_SYNC_FLUSH);
|
|
|
|
|
|
|
|
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
|
|
|
|
{
|
|
|
|
inflateEnd(&inflateState);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
out.append(reinterpret_cast<char*>(compressBuffer.get()),
|
|
|
|
kBufferSize - inflateState.avail_out);
|
2019-03-01 06:54:03 +01:00
|
|
|
} while (inflateState.avail_out == 0);
|
|
|
|
|
|
|
|
inflateEnd(&inflateState);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
2019-06-06 02:04:24 +02:00
|
|
|
if (args->logger)
|
2019-03-01 06:54:03 +01:00
|
|
|
{
|
2019-06-06 02:04:24 +02:00
|
|
|
args->logger(msg);
|
2019-03-01 06:54:03 +01:00
|
|
|
}
|
|
|
|
}
|
2019-09-23 19:25:23 +02:00
|
|
|
} // namespace ix
|