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:
Benjamin Sergeant
2019-02-28 21:54:03 -08:00
committed by GitHub
parent e881b82511
commit 0cff5065d9
40 changed files with 9871 additions and 206 deletions

View File

@ -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

View File

@ -6,6 +6,7 @@
#pragma once
#include <cstdint>
#include <vector>
namespace ix

View File

@ -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
View 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);
}

View File

@ -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
View 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;
}
}