Feature/http (#16)
* add skeleton and broken http client code. GET returns "Resource temporarily unavailable" errors... * linux compile fix * can GET some pages * Update formatting in README.md * unittest for sending large messages * document bug * Feature/send large message (#14) * introduce send fragment * can pass a fin frame * can send messages which are a perfect multiple of the chunk size * set fin only for last fragment * cleanup * last fragment should be of type CONTINUATION * Add simple send and receive programs * speedups receiving + better way to wait for thing * receive speedup by using linked list of chunks instead of large array * document bug * use chunks to receive data * trailing spaces * Update README.md Add note about message fragmentation. * Feature/ws cli (#15) * New command line tool for transfering files / still very beta. * add readme * use cli11 for argument parsing * json -> msgpack * stop using base64 and use binary which can be stored in message pack * add target for building with homebrew * all CMakeLists are referenced by the top level one * add ws_chat and ws_connect sub commands to ws * cleanup * add echo and broadcast server as ws sub-commands * add gitignore * comments * ping pong added to ws * mv cobra_publisher under ws folder * Update README.md * linux build fix * linux build fix * move http_client to a ws sub-command * simple HTTP post support (urlencode parameters) * can specify extra headers * chunk encoding / simple redirect support / -I option * follow redirects is optional * make README vim markdown plugin friendly * cleanup argument parsing + add socket creation factory * add missing file * http gzip compression * cleanup * doc * Feature/send large message (#14) * introduce send fragment * can pass a fin frame * can send messages which are a perfect multiple of the chunk size * set fin only for last fragment * cleanup * last fragment should be of type CONTINUATION * Add simple send and receive programs * speedups receiving + better way to wait for thing * receive speedup by using linked list of chunks instead of large array * document bug * use chunks to receive data * trailing spaces
This commit is contained in:
committed by
GitHub
parent
e881b82511
commit
0cff5065d9
@ -23,6 +23,7 @@ add_executable(ws
|
||||
ixcrypto/IXHash.cpp
|
||||
ixcrypto/IXUuid.cpp
|
||||
|
||||
ws_http_client.cpp
|
||||
ws_ping_pong.cpp
|
||||
ws_broadcast_server.cpp
|
||||
ws_echo_server.cpp
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
|
66
ws/ws.cpp
66
ws/ws.cpp
@ -1,9 +1,14 @@
|
||||
/*
|
||||
* ws.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
//
|
||||
// Main driver for websocket utilities
|
||||
//
|
||||
#include "ws.h"
|
||||
|
||||
//
|
||||
// Main drive for websocket utilities
|
||||
//
|
||||
@ -15,28 +20,6 @@
|
||||
#include <cli11/CLI11.hpp>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
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)
|
||||
{
|
||||
CLI::App app{"ws is a websocket tool"};
|
||||
@ -45,12 +28,23 @@ int main(int argc, char** argv)
|
||||
std::string url("ws://127.0.0.1:8080");
|
||||
std::string path;
|
||||
std::string user;
|
||||
std::string data;
|
||||
std::string headers;
|
||||
std::string output;
|
||||
bool headersOnly = false;
|
||||
bool followRedirects = false;
|
||||
bool verbose = false;
|
||||
bool save = false;
|
||||
bool compress = false;
|
||||
int port = 8080;
|
||||
int connectTimeOut = 60;
|
||||
int transferTimeout = 1800;
|
||||
int maxRedirects = 5;
|
||||
|
||||
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();
|
||||
|
||||
@ -73,6 +67,21 @@ int main(int argc, char** argv)
|
||||
CLI::App* pingPongApp = app.add_subcommand("ping", "Ping pong");
|
||||
pingPongApp->add_option("url", url, "Connection url")->required();
|
||||
|
||||
CLI::App* httpClientApp = app.add_subcommand("curl", "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();
|
||||
httpClientApp->add_option("-H", headers, "Header")->join();
|
||||
httpClientApp->add_option("--output", output, "Output file");
|
||||
httpClientApp->add_flag("-I", headersOnly, "Send a HEAD request");
|
||||
httpClientApp->add_flag("-L", followRedirects, "Follow redirects");
|
||||
httpClientApp->add_option("--max-redirects", maxRedirects, "Max Redirects");
|
||||
httpClientApp->add_flag("-v", verbose, "Verbose");
|
||||
httpClientApp->add_flag("-O", save, "Save output to disk");
|
||||
httpClientApp->add_flag("--compress", compress, "Enable gzip compression");
|
||||
httpClientApp->add_option("--connect-timeout", connectTimeOut, "Connection timeout");
|
||||
httpClientApp->add_option("--transfer-timeout", transferTimeout, "Transfer timeout");
|
||||
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
ix::Socket::init();
|
||||
@ -110,9 +119,12 @@ int main(int argc, char** argv)
|
||||
{
|
||||
return ix::ws_ping_pong_main(url);
|
||||
}
|
||||
else
|
||||
else if (app.got_subcommand("curl"))
|
||||
{
|
||||
assert(false);
|
||||
return ix::ws_http_client_main(url, headers, data, headersOnly,
|
||||
connectTimeOut, transferTimeout,
|
||||
followRedirects, maxRedirects, verbose,
|
||||
save, output, compress);
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
43
ws/ws.h
Normal file
43
ws/ws.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* ws.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int ws_http_client_main(const std::string& url,
|
||||
const std::string& headers,
|
||||
const std::string& data,
|
||||
bool headersOnly,
|
||||
int connectTimeout,
|
||||
int transferTimeout,
|
||||
bool followRedirects,
|
||||
int maxRedirects,
|
||||
bool verbose,
|
||||
bool save,
|
||||
const std::string& output,
|
||||
bool compress);
|
||||
|
||||
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);
|
||||
}
|
@ -5,9 +5,10 @@
|
||||
*/
|
||||
|
||||
//
|
||||
// Simple chat program that talks to the node.js server at
|
||||
// websocket_chat_server/broacast-server.js
|
||||
//
|
||||
// Simple chat program that talks to a broadcast server
|
||||
// Broadcast server can be ran with `ws broadcast_server`
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <queue>
|
||||
|
183
ws/ws_http_client.cpp
Normal file
183
ws/ws_http_client.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* http_client.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <ixwebsocket/IXHttpClient.h>
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
|
||||
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 headers;
|
||||
|
||||
// Split by \n
|
||||
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+2);
|
||||
|
||||
std::cerr << key << ": " << val << std::endl;
|
||||
headers[key] = val;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
//
|
||||
// Useful endpoint to test HTTP post
|
||||
// https://postman-echo.com/post
|
||||
//
|
||||
HttpParameters parsePostParameters(const std::string& data)
|
||||
{
|
||||
HttpParameters httpParameters;
|
||||
|
||||
// Split by \n
|
||||
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::cerr << key << ": " << val << std::endl;
|
||||
httpParameters[key] = val;
|
||||
}
|
||||
|
||||
return httpParameters;
|
||||
}
|
||||
|
||||
int ws_http_client_main(const std::string& url,
|
||||
const std::string& headersData,
|
||||
const std::string& data,
|
||||
bool headersOnly,
|
||||
int connectTimeout,
|
||||
int transferTimeout,
|
||||
bool followRedirects,
|
||||
int maxRedirects,
|
||||
bool verbose,
|
||||
bool save,
|
||||
const std::string& output,
|
||||
bool compress)
|
||||
{
|
||||
HttpRequestArgs args;
|
||||
args.extraHeaders = parseHeaders(headersData);
|
||||
args.connectTimeout = connectTimeout;
|
||||
args.transferTimeout = transferTimeout;
|
||||
args.followRedirects = followRedirects;
|
||||
args.maxRedirects = maxRedirects;
|
||||
args.verbose = verbose;
|
||||
args.compress = compress;
|
||||
args.logger = [](const std::string& msg)
|
||||
{
|
||||
std::cout << msg;
|
||||
};
|
||||
|
||||
HttpParameters httpParameters = parsePostParameters(data);
|
||||
|
||||
HttpClient httpClient;
|
||||
HttpResponse out;
|
||||
if (headersOnly)
|
||||
{
|
||||
out = httpClient.head(url, args);
|
||||
}
|
||||
else if (data.empty())
|
||||
{
|
||||
out = httpClient.get(url, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
out = httpClient.post(url, httpParameters, args);
|
||||
}
|
||||
|
||||
auto statusCode = std::get<0>(out);
|
||||
auto errorCode = std::get<1>(out);
|
||||
auto responseHeaders = std::get<2>(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)
|
||||
{
|
||||
std::cerr << it.first << ": " << it.second << std::endl;
|
||||
}
|
||||
|
||||
std::cerr << "Upload size: " << uploadSize << std::endl;
|
||||
std::cerr << "Download size: " << downloadSize << std::endl;
|
||||
|
||||
std::cerr << "Status: " << statusCode << std::endl;
|
||||
if (errorCode != HttpErrorCode_Ok)
|
||||
{
|
||||
std::cerr << "error message: " << errorMsg << std::endl;
|
||||
}
|
||||
|
||||
if (!headersOnly && errorCode == HttpErrorCode_Ok)
|
||||
{
|
||||
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();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (responseHeaders["Content-Type"] != "application/octet-stream")
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user