From 3bcd6f97a62820f8e999a686c0d1ea2a3f48ec79 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Mon, 25 Feb 2019 15:55:38 -0800 Subject: [PATCH] simple HTTP post support (urlencode parameters) --- examples/http_client/.gitignore | 1 - examples/http_client/CMakeLists.txt | 22 ----- ixwebsocket/IXHttpClient.cpp | 106 ++++++++++++++++++++++--- ixwebsocket/IXHttpClient.h | 14 +++- ixwebsocket/IXUrlParser.cpp | 2 +- ixwebsocket/IXUrlParser.h | 2 +- ixwebsocket/IXWebSocketHttpHeaders.cpp | 4 +- ws/ws.cpp | 12 ++- ws/ws_chat.cpp | 2 +- ws/ws_http_client.cpp | 46 ++++++++++- 10 files changed, 164 insertions(+), 47 deletions(-) delete mode 100644 examples/http_client/.gitignore delete mode 100644 examples/http_client/CMakeLists.txt diff --git a/examples/http_client/.gitignore b/examples/http_client/.gitignore deleted file mode 100644 index 378eac25..00000000 --- a/examples/http_client/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build diff --git a/examples/http_client/CMakeLists.txt b/examples/http_client/CMakeLists.txt deleted file mode 100644 index d6afe8fc..00000000 --- a/examples/http_client/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# Author: Benjamin Sergeant -# Copyright (c) 2018 Machine Zone, Inc. All rights reserved. -# - -cmake_minimum_required (VERSION 3.4.1) -project (http_client) - -set (CMAKE_CXX_STANDARD 14) - -option(USE_TLS "Add TLS support" ON) - -add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket) - -add_executable(http_client http_client.cpp) - -if (APPLE AND USE_TLS) - target_link_libraries(http_client "-framework foundation" "-framework security") -endif() - -target_link_libraries(http_client ixwebsocket) -install(TARGETS http_client DESTINATION bin) diff --git a/ixwebsocket/IXHttpClient.cpp b/ixwebsocket/IXHttpClient.cpp index ffe1306a..c0e27681 100644 --- a/ixwebsocket/IXHttpClient.cpp +++ b/ixwebsocket/IXHttpClient.cpp @@ -18,9 +18,10 @@ #include #include +#include #include -namespace ix +namespace ix { HttpClient::HttpClient() { @@ -32,8 +33,11 @@ namespace ix } - HttpResponse HttpClient::get(const std::string& url, - bool verbose) + HttpResponse HttpClient::request( + const std::string& url, + const std::string& verb, + const HttpParameters& httpParameters, + bool verbose) { int code = 0; WebSocketHttpHeaders headers; @@ -68,17 +72,47 @@ namespace ix return std::make_tuple(code, headers, payload, errorMsg); } - // FIXME: missing url parsing + std::string body; + if (verb == "POST") + { + 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 << "&"; + } + } + body = ss.str(); + } std::stringstream ss; - ss << "GET " << path << " HTTP/1.1\r\n"; + ss << verb << " " << path << " HTTP/1.1\r\n"; ss << "Host: " << host << "\r\n"; ss << "User-Agent: ixwebsocket/1.0.0" << "\r\n"; ss << "Accept: */*" << "\r\n"; - ss << "\r\n"; + if (verb == "POST") + { + ss << "Content-Length: " << body.size() << "\r\n"; + ss << "Content-Type: application/x-www-form-urlencoded" << "\r\n"; + ss << "\r\n"; + ss << body; + } + else + { + ss << "\r\n"; + } + std::string request(ss.str()); - int timeoutSecs = 3; + int timeoutSecs = 10; std::string errMsg; static std::atomic requestInitCancellation(false); @@ -95,8 +129,12 @@ namespace ix if (verbose) { - std::cout << "Sending request: " << request - << "to " << host << ":" << port + std::cout << "Sending " << verb << " request " + << "to " << host << ":" << port << std::endl + << "request size: " << request.size() << " bytes" + << "=============" << std::endl + << request + << "=============" << std::endl << std::endl; } @@ -174,24 +212,66 @@ namespace ix return std::make_tuple(code, headers, payload, ""); } + + HttpResponse HttpClient::get( + const std::string& url, + bool verbose) + { + return request(url, "GET", HttpParameters(), verbose); + } + + HttpResponse HttpClient::post( + const std::string& url, + const HttpParameters& httpParameters, + bool verbose) + { + return request(url, "POST", httpParameters, verbose); + } + + std::string HttpClient::urlEncode(const std::string& value) + { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (std::string::const_iterator i = value.begin(), n = value.end(); + i != n; ++i) + { + 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(); + } } #if 0 std::vector rxbuf; - while (true) + while (true) { int N = (int) _rxbuf.size(); _rxbuf.resize(N + 1500); ssize_t ret = _socket->recv((char*)&_rxbuf[0] + N, 1500); - if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK || + if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK || _socket->getErrno() == EAGAIN)) { _rxbuf.resize(N); break; } - else if (ret <= 0) + else if (ret <= 0) { _rxbuf.resize(N); @@ -199,7 +279,7 @@ namespace ix setReadyState(CLOSED); break; } - else + else { _rxbuf.resize(N + ret); } diff --git a/ixwebsocket/IXHttpClient.h b/ixwebsocket/IXHttpClient.h index b2429f6d..48cf640c 100644 --- a/ixwebsocket/IXHttpClient.h +++ b/ixwebsocket/IXHttpClient.h @@ -12,13 +12,15 @@ #include #include #include +#include #include "IXSocket.h" #include "IXWebSocketHttpHeaders.h" -namespace ix +namespace ix { using HttpResponse = std::tuple; + using HttpParameters = std::map; class HttpClient { public: @@ -27,8 +29,18 @@ namespace ix // Static methods ? HttpResponse get(const std::string& url, bool verbose); + HttpResponse post(const std::string& url, + const HttpParameters& httpParameters, + bool verbose); private: + HttpResponse request(const std::string& url, + const std::string& verb, + const HttpParameters& httpParameters, + bool verbose); + + std::string urlEncode(const std::string& value); + std::shared_ptr _socket; }; } diff --git a/ixwebsocket/IXUrlParser.cpp b/ixwebsocket/IXUrlParser.cpp index 7ba6dc2a..4526036a 100644 --- a/ixwebsocket/IXUrlParser.cpp +++ b/ixwebsocket/IXUrlParser.cpp @@ -11,7 +11,7 @@ #include -namespace ix +namespace ix { bool parseUrl(const std::string& url, std::string& protocol, diff --git a/ixwebsocket/IXUrlParser.h b/ixwebsocket/IXUrlParser.h index e54c5201..05264c35 100644 --- a/ixwebsocket/IXUrlParser.h +++ b/ixwebsocket/IXUrlParser.h @@ -8,7 +8,7 @@ #include -namespace ix +namespace ix { bool parseUrl(const std::string& url, std::string& protocol, diff --git a/ixwebsocket/IXWebSocketHttpHeaders.cpp b/ixwebsocket/IXWebSocketHttpHeaders.cpp index dfd99f34..7f5d449e 100644 --- a/ixwebsocket/IXWebSocketHttpHeaders.cpp +++ b/ixwebsocket/IXWebSocketHttpHeaders.cpp @@ -10,7 +10,7 @@ #include #include -namespace ix +namespace ix { std::pair parseHttpHeaders( std::shared_ptr socket, @@ -21,7 +21,7 @@ namespace ix char line[1024]; int i; - while (true) + while (true) { int colon = 0; diff --git a/ws/ws.cpp b/ws/ws.cpp index b4ab21c2..2dbb9e9d 100644 --- a/ws/ws.cpp +++ b/ws/ws.cpp @@ -17,7 +17,8 @@ namespace ix { - int ws_http_client_main(const std::string& url); + int ws_http_client_main(const std::string& url, + const std::string& data); int ws_ping_pong_main(const std::string& url); @@ -47,11 +48,13 @@ int main(int argc, char** argv) std::string url("ws://127.0.0.1:8080"); std::string path; std::string user; + std::string data; int port = 8080; CLI::App* sendApp = app.add_subcommand("send", "Send a file"); sendApp->add_option("url", url, "Connection url")->required(); - sendApp->add_option("path", path, "Path to the file to send")->required(); + sendApp->add_option("path", path, "Path to the file to send") + ->required()->check(CLI::ExistingPath); CLI::App* receiveApp = app.add_subcommand("receive", "Receive a file"); receiveApp->add_option("url", url, "Connection url")->required(); @@ -77,6 +80,8 @@ int main(int argc, char** argv) CLI::App* httpClientApp = app.add_subcommand("http_client", "HTTP Client"); httpClientApp->add_option("url", url, "Connection url")->required(); + httpClientApp->add_option("-d", data, "Form data")->join(); + httpClientApp->add_option("-F", data, "Form data")->join(); CLI11_PARSE(app, argc, argv); @@ -117,7 +122,8 @@ int main(int argc, char** argv) } else if (app.got_subcommand("http_client")) { - return ix::ws_http_client_main(url); + std::cout << "data: " << data << std::endl; + return ix::ws_http_client_main(url, data); } return 1; diff --git a/ws/ws_chat.cpp b/ws/ws_chat.cpp index d621971b..1bf0fe48 100644 --- a/ws/ws_chat.cpp +++ b/ws/ws_chat.cpp @@ -7,7 +7,7 @@ // // Simple chat program that talks to the node.js server at // websocket_chat_server/broacast-server.js -// +// #include #include #include diff --git a/ws/ws_http_client.cpp b/ws/ws_http_client.cpp index 8a7b14b9..37a3a388 100644 --- a/ws/ws_http_client.cpp +++ b/ws/ws_http_client.cpp @@ -10,11 +10,51 @@ namespace ix { - void ws_http_client_main(const std::string& url) + // + // Useful endpoint to test HTTP post + // https://postman-echo.com/post + // + HttpParameters parsePostParameters(const std::string& data) { + HttpParameters httpParameters; + + // Split by ; + std::string token; + std::stringstream tokenStream(data); + + while (std::getline(tokenStream, token)) + { + std::size_t pos = token.rfind('='); + + // Bail out if last '.' is found + if (pos == std::string::npos) continue; + + auto key = token.substr(0, pos); + auto val = token.substr(pos+1); + + std::cout << key << ": " << val << std::endl; + httpParameters[key] = val; + } + + return httpParameters; + } + + int ws_http_client_main(const std::string& url, + const std::string& data) + { + HttpParameters httpParameters = parsePostParameters(data); + HttpClient httpClient; bool verbose = true; - auto out = httpClient.get(url, verbose); + HttpResponse out; + if (data.empty()) + { + out = httpClient.get(url, verbose); + } + else + { + out = httpClient.post(url, httpParameters, verbose); + } auto errorCode = std::get<0>(out); auto headers = std::get<1>(out); auto payload = std::get<2>(out); @@ -32,5 +72,7 @@ namespace ix } std::cout << "payload: " << payload << std::endl; + + return 0; } }