cleanup argument parsing + add socket creation factory

This commit is contained in:
Benjamin Sergeant 2019-02-27 16:35:00 -08:00
parent 7c2bc2cf7e
commit 0a752e7d18
9 changed files with 289 additions and 194 deletions

View File

@ -20,6 +20,7 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXSocket.cpp ixwebsocket/IXSocket.cpp
ixwebsocket/IXSocketServer.cpp ixwebsocket/IXSocketServer.cpp
ixwebsocket/IXSocketConnect.cpp ixwebsocket/IXSocketConnect.cpp
ixwebsocket/IXSocketFactory.cpp
ixwebsocket/IXDNSLookup.cpp ixwebsocket/IXDNSLookup.cpp
ixwebsocket/IXCancellationRequest.cpp ixwebsocket/IXCancellationRequest.cpp
ixwebsocket/IXWebSocket.cpp ixwebsocket/IXWebSocket.cpp
@ -39,6 +40,7 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXSocket.h ixwebsocket/IXSocket.h
ixwebsocket/IXSocketServer.h ixwebsocket/IXSocketServer.h
ixwebsocket/IXSocketConnect.h ixwebsocket/IXSocketConnect.h
ixwebsocket/IXSocketFactory.h
ixwebsocket/IXSetThreadName.h ixwebsocket/IXSetThreadName.h
ixwebsocket/IXDNSLookup.h ixwebsocket/IXDNSLookup.h
ixwebsocket/IXCancellationRequest.h ixwebsocket/IXCancellationRequest.h

View File

@ -7,14 +7,7 @@
#include "IXHttpClient.h" #include "IXHttpClient.h"
#include "IXUrlParser.h" #include "IXUrlParser.h"
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include "IXSocketFactory.h"
#if defined(__APPLE__) or defined(__linux__)
# ifdef __APPLE__
# include <ixwebsocket/IXSocketAppleSSL.h>
# else
# include <ixwebsocket/IXSocketOpenSSL.h>
# endif
#endif
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
@ -34,12 +27,9 @@ namespace ix
} }
HttpResponse HttpClient::request( HttpResponse HttpClient::request(
const std::string& url,
const std::string& verb, const std::string& verb,
const WebSocketHttpHeaders& extraHeaders, const std::string& body,
const HttpParameters& httpParameters, HttpRequestArgs args)
bool followRedirects,
bool verbose)
{ {
int code = 0; int code = 0;
WebSocketHttpHeaders headers; WebSocketHttpHeaders headers;
@ -48,68 +38,54 @@ namespace ix
std::string protocol, host, path, query; std::string protocol, host, path, query;
int port; int port;
if (!parseUrl(url, protocol, host, path, query, port)) if (!parseUrl(args.url, protocol, host, path, query, port))
{
code = 0; // 0 ?
std::string errorMsg("Cannot parse url");
return std::make_tuple(code, headers, payload, errorMsg);
}
if (protocol == "http")
{
_socket = std::make_shared<Socket>();
}
else if (protocol == "https")
{
# ifdef __APPLE__
_socket = std::make_shared<SocketAppleSSL>();
# else
_socket = std::make_shared<SocketOpenSSL>();
# endif
}
else
{
code = 0; // 0 ?
std::string errorMsg("Bad protocol");
return std::make_tuple(code, headers, payload, errorMsg);
}
std::string body;
if (verb == "POST")
{ {
std::stringstream ss; std::stringstream ss;
size_t count = httpParameters.size(); ss << "Cannot parse url: " << args.url;
size_t i = 0; return std::make_tuple(code, headers, payload, ss.str());
}
for (auto&& it : httpParameters) if (protocol != "http" && protocol != "https")
{ {
ss << urlEncode(it.first) std::stringstream ss;
<< "=" ss << "Invalid protocol: " << protocol
<< urlEncode(it.second); << " for url " << args.url
<< " . Supported protocols are http and https";
if (i++ < (count-1)) return std::make_tuple(code, headers, payload, ss.str());
}
bool tls = protocol == "https";
std::string errorMsg;
_socket = createSocket(tls, errorMsg);
if (!_socket)
{ {
ss << "&"; return std::make_tuple(code, headers, payload, errorMsg);
}
}
body = ss.str();
} }
// Build request string
std::stringstream ss; std::stringstream ss;
ss << verb << " " << path << " HTTP/1.1\r\n"; ss << verb << " " << path << " HTTP/1.1\r\n";
ss << "Host: " << host << "\r\n"; ss << "Host: " << host << "\r\n";
ss << "User-Agent: ixwebsocket/1.0.0" << "\r\n"; ss << "User-Agent: ixwebsocket/1.0.0" << "\r\n";
ss << "Accept: */*" << "\r\n"; ss << "Accept: */*" << "\r\n";
for (auto&& it : extraHeaders) // Append extra headers
for (auto&& it : args.extraHeaders)
{ {
ss << it.first << ": " << urlEncode(it.second) << "\r\n"; ss << it.first << ": " << it.second << "\r\n";
} }
if (verb == "POST") if (verb == "POST")
{ {
ss << "Content-Length: " << body.size() << "\r\n"; ss << "Content-Length: " << body.size() << "\r\n";
// Set default Content-Type if unspecified
if (args.extraHeaders.find("Content-Type") == args.extraHeaders.end())
{
ss << "Content-Type: application/x-www-form-urlencoded" << "\r\n"; ss << "Content-Type: application/x-www-form-urlencoded" << "\r\n";
}
ss << "\r\n"; ss << "\r\n";
ss << body; ss << body;
} }
@ -120,26 +96,24 @@ namespace ix
std::string req(ss.str()); std::string req(ss.str());
int timeoutSecs = 10;
std::string errMsg; std::string errMsg;
static std::atomic<bool> requestInitCancellation(false); std::atomic<bool> requestInitCancellation(false);
auto isCancellationRequested = auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation); makeCancellationRequestWithTimeout(args.timeoutSecs, requestInitCancellation);
bool success = _socket->connect(host, port, errMsg, isCancellationRequested); bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
if (!success) if (!success)
{ {
code = 0; // 0 ? std::stringstream ss;
std::string errorMsg("Cannot connect to url"); ss << "Cannot connect to url: " << args.url;
return std::make_tuple(code, headers, payload, errorMsg); return std::make_tuple(code, headers, payload, ss.str());
} }
if (verbose) if (args.verbose)
{ {
std::cout << "Sending " << verb << " request " std::cerr << "Sending " << verb << " request "
<< "to " << host << ":" << port << std::endl << "to " << host << ":" << port << std::endl
<< "request size: " << req.size() << " bytes" << "request size: " << req.size() << " bytes" << std::endl
<< "=============" << std::endl << "=============" << std::endl
<< req << req
<< "=============" << std::endl << "=============" << std::endl
@ -148,7 +122,6 @@ namespace ix
if (!_socket->writeBytes(req, isCancellationRequested)) if (!_socket->writeBytes(req, isCancellationRequested))
{ {
code = 0; // 0 ?
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, headers, payload, errorMsg);
} }
@ -159,20 +132,12 @@ namespace ix
if (!lineValid) if (!lineValid)
{ {
code = 0; // 0 ?
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, headers, payload, errorMsg);
} }
if (verbose)
{
std::cout << "first line: " << line << std::endl;
}
code = -1;
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1) if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
{ {
code = 0; // 0 ?
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, headers, payload, errorMsg);
} }
@ -189,7 +154,8 @@ namespace ix
} }
// Redirect ? // Redirect ?
if (code == 301 && followRedirects) // FIXME wrong conditional
if ((code == 301 || code == 308) && args.followRedirects)
{ {
if (headers.find("location") == headers.end()) if (headers.find("location") == headers.end())
{ {
@ -198,8 +164,9 @@ namespace ix
return std::make_tuple(code, headers, payload, errorMsg); return std::make_tuple(code, headers, payload, errorMsg);
} }
// Recurse
std::string location = headers["location"]; std::string location = headers["location"];
return request(location, verb, extraHeaders, httpParameters, followRedirects, verbose); return request(verb, body, args);
} }
if (verb == "HEAD") if (verb == "HEAD")
@ -254,6 +221,12 @@ namespace ix
ss << std::hex << line; ss << std::hex << line;
ss >> chunkSize; ss >> chunkSize;
if (args.verbose)
{
std::cerr << "Reading " << chunkSize << " bytes"
<< std::endl;
}
payload.reserve(payload.size() + chunkSize); payload.reserve(payload.size() + chunkSize);
// Read another line // Read another line
@ -283,6 +256,10 @@ namespace ix
if (chunkSize == 0) break; if (chunkSize == 0) break;
} }
} }
else if (code == 204)
{
; // 204 is NoContent response code
}
else else
{ {
code = 0; // 0 ? code = 0; // 0 ?
@ -293,38 +270,26 @@ namespace ix
return std::make_tuple(code, headers, payload, ""); return std::make_tuple(code, headers, payload, "");
} }
HttpResponse HttpClient::get( HttpResponse HttpClient::get(HttpRequestArgs args)
const std::string& url,
const WebSocketHttpHeaders& extraHeaders,
bool followRedirects,
bool verbose)
{ {
return request(url, "GET", extraHeaders, return request("GET", std::string(), args);
HttpParameters(), followRedirects,
verbose);
} }
HttpResponse HttpClient::post( HttpResponse HttpClient::head(HttpRequestArgs args)
const std::string& url,
const WebSocketHttpHeaders& extraHeaders,
const HttpParameters& httpParameters,
bool followRedirects,
bool verbose)
{ {
return request(url, "POST", extraHeaders, return request("HEAD", std::string(), args);
httpParameters, followRedirects,
verbose);
} }
HttpResponse HttpClient::head( HttpResponse HttpClient::post(const HttpParameters& httpParameters,
const std::string& url, HttpRequestArgs args)
const WebSocketHttpHeaders& extraHeaders,
bool followRedirects,
bool verbose)
{ {
return request(url, "HEAD", extraHeaders, return request("POST", serializeHttpParameters(httpParameters), args);
HttpParameters(), followRedirects, }
verbose);
HttpResponse HttpClient::post(const std::string& body,
HttpRequestArgs args)
{
return request("POST", body, args);
} }
std::string HttpClient::urlEncode(const std::string& value) std::string HttpClient::urlEncode(const std::string& value)
@ -353,4 +318,24 @@ namespace ix
return escaped.str(); 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)
{
ss << urlEncode(it.first)
<< "="
<< urlEncode(it.second);
if (i++ < (count-1))
{
ss << "&";
}
}
return ss.str();
}
} }

View File

@ -22,35 +22,35 @@ namespace ix
using HttpResponse = std::tuple<int, WebSocketHttpHeaders, std::string, std::string>; using HttpResponse = std::tuple<int, WebSocketHttpHeaders, std::string, std::string>;
using HttpParameters = std::map<std::string, std::string>; using HttpParameters = std::map<std::string, std::string>;
struct HttpRequestArgs
{
std::string url;
WebSocketHttpHeaders extraHeaders;
std::string body;
int timeoutSecs;
bool followRedirects;
bool verbose;
};
class HttpClient { class HttpClient {
public: public:
HttpClient(); HttpClient();
~HttpClient(); ~HttpClient();
// Static methods ? HttpResponse get(HttpRequestArgs args);
HttpResponse get(const std::string& url, HttpResponse head(HttpRequestArgs args);
const WebSocketHttpHeaders& extraHeaders,
bool followRedirects,
bool verbose);
HttpResponse post(const std::string& url, HttpResponse post(const HttpParameters& httpParameters,
const WebSocketHttpHeaders& extraHeaders, HttpRequestArgs args);
const HttpParameters& httpParameters, HttpResponse post(const std::string& body,
bool followRedirects, HttpRequestArgs args);
bool verbose);
HttpResponse head(const std::string& url,
const WebSocketHttpHeaders& extraHeaders,
bool followRedirects,
bool verbose);
private: private:
HttpResponse request(const std::string& url, HttpResponse request(const std::string& verb,
const std::string& verb, const std::string& body,
const WebSocketHttpHeaders& extraHeaders, HttpRequestArgs args);
const HttpParameters& httpParameters,
bool followRedirects, std::string serializeHttpParameters(const HttpParameters& httpParameters);
bool verbose);
std::string urlEncode(const std::string& value); std::string urlEncode(const std::string& value);

View File

@ -0,0 +1,42 @@
/*
* IXSocketFactory.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXSocketFactory.h"
#if defined(__APPLE__) or defined(__linux__)
# ifdef __APPLE__
# include <ixwebsocket/IXSocketAppleSSL.h>
# else
# include <ixwebsocket/IXSocketOpenSSL.h>
# endif
#endif
namespace ix
{
std::shared_ptr<Socket> createSocket(bool tls,
std::string& errorMsg)
{
errorMsg.clear();
if (!tls)
{
return std::make_shared<Socket>();
}
else
{
#ifdef IXWEBSOCKET_USE_TLS
# ifdef __APPLE__
return std::make_shared<SocketAppleSSL>();
# else
return std::make_shared<SocketOpenSSL>();
# endif
#else
errorMsg = "TLS support is not enabled on this platform.";
return nullptr;
#endif
}
}
}

View File

@ -0,0 +1,17 @@
/*
* IXSocketFactory.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <memory>
namespace ix
{
class Socket;
std::shared_ptr<Socket> createSocket(bool tls,
std::string& errorMsg);
}

View File

@ -9,7 +9,7 @@
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
#include <string> #include <string>
#include <unordered_map> #include <map>
#include <memory> #include <memory>
#include <algorithm> #include <algorithm>
@ -17,7 +17,27 @@ namespace ix
{ {
class Socket; class Socket;
using WebSocketHttpHeaders = std::unordered_map<std::string, std::string>; struct CaseInsensitiveLess
{
// Case Insensitive compare_less binary function
struct NocaseCompare
{
bool operator() (const unsigned char& c1, const unsigned char& c2) const
{
return std::tolower(c1) < std::tolower(c2);
}
};
bool operator() (const std::string & s1, const std::string & s2) const
{
return std::lexicographical_compare
(s1.begin(), s1.end(), // source range
s2.begin(), s2.end(), // dest range
NocaseCompare()); // comparison
}
};
using WebSocketHttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders( std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
std::shared_ptr<Socket> socket, std::shared_ptr<Socket> socket,

View File

@ -12,6 +12,7 @@
#include "IXWebSocketHandshake.h" #include "IXWebSocketHandshake.h"
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include "IXUrlParser.h" #include "IXUrlParser.h"
#include "IXSocketFactory.h"
#ifdef IXWEBSOCKET_USE_TLS #ifdef IXWEBSOCKET_USE_TLS
# ifdef __APPLE__ # ifdef __APPLE__
@ -78,23 +79,23 @@ namespace ix
std::string("Could not parse URL ") + url); std::string("Could not parse URL ") + url);
} }
if (protocol == "wss") if (protocol != "ws" && protocol != "wss")
{ {
_socket.reset(); std::stringstream ss;
#ifdef IXWEBSOCKET_USE_TLS ss << "Invalid protocol: " << protocol
# ifdef __APPLE__ << " for url " << url
_socket = std::make_shared<SocketAppleSSL>(); << " . Supported protocols are ws and wss";
# else
_socket = std::make_shared<SocketOpenSSL>(); return WebSocketInitResult(false, 0, ss.str());
# endif
#else
return WebSocketInitResult(false, 0, "TLS is not supported.");
#endif
} }
else
bool tls = protocol == "wss";
std::string errorMsg;
_socket = createSocket(tls, errorMsg);
if (!_socket)
{ {
_socket.reset(); return WebSocketInitResult(false, 0, errorMsg);
_socket = std::make_shared<Socket>();
} }
WebSocketHandshake webSocketHandshake(_requestInitCancellation, WebSocketHandshake webSocketHandshake(_requestInitCancellation,

View File

@ -1,12 +1,13 @@
/* /*
* ws.cpp * ws.cpp
* Author: Benjamin Sergeant * Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. * Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/ */
// //
// Main drive for websocket utilities // Main driver for websocket utilities
// //
#include "ws.h"
#include <string> #include <string>
#include <sstream> #include <sstream>
@ -15,34 +16,6 @@
#include <cli11/CLI11.hpp> #include <cli11/CLI11.hpp>
#include <ixwebsocket/IXSocket.h> #include <ixwebsocket/IXSocket.h>
namespace ix
{
int ws_http_client_main(const std::string& url,
const std::string& headers,
const std::string& data,
bool headersOnly,
bool followRedirects);
int ws_ping_pong_main(const std::string& url);
int ws_echo_server_main(int port);
int ws_broadcast_server_main(int port);
int ws_chat_main(const std::string& url,
const std::string& user);
int ws_connect_main(const std::string& url);
int ws_receive_main(const std::string& url,
bool enablePerMessageDeflate);
int ws_transfer_main(int port);
int ws_send_main(const std::string& url,
const std::string& path);
}
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
CLI::App app{"ws is a websocket tool"}; CLI::App app{"ws is a websocket tool"};
@ -53,9 +26,13 @@ int main(int argc, char** argv)
std::string user; std::string user;
std::string data; std::string data;
std::string headers; std::string headers;
std::string output;
bool headersOnly = false; bool headersOnly = false;
bool followRedirects = false; bool followRedirects = false;
bool verbose = false;
bool save = false;
int port = 8080; int port = 8080;
int connectTimeOutSeconds = 3;
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();
@ -84,13 +61,17 @@ int main(int argc, char** argv)
CLI::App* pingPongApp = app.add_subcommand("ping", "Ping pong"); CLI::App* pingPongApp = app.add_subcommand("ping", "Ping pong");
pingPongApp->add_option("url", url, "Connection url")->required(); pingPongApp->add_option("url", url, "Connection url")->required();
CLI::App* httpClientApp = app.add_subcommand("http_client", "HTTP Client"); CLI::App* httpClientApp = app.add_subcommand("curl", "HTTP Client");
httpClientApp->add_option("url", url, "Connection url")->required(); httpClientApp->add_option("url", url, "Connection url")->required();
httpClientApp->add_option("-d", data, "Form data")->join(); httpClientApp->add_option("-d", data, "Form data")->join();
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_flag("-I", headersOnly, "Header"); httpClientApp->add_flag("-I", headersOnly, "Header");
httpClientApp->add_flag("-L", followRedirects, "Header"); httpClientApp->add_flag("-L", followRedirects, "Header");
httpClientApp->add_flag("-v", verbose, "Verbose");
httpClientApp->add_flag("-O", save, "Save to disk");
httpClientApp->add_option("--connect-timeout", connectTimeOutSeconds, "Connection timeout");
CLI11_PARSE(app, argc, argv); CLI11_PARSE(app, argc, argv);
@ -129,10 +110,11 @@ int main(int argc, char** argv)
{ {
return ix::ws_ping_pong_main(url); return ix::ws_ping_pong_main(url);
} }
else if (app.got_subcommand("http_client")) else if (app.got_subcommand("curl"))
{ {
std::cout << "data: " << data << std::endl; return ix::ws_http_client_main(url, headers, data,
return ix::ws_http_client_main(url, headers, data, headersOnly, followRedirects); headersOnly, connectTimeOutSeconds,
followRedirects, verbose, save, output);
} }
return 1; return 1;

View File

@ -6,11 +6,28 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <fstream>
#include <ixwebsocket/IXHttpClient.h> #include <ixwebsocket/IXHttpClient.h>
#include <ixwebsocket/IXWebSocketHttpHeaders.h> #include <ixwebsocket/IXWebSocketHttpHeaders.h>
namespace ix namespace ix
{ {
std::string extractFilename(const std::string& path)
{
std::string::size_type idx;
idx = path.rfind('/');
if (idx != std::string::npos)
{
std::string filename = path.substr(idx+1);
return filename;
}
else
{
return path;
}
}
WebSocketHttpHeaders parseHeaders(const std::string& data) WebSocketHttpHeaders parseHeaders(const std::string& data)
{ {
WebSocketHttpHeaders headers; WebSocketHttpHeaders headers;
@ -29,7 +46,7 @@ namespace ix
auto key = token.substr(0, pos); auto key = token.substr(0, pos);
auto val = token.substr(pos+2); auto val = token.substr(pos+2);
std::cout << key << ": " << val << std::endl; std::cerr << key << ": " << val << std::endl;
headers[key] = val; headers[key] = val;
} }
@ -58,7 +75,7 @@ namespace ix
auto key = token.substr(0, pos); auto key = token.substr(0, pos);
auto val = token.substr(pos+1); auto val = token.substr(pos+1);
std::cout << key << ": " << val << std::endl; std::cerr << key << ": " << val << std::endl;
httpParameters[key] = val; httpParameters[key] = val;
} }
@ -69,31 +86,36 @@ namespace ix
const std::string& headersData, const std::string& headersData,
const std::string& data, const std::string& data,
bool headersOnly, bool headersOnly,
bool followRedirects) int timeoutSecs,
bool followRedirects,
bool verbose,
bool save,
const std::string& output)
{ {
HttpRequestArgs args;
args.url = url;
args.extraHeaders = parseHeaders(headersData);
args.timeoutSecs = timeoutSecs;
args.followRedirects = followRedirects;
args.verbose = verbose;
HttpParameters httpParameters = parsePostParameters(data); HttpParameters httpParameters = parsePostParameters(data);
WebSocketHttpHeaders headers = parseHeaders(headersData);
HttpClient httpClient; HttpClient httpClient;
bool verbose = true;
HttpResponse out; HttpResponse out;
if (headersOnly) if (headersOnly)
{ {
out = httpClient.head(url, headers, out = httpClient.head(args);
followRedirects, verbose);
} }
else if (data.empty()) else if (data.empty())
{ {
out = httpClient.get(url, headers, out = httpClient.get(args);
followRedirects, verbose);
} }
else else
{ {
out = httpClient.post(url, headers, out = httpClient.post(httpParameters, args);
httpParameters,
followRedirects,
verbose);
} }
auto errorCode = std::get<0>(out); auto errorCode = std::get<0>(out);
auto responseHeaders = std::get<1>(out); auto responseHeaders = std::get<1>(out);
auto payload = std::get<2>(out); auto payload = std::get<2>(out);
@ -101,19 +123,43 @@ namespace ix
for (auto it : responseHeaders) for (auto it : responseHeaders)
{ {
std::cout << it.first << ": " << it.second << std::endl; std::cerr << it.first << ": " << it.second << std::endl;
} }
std::cout << "error code: " << errorCode << std::endl; std::cerr << "error code: " << errorCode << std::endl;
if (!errorMsg.empty()) if (!errorMsg.empty())
{ {
std::cout << "error message: " << errorMsg << std::endl; std::cerr << "error message: " << errorMsg << std::endl;
} }
if (!headersOnly) if (!headersOnly && errorCode == 200)
{
if (responseHeaders["Content-Type"] != "application/octet-stream")
{ {
std::cout << "payload: " << payload << std::endl; std::cout << "payload: " << payload << std::endl;
} }
else
{
std::cerr << "Binary output can mess up your terminal." << std::endl;
std::cerr << "Use the -O flag to save the file to disk." << std::endl;
std::cerr << "You can also use the --output option to specify a filename." << std::endl;
}
if (save || !output.empty())
{
// FIMXE we should decode the url first
std::string filename = extractFilename(url);
if (!output.empty())
{
filename = output;
}
std::cout << "Writing to disk: " << filename << std::endl;
std::ofstream out(filename);
out.write((char*)&payload.front(), payload.size());
out.close();
}
}
return 0; return 0;
} }