simple HTTP post support (urlencode parameters)
This commit is contained in:
		
							
								
								
									
										1
									
								
								examples/http_client/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								examples/http_client/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| build | ||||
| @@ -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) | ||||
| @@ -18,9 +18,10 @@ | ||||
|  | ||||
| #include <iostream> | ||||
| #include <sstream> | ||||
| #include <iomanip> | ||||
| #include <vector> | ||||
|  | ||||
| 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<bool> 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<uint8_t> 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); | ||||
|             } | ||||
|   | ||||
| @@ -12,13 +12,15 @@ | ||||
| #include <atomic> | ||||
| #include <tuple> | ||||
| #include <memory> | ||||
| #include <map> | ||||
|  | ||||
| #include "IXSocket.h" | ||||
| #include "IXWebSocketHttpHeaders.h" | ||||
|  | ||||
| namespace ix  | ||||
| namespace ix | ||||
| { | ||||
|     using HttpResponse = std::tuple<int, WebSocketHttpHeaders, std::string, std::string>; | ||||
|     using HttpParameters = std::map<std::string, std::string>; | ||||
|  | ||||
|     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> _socket; | ||||
|     }; | ||||
| } | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| #include <sstream> | ||||
|  | ||||
|  | ||||
| namespace ix  | ||||
| namespace ix | ||||
| { | ||||
|     bool parseUrl(const std::string& url, | ||||
|                   std::string& protocol, | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| namespace ix  | ||||
| namespace ix | ||||
| { | ||||
|     bool parseUrl(const std::string& url, | ||||
|                   std::string& protocol, | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
|  | ||||
| namespace ix  | ||||
| namespace ix | ||||
| { | ||||
|     std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders( | ||||
|         std::shared_ptr<Socket> socket, | ||||
| @@ -21,7 +21,7 @@ namespace ix | ||||
|         char line[1024]; | ||||
|         int i; | ||||
|  | ||||
|         while (true)  | ||||
|         while (true) | ||||
|         { | ||||
|             int colon = 0; | ||||
|  | ||||
|   | ||||
							
								
								
									
										12
									
								
								ws/ws.cpp
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								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; | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
| // | ||||
| // Simple chat program that talks to the node.js server at | ||||
| // websocket_chat_server/broacast-server.js | ||||
| //  | ||||
| // | ||||
| #include <iostream> | ||||
| #include <sstream> | ||||
| #include <queue> | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user