cleanup
This commit is contained in:
parent
6d56f7223a
commit
285c12775a
@ -9,15 +9,19 @@
|
|||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
#include "IXSocketFactory.h"
|
#include "IXSocketFactory.h"
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
|
const std::string HttpClient::kPost = "POST";
|
||||||
|
const std::string HttpClient::kGet = "GET";
|
||||||
|
const std::string HttpClient::kHead = "HEAD";
|
||||||
|
|
||||||
HttpClient::HttpClient()
|
HttpClient::HttpClient()
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -29,32 +33,29 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse HttpClient::request(
|
HttpResponse HttpClient::request(
|
||||||
|
const std::string& url,
|
||||||
const std::string& verb,
|
const std::string& verb,
|
||||||
const std::string& body,
|
const std::string& body,
|
||||||
HttpRequestArgs args)
|
const HttpRequestArgs& args,
|
||||||
|
int redirects)
|
||||||
{
|
{
|
||||||
|
uint64_t uploadSize = 0;
|
||||||
|
uint64_t downloadSize = 0;
|
||||||
int code = 0;
|
int code = 0;
|
||||||
WebSocketHttpHeaders headers;
|
WebSocketHttpHeaders headers;
|
||||||
std::string payload;
|
std::string payload;
|
||||||
|
|
||||||
std::string protocol, host, path, query;
|
std::string protocol, host, path, query;
|
||||||
int port;
|
int port;
|
||||||
|
bool websocket = false;
|
||||||
|
|
||||||
if (!parseUrl(args.url, protocol, host, path, query, port))
|
if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot parse url: " << args.url;
|
ss << "Cannot parse url: " << url;
|
||||||
return std::make_tuple(code, headers, payload, ss.str());
|
return std::make_tuple(code, HttpErrorCode_UrlMalformed,
|
||||||
}
|
headers, payload, ss.str(),
|
||||||
|
uploadSize, downloadSize);
|
||||||
if (protocol != "http" && protocol != "https")
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Invalid protocol: " << protocol
|
|
||||||
<< " for url " << args.url
|
|
||||||
<< " . Supported protocols are http and https";
|
|
||||||
|
|
||||||
return std::make_tuple(code, headers, payload, ss.str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool tls = protocol == "https";
|
bool tls = protocol == "https";
|
||||||
@ -63,7 +64,9 @@ namespace ix
|
|||||||
|
|
||||||
if (!_socket)
|
if (!_socket)
|
||||||
{
|
{
|
||||||
return std::make_tuple(code, headers, payload, errorMsg);
|
return std::make_tuple(code, HttpErrorCode_CannotCreateSocket,
|
||||||
|
headers, payload, errorMsg,
|
||||||
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build request string
|
// Build request string
|
||||||
@ -84,7 +87,7 @@ namespace ix
|
|||||||
ss << it.first << ": " << it.second << "\r\n";
|
ss << it.first << ": " << it.second << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verb == "POST")
|
if (verb == kPost)
|
||||||
{
|
{
|
||||||
ss << "Content-Length: " << body.size() << "\r\n";
|
ss << "Content-Length: " << body.size() << "\r\n";
|
||||||
|
|
||||||
@ -102,37 +105,51 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string req(ss.str());
|
std::string req(ss.str());
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
std::atomic<bool> requestInitCancellation(false);
|
std::atomic<bool> requestInitCancellation(false);
|
||||||
|
|
||||||
|
// Make a cancellation object dealing with connection timeout
|
||||||
auto isCancellationRequested =
|
auto isCancellationRequested =
|
||||||
makeCancellationRequestWithTimeout(args.timeoutSecs, requestInitCancellation);
|
makeCancellationRequestWithTimeout(args.connectTimeout, requestInitCancellation);
|
||||||
|
|
||||||
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot connect to url: " << args.url;
|
ss << "Cannot connect to url: " << url;
|
||||||
return std::make_tuple(code, headers, payload, ss.str());
|
return std::make_tuple(code, HttpErrorCode_CannotConnect,
|
||||||
|
headers, payload, ss.str(),
|
||||||
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make a new cancellation object dealing with transfer timeout
|
||||||
|
isCancellationRequested =
|
||||||
|
makeCancellationRequestWithTimeout(args.transferTimeout, requestInitCancellation);
|
||||||
|
|
||||||
if (args.verbose)
|
if (args.verbose)
|
||||||
{
|
{
|
||||||
std::cerr << "Sending " << verb << " request "
|
std::stringstream ss;
|
||||||
|
ss << "Sending " << verb << " request "
|
||||||
<< "to " << host << ":" << port << std::endl
|
<< "to " << host << ":" << port << std::endl
|
||||||
<< "request size: " << req.size() << " bytes" << std::endl
|
<< "request size: " << req.size() << " bytes" << std::endl
|
||||||
<< "=============" << std::endl
|
<< "=============" << std::endl
|
||||||
<< req
|
<< req
|
||||||
<< "=============" << std::endl
|
<< "=============" << std::endl
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
|
||||||
|
log(ss.str(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_socket->writeBytes(req, isCancellationRequested))
|
if (!_socket->writeBytes(req, isCancellationRequested))
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot send request");
|
std::string errorMsg("Cannot send request");
|
||||||
return std::make_tuple(code, headers, payload, errorMsg);
|
return std::make_tuple(code, HttpErrorCode_SendError,
|
||||||
|
headers, payload, errorMsg,
|
||||||
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadSize = req.size();
|
||||||
|
|
||||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
auto lineResult = _socket->readLine(isCancellationRequested);
|
||||||
auto lineValid = lineResult.first;
|
auto lineValid = lineResult.first;
|
||||||
auto line = lineResult.second;
|
auto line = lineResult.second;
|
||||||
@ -140,13 +157,24 @@ namespace ix
|
|||||||
if (!lineValid)
|
if (!lineValid)
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot retrieve status line");
|
std::string errorMsg("Cannot retrieve status line");
|
||||||
return std::make_tuple(code, headers, payload, errorMsg);
|
return std::make_tuple(code, HttpErrorCode_CannotReadStatusLine,
|
||||||
|
headers, payload, errorMsg,
|
||||||
|
uploadSize, downloadSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.verbose)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Status line " << line;
|
||||||
|
log(ss.str(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
|
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot parse response code from status line");
|
std::string errorMsg("Cannot parse response code from status line");
|
||||||
return std::make_tuple(code, headers, payload, errorMsg);
|
return std::make_tuple(code, HttpErrorCode_MissingStatus,
|
||||||
|
headers, payload, errorMsg,
|
||||||
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
||||||
@ -155,39 +183,50 @@ namespace ix
|
|||||||
|
|
||||||
if (!headersValid)
|
if (!headersValid)
|
||||||
{
|
{
|
||||||
code = 0; // 0 ?
|
|
||||||
std::string errorMsg("Cannot parse http headers");
|
std::string errorMsg("Cannot parse http headers");
|
||||||
return std::make_tuple(code, headers, payload, errorMsg);
|
return std::make_tuple(code, HttpErrorCode_HeaderParsingError,
|
||||||
|
headers, payload, errorMsg,
|
||||||
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect ?
|
// Redirect ?
|
||||||
// FIXME wrong conditional
|
if ((code >= 301 && code <= 308) && args.followRedirects)
|
||||||
if ((code == 301 || code == 308) && args.followRedirects)
|
|
||||||
{
|
{
|
||||||
if (headers.find("location") == headers.end())
|
if (headers.find("Location") == headers.end())
|
||||||
{
|
{
|
||||||
code = 0; // 0 ?
|
|
||||||
std::string errorMsg("Missing location header for redirect");
|
std::string errorMsg("Missing location header for redirect");
|
||||||
return std::make_tuple(code, headers, payload, errorMsg);
|
return std::make_tuple(code, HttpErrorCode_MissingLocation,
|
||||||
|
headers, payload, errorMsg,
|
||||||
|
uploadSize, downloadSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redirects >= args.maxRedirects)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Too many redirects: " << redirects;
|
||||||
|
return std::make_tuple(code, HttpErrorCode_TooManyRedirects,
|
||||||
|
headers, payload, ss.str(),
|
||||||
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurse
|
// Recurse
|
||||||
std::string location = headers["location"];
|
std::string location = headers["Location"];
|
||||||
return request(verb, body, args);
|
return request(location, verb, body, args, redirects+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verb == "HEAD")
|
if (verb == "HEAD")
|
||||||
{
|
{
|
||||||
return std::make_tuple(code, headers, payload, std::string());
|
return std::make_tuple(code, HttpErrorCode_Ok,
|
||||||
|
headers, payload, std::string(),
|
||||||
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse response:
|
// Parse response:
|
||||||
// http://bryce-thomas.blogspot.com/2012/01/technical-parsing-http-to-extract.html
|
if (headers.find("Content-Length") != headers.end())
|
||||||
if (headers.find("content-length") != headers.end())
|
|
||||||
{
|
{
|
||||||
ssize_t contentLength = -1;
|
ssize_t contentLength = -1;
|
||||||
ss.str("");
|
ss.str("");
|
||||||
ss << headers["content-length"];
|
ss << headers["Content-Length"];
|
||||||
ss >> contentLength;
|
ss >> contentLength;
|
||||||
|
|
||||||
payload.reserve(contentLength);
|
payload.reserve(contentLength);
|
||||||
@ -198,18 +237,16 @@ namespace ix
|
|||||||
char c;
|
char c;
|
||||||
if (!_socket->readByte(&c, isCancellationRequested))
|
if (!_socket->readByte(&c, isCancellationRequested))
|
||||||
{
|
{
|
||||||
ss.str("");
|
return std::make_tuple(code, HttpErrorCode_ReadError,
|
||||||
ss << "Cannot read byte";
|
headers, payload, "Cannot read byte",
|
||||||
return std::make_tuple(-1, headers, payload, "Cannot read byte");
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
payload += c;
|
payload += c;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "I WAS HERE" << std::endl;
|
|
||||||
}
|
}
|
||||||
else if (headers.find("transfer-encoding") != headers.end() &&
|
else if (headers.find("Transfer-Encoding") != headers.end() &&
|
||||||
headers["transfer-encoding"] == "chunked")
|
headers["Transfer-Encoding"] == "chunked")
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
|
|
||||||
@ -220,9 +257,9 @@ namespace ix
|
|||||||
|
|
||||||
if (!lineResult.first)
|
if (!lineResult.first)
|
||||||
{
|
{
|
||||||
code = 0; // 0 ?
|
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
||||||
std::string errorMsg("Cannot read http body");
|
headers, payload, errorMsg,
|
||||||
return std::make_tuple(code, headers, payload, errorMsg);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t chunkSize;
|
uint64_t chunkSize;
|
||||||
@ -232,8 +269,10 @@ namespace ix
|
|||||||
|
|
||||||
if (args.verbose)
|
if (args.verbose)
|
||||||
{
|
{
|
||||||
std::cerr << "Reading " << chunkSize << " bytes"
|
std::stringstream oss;
|
||||||
|
oss << "Reading " << chunkSize << " bytes"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
log(oss.str(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.reserve(payload.size() + chunkSize);
|
payload.reserve(payload.size() + chunkSize);
|
||||||
@ -245,9 +284,10 @@ namespace ix
|
|||||||
char c;
|
char c;
|
||||||
if (!_socket->readByte(&c, isCancellationRequested))
|
if (!_socket->readByte(&c, isCancellationRequested))
|
||||||
{
|
{
|
||||||
ss.str("");
|
errorMsg = "Cannot read byte";
|
||||||
ss << "Cannot read byte";
|
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
||||||
return std::make_tuple(-1, headers, payload, ss.str());
|
headers, payload, errorMsg,
|
||||||
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
payload += c;
|
payload += c;
|
||||||
@ -257,9 +297,9 @@ namespace ix
|
|||||||
|
|
||||||
if (!lineResult.first)
|
if (!lineResult.first)
|
||||||
{
|
{
|
||||||
code = 0; // 0 ?
|
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
||||||
std::string errorMsg("Cannot read http body");
|
headers, payload, errorMsg,
|
||||||
return std::make_tuple(code, headers, payload, errorMsg);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chunkSize == 0) break;
|
if (chunkSize == 0) break;
|
||||||
@ -271,48 +311,57 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
code = 0; // 0 ?
|
|
||||||
std::string errorMsg("Cannot read http body");
|
std::string errorMsg("Cannot read http body");
|
||||||
return std::make_tuple(-1, headers, payload, errorMsg);
|
return std::make_tuple(code, HttpErrorCode_CannotReadBody,
|
||||||
|
headers, payload, errorMsg,
|
||||||
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadSize = payload.size();
|
||||||
|
|
||||||
// If the content was compressed with gzip, decode it
|
// If the content was compressed with gzip, decode it
|
||||||
if (headers["Content-Encoding"] == "gzip")
|
if (headers["Content-Encoding"] == "gzip")
|
||||||
{
|
{
|
||||||
if (args.verbose) std::cout << "Decoding gzip..." << std::endl;
|
|
||||||
|
|
||||||
std::string decompressedPayload;
|
std::string decompressedPayload;
|
||||||
if (!gzipInflate(payload, decompressedPayload))
|
if (!gzipInflate(payload, decompressedPayload))
|
||||||
{
|
{
|
||||||
std::string errorMsg("Error decompressing payload");
|
std::string errorMsg("Error decompressing payload");
|
||||||
return std::make_tuple(-1, headers, payload, errorMsg);
|
return std::make_tuple(code, HttpErrorCode_Gzip,
|
||||||
|
headers, payload, errorMsg,
|
||||||
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
payload = decompressedPayload;
|
payload = decompressedPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_tuple(code, headers, payload, "");
|
return std::make_tuple(code, HttpErrorCode_Ok,
|
||||||
|
headers, payload, std::string(),
|
||||||
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse HttpClient::get(HttpRequestArgs args)
|
HttpResponse HttpClient::get(const std::string& url,
|
||||||
|
const HttpRequestArgs& args)
|
||||||
{
|
{
|
||||||
return request("GET", std::string(), args);
|
return request(url, kGet, std::string(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse HttpClient::head(HttpRequestArgs args)
|
HttpResponse HttpClient::head(const std::string& url,
|
||||||
|
const HttpRequestArgs& args)
|
||||||
{
|
{
|
||||||
return request("HEAD", std::string(), args);
|
return request(url, kHead, std::string(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse HttpClient::post(const HttpParameters& httpParameters,
|
HttpResponse HttpClient::post(const std::string& url,
|
||||||
HttpRequestArgs args)
|
const HttpParameters& httpParameters,
|
||||||
|
const HttpRequestArgs& args)
|
||||||
{
|
{
|
||||||
return request("POST", serializeHttpParameters(httpParameters), args);
|
return request(url, kPost, serializeHttpParameters(httpParameters), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse HttpClient::post(const std::string& body,
|
HttpResponse HttpClient::post(const std::string& url,
|
||||||
HttpRequestArgs args)
|
const std::string& body,
|
||||||
|
const HttpRequestArgs& args)
|
||||||
{
|
{
|
||||||
return request("POST", body, args);
|
return request(url, kPost, body, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string HttpClient::urlEncode(const std::string& value)
|
std::string HttpClient::urlEncode(const std::string& value)
|
||||||
@ -367,7 +416,7 @@ namespace ix
|
|||||||
std::string& out)
|
std::string& out)
|
||||||
{
|
{
|
||||||
z_stream inflateState;
|
z_stream inflateState;
|
||||||
memset(&inflateState, 0, sizeof(inflateState));
|
std::memset(&inflateState, 0, sizeof(inflateState));
|
||||||
|
|
||||||
inflateState.zalloc = Z_NULL;
|
inflateState.zalloc = Z_NULL;
|
||||||
inflateState.zfree = Z_NULL;
|
inflateState.zfree = Z_NULL;
|
||||||
@ -410,4 +459,13 @@ namespace ix
|
|||||||
inflateEnd(&inflateState);
|
inflateEnd(&inflateState);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HttpClient::log(const std::string& msg,
|
||||||
|
const HttpRequestArgs& args)
|
||||||
|
{
|
||||||
|
if (args.logger)
|
||||||
|
{
|
||||||
|
args.logger(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,18 +19,48 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
using HttpResponse = std::tuple<int, WebSocketHttpHeaders, std::string, std::string>;
|
enum HttpErrorCode
|
||||||
|
{
|
||||||
|
HttpErrorCode_Ok = 0,
|
||||||
|
HttpErrorCode_CannotConnect = 1,
|
||||||
|
HttpErrorCode_Timeout = 2,
|
||||||
|
HttpErrorCode_Gzip = 3,
|
||||||
|
HttpErrorCode_UrlMalformed = 4,
|
||||||
|
HttpErrorCode_CannotCreateSocket = 5,
|
||||||
|
HttpErrorCode_SendError = 6,
|
||||||
|
HttpErrorCode_ReadError = 7,
|
||||||
|
HttpErrorCode_CannotReadStatusLine = 8,
|
||||||
|
HttpErrorCode_MissingStatus = 9,
|
||||||
|
HttpErrorCode_HeaderParsingError = 10,
|
||||||
|
HttpErrorCode_MissingLocation = 11,
|
||||||
|
HttpErrorCode_TooManyRedirects = 12,
|
||||||
|
HttpErrorCode_ChunkReadError = 13,
|
||||||
|
HttpErrorCode_CannotReadBody = 14
|
||||||
|
};
|
||||||
|
|
||||||
|
using HttpResponse = std::tuple<int, // status
|
||||||
|
HttpErrorCode, // error code
|
||||||
|
WebSocketHttpHeaders,
|
||||||
|
std::string, // payload
|
||||||
|
std::string, // error msg
|
||||||
|
uint64_t, // upload size
|
||||||
|
uint64_t>; // download size
|
||||||
|
|
||||||
using HttpParameters = std::map<std::string, std::string>;
|
using HttpParameters = std::map<std::string, std::string>;
|
||||||
|
using Logger = std::function<void(const std::string&)>;
|
||||||
|
|
||||||
struct HttpRequestArgs
|
struct HttpRequestArgs
|
||||||
{
|
{
|
||||||
std::string url;
|
std::string url;
|
||||||
WebSocketHttpHeaders extraHeaders;
|
WebSocketHttpHeaders extraHeaders;
|
||||||
std::string body;
|
std::string body;
|
||||||
int timeoutSecs;
|
int connectTimeout;
|
||||||
|
int transferTimeout;
|
||||||
bool followRedirects;
|
bool followRedirects;
|
||||||
|
int maxRedirects;
|
||||||
bool verbose;
|
bool verbose;
|
||||||
bool compress;
|
bool compress;
|
||||||
|
Logger logger;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HttpClient {
|
class HttpClient {
|
||||||
@ -38,27 +68,39 @@ namespace ix
|
|||||||
HttpClient();
|
HttpClient();
|
||||||
~HttpClient();
|
~HttpClient();
|
||||||
|
|
||||||
HttpResponse get(HttpRequestArgs args);
|
HttpResponse get(const std::string& url,
|
||||||
HttpResponse head(HttpRequestArgs args);
|
const HttpRequestArgs& args);
|
||||||
|
HttpResponse head(const std::string& url,
|
||||||
|
const HttpRequestArgs& args);
|
||||||
|
|
||||||
HttpResponse post(const HttpParameters& httpParameters,
|
HttpResponse post(const std::string& url,
|
||||||
HttpRequestArgs args);
|
const HttpParameters& httpParameters,
|
||||||
HttpResponse post(const std::string& body,
|
const HttpRequestArgs& args);
|
||||||
HttpRequestArgs args);
|
HttpResponse post(const std::string& url,
|
||||||
|
const std::string& body,
|
||||||
|
const HttpRequestArgs& args);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HttpResponse request(const std::string& verb,
|
HttpResponse request(const std::string& url,
|
||||||
|
const std::string& verb,
|
||||||
const std::string& body,
|
const std::string& body,
|
||||||
HttpRequestArgs args);
|
const HttpRequestArgs& args,
|
||||||
|
int redirects = 0);
|
||||||
|
|
||||||
std::string serializeHttpParameters(const HttpParameters& httpParameters);
|
std::string serializeHttpParameters(const HttpParameters& httpParameters);
|
||||||
|
|
||||||
std::string urlEncode(const std::string& value);
|
std::string urlEncode(const std::string& value);
|
||||||
|
|
||||||
|
void log(const std::string& msg, const HttpRequestArgs& args);
|
||||||
|
|
||||||
bool gzipInflate(
|
bool gzipInflate(
|
||||||
const std::string& in,
|
const std::string& in,
|
||||||
std::string& out);
|
std::string& out);
|
||||||
|
|
||||||
std::shared_ptr<Socket> _socket;
|
std::shared_ptr<Socket> _socket;
|
||||||
|
|
||||||
|
const static std::string kPost;
|
||||||
|
const static std::string kGet;
|
||||||
|
const static std::string kHead;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -6,23 +6,29 @@
|
|||||||
|
|
||||||
#include "IXUrlParser.h"
|
#include "IXUrlParser.h"
|
||||||
|
|
||||||
#include <regex>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
bool parseUrl(const std::string& url,
|
//
|
||||||
|
// The only difference between those 2 regex is the protocol
|
||||||
|
//
|
||||||
|
std::regex UrlParser::_httpRegex("(http|https)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
|
||||||
|
std::regex UrlParser::_webSocketRegex("(ws|wss)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
|
||||||
|
|
||||||
|
bool UrlParser::parse(const std::string& url,
|
||||||
std::string& protocol,
|
std::string& protocol,
|
||||||
std::string& host,
|
std::string& host,
|
||||||
std::string& path,
|
std::string& path,
|
||||||
std::string& query,
|
std::string& query,
|
||||||
int& port)
|
int& port,
|
||||||
|
bool websocket)
|
||||||
{
|
{
|
||||||
std::regex ex("(ws|wss|http|https)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
|
|
||||||
std::cmatch what;
|
std::cmatch what;
|
||||||
if (!regex_match(url.c_str(), what, ex))
|
if (!regex_match(url.c_str(), what,
|
||||||
|
websocket ? _webSocketRegex : _httpRegex))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -77,12 +83,12 @@ namespace ix
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void printUrl(const std::string& url)
|
void UrlParser::printUrl(const std::string& url, bool websocket)
|
||||||
{
|
{
|
||||||
std::string protocol, host, path, query;
|
std::string protocol, host, path, query;
|
||||||
int port {0};
|
int port {0};
|
||||||
|
|
||||||
if (!parseUrl(url, protocol, host, path, query, port))
|
if (!parse(url, protocol, host, path, query, port, websocket))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -7,15 +7,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
bool parseUrl(const std::string& url,
|
class UrlParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool parse(const std::string& url,
|
||||||
std::string& protocol,
|
std::string& protocol,
|
||||||
std::string& host,
|
std::string& host,
|
||||||
std::string& path,
|
std::string& path,
|
||||||
std::string& query,
|
std::string& query,
|
||||||
int& port);
|
int& port,
|
||||||
|
bool websocket);
|
||||||
|
|
||||||
void printUrl(const std::string& url);
|
static void printUrl(const std::string& url, bool websocket);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::regex _httpRegex;
|
||||||
|
static std::regex _webSocketRegex;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -72,8 +72,9 @@ namespace ix
|
|||||||
{
|
{
|
||||||
std::string protocol, host, path, query;
|
std::string protocol, host, path, query;
|
||||||
int port;
|
int port;
|
||||||
|
bool websocket = true;
|
||||||
|
|
||||||
if (!parseUrl(url, protocol, host, path, query, port))
|
if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
|
||||||
{
|
{
|
||||||
return WebSocketInitResult(false, 0,
|
return WebSocketInitResult(false, 0,
|
||||||
std::string("Could not parse URL ") + url);
|
std::string("Could not parse URL ") + url);
|
||||||
|
24
ws/ws.cpp
24
ws/ws.cpp
@ -33,7 +33,9 @@ int main(int argc, char** argv)
|
|||||||
bool save = false;
|
bool save = false;
|
||||||
bool compress = false;
|
bool compress = false;
|
||||||
int port = 8080;
|
int port = 8080;
|
||||||
int connectTimeOutSeconds = 3;
|
int connectTimeOut = 60;
|
||||||
|
int transferTimeout = 1800;
|
||||||
|
int maxRedirects = 5;
|
||||||
|
|
||||||
CLI::App* sendApp = app.add_subcommand("send", "Send a file");
|
CLI::App* sendApp = app.add_subcommand("send", "Send a file");
|
||||||
sendApp->add_option("url", url, "Connection url")->required();
|
sendApp->add_option("url", url, "Connection url")->required();
|
||||||
@ -68,12 +70,14 @@ int main(int argc, char** argv)
|
|||||||
httpClientApp->add_option("-F", data, "Form data")->join();
|
httpClientApp->add_option("-F", data, "Form data")->join();
|
||||||
httpClientApp->add_option("-H", headers, "Header")->join();
|
httpClientApp->add_option("-H", headers, "Header")->join();
|
||||||
httpClientApp->add_option("--output", output, "Output file");
|
httpClientApp->add_option("--output", output, "Output file");
|
||||||
httpClientApp->add_flag("-I", headersOnly, "Header");
|
httpClientApp->add_flag("-I", headersOnly, "Send a HEAD request");
|
||||||
httpClientApp->add_flag("-L", followRedirects, "Header");
|
httpClientApp->add_flag("-L", followRedirects, "Follow redirects");
|
||||||
|
httpClientApp->add_option("--max-redirects", maxRedirects, "Max Redirects");
|
||||||
httpClientApp->add_flag("-v", verbose, "Verbose");
|
httpClientApp->add_flag("-v", verbose, "Verbose");
|
||||||
httpClientApp->add_flag("-O", save, "Save to disk");
|
httpClientApp->add_flag("-O", save, "Save output to disk");
|
||||||
httpClientApp->add_flag("--compress", compress, "gzip compression");
|
httpClientApp->add_flag("--compress", compress, "Enable gzip compression");
|
||||||
httpClientApp->add_option("--connect-timeout", connectTimeOutSeconds, "Connection timeout");
|
httpClientApp->add_option("--connect-timeout", connectTimeOut, "Connection timeout");
|
||||||
|
httpClientApp->add_option("--transfer-timeout", transferTimeout, "Transfer timeout");
|
||||||
|
|
||||||
CLI11_PARSE(app, argc, argv);
|
CLI11_PARSE(app, argc, argv);
|
||||||
|
|
||||||
@ -114,10 +118,10 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
else if (app.got_subcommand("curl"))
|
else if (app.got_subcommand("curl"))
|
||||||
{
|
{
|
||||||
return ix::ws_http_client_main(url, headers, data,
|
return ix::ws_http_client_main(url, headers, data, headersOnly,
|
||||||
headersOnly, connectTimeOutSeconds,
|
connectTimeOut, transferTimeout,
|
||||||
followRedirects, verbose, save, output,
|
followRedirects, maxRedirects, verbose,
|
||||||
compress);
|
save, output, compress);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
4
ws/ws.h
4
ws/ws.h
@ -13,8 +13,10 @@ namespace ix
|
|||||||
const std::string& headers,
|
const std::string& headers,
|
||||||
const std::string& data,
|
const std::string& data,
|
||||||
bool headersOnly,
|
bool headersOnly,
|
||||||
int timeoutSecs,
|
int connectTimeout,
|
||||||
|
int transferTimeout,
|
||||||
bool followRedirects,
|
bool followRedirects,
|
||||||
|
int maxRedirects,
|
||||||
bool verbose,
|
bool verbose,
|
||||||
bool save,
|
bool save,
|
||||||
const std::string& output,
|
const std::string& output,
|
||||||
|
@ -86,20 +86,27 @@ namespace ix
|
|||||||
const std::string& headersData,
|
const std::string& headersData,
|
||||||
const std::string& data,
|
const std::string& data,
|
||||||
bool headersOnly,
|
bool headersOnly,
|
||||||
int timeoutSecs,
|
int connectTimeout,
|
||||||
|
int transferTimeout,
|
||||||
bool followRedirects,
|
bool followRedirects,
|
||||||
|
int maxRedirects,
|
||||||
bool verbose,
|
bool verbose,
|
||||||
bool save,
|
bool save,
|
||||||
const std::string& output,
|
const std::string& output,
|
||||||
bool compress)
|
bool compress)
|
||||||
{
|
{
|
||||||
HttpRequestArgs args;
|
HttpRequestArgs args;
|
||||||
args.url = url;
|
|
||||||
args.extraHeaders = parseHeaders(headersData);
|
args.extraHeaders = parseHeaders(headersData);
|
||||||
args.timeoutSecs = timeoutSecs;
|
args.connectTimeout = connectTimeout;
|
||||||
|
args.transferTimeout = transferTimeout;
|
||||||
args.followRedirects = followRedirects;
|
args.followRedirects = followRedirects;
|
||||||
|
args.maxRedirects = maxRedirects;
|
||||||
args.verbose = verbose;
|
args.verbose = verbose;
|
||||||
args.compress = compress;
|
args.compress = compress;
|
||||||
|
args.logger = [](const std::string& msg)
|
||||||
|
{
|
||||||
|
std::cout << msg;
|
||||||
|
};
|
||||||
|
|
||||||
HttpParameters httpParameters = parsePostParameters(data);
|
HttpParameters httpParameters = parsePostParameters(data);
|
||||||
|
|
||||||
@ -107,34 +114,40 @@ namespace ix
|
|||||||
HttpResponse out;
|
HttpResponse out;
|
||||||
if (headersOnly)
|
if (headersOnly)
|
||||||
{
|
{
|
||||||
out = httpClient.head(args);
|
out = httpClient.head(url, args);
|
||||||
}
|
}
|
||||||
else if (data.empty())
|
else if (data.empty())
|
||||||
{
|
{
|
||||||
out = httpClient.get(args);
|
out = httpClient.get(url, args);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
out = httpClient.post(httpParameters, args);
|
out = httpClient.post(url, httpParameters, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto errorCode = std::get<0>(out);
|
auto statusCode = std::get<0>(out);
|
||||||
auto responseHeaders = std::get<1>(out);
|
auto errorCode = std::get<1>(out);
|
||||||
auto payload = std::get<2>(out);
|
auto responseHeaders = std::get<2>(out);
|
||||||
auto errorMsg = std::get<3>(out);
|
auto payload = std::get<3>(out);
|
||||||
|
auto errorMsg = std::get<4>(out);
|
||||||
|
auto uploadSize = std::get<5>(out);
|
||||||
|
auto downloadSize = std::get<6>(out);
|
||||||
|
|
||||||
for (auto it : responseHeaders)
|
for (auto it : responseHeaders)
|
||||||
{
|
{
|
||||||
std::cerr << it.first << ": " << it.second << std::endl;
|
std::cerr << it.first << ": " << it.second << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cerr << "error code: " << errorCode << std::endl;
|
std::cerr << "Upload size: " << uploadSize << std::endl;
|
||||||
if (errorCode != 200)
|
std::cerr << "Download size: " << downloadSize << std::endl;
|
||||||
|
|
||||||
|
std::cerr << "Status: " << statusCode << std::endl;
|
||||||
|
if (errorCode != HttpErrorCode_Ok)
|
||||||
{
|
{
|
||||||
std::cerr << "error message: " << errorMsg << std::endl;
|
std::cerr << "error message: " << errorMsg << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!headersOnly && errorCode == 200)
|
if (!headersOnly && errorCode == HttpErrorCode_Ok)
|
||||||
{
|
{
|
||||||
if (save || !output.empty())
|
if (save || !output.empty())
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user