Siesta is a minimalistic HTTP, REST and Websocket framework for C++, written in pure-C++11, based upon NNG (Nanomsg-Next-Generation).
Main features:
- Webserver
- REST API
- Websocket (binary/text)
The design goals for Siesta are:
- Minimalistic and simple interface.
- Dynamic route creation and removal.
- Minimal dependencies. Unless you need TLS, the only dependency is NNG.
- No required payload format. Requests and responses are plain strings/data.
- Cross platform.
Siesta will basically run on any platform supported by NNG. These are (taken from NNG Github Readme): Linux, macOS, Windows (Vista or better), illumos, Solaris, FreeBSD, Android, and iOS.
Since payloads are plain strings/data, you're free to use any layer on top of this, f.i. JSON or XML.
By prefixing a URI route segment with ':', that segment of the URI becomes a URI parameter, which can be retrieved in the route handler:
... server::TokenHolder h; h += server->addRoute( HttpMethod::GET, "/:resource/:index", [](const server::rest::Request& req, server::rest::Response& resp) { const auto& uri_params = req.getUriParameters(); std::stringstream body; body << "resource=" << uri_params.at("resource") << std::endl; body << "index=" << uri_params.at("index") << std::endl; resp.setBody(body.str()); }); ...
URI Queries are supported via server::Request::getQueries method:
... server::TokenHolder h; h += server->addRoute( HttpMethod::GET, "/resource/get", [](const server::rest::Request& req, server::rest::Response& resp) { const auto& queries = req.getQueries(); std::stringstream body; body << "Queries:" << std::endl; for (auto q : queries) { body << q.first << "=" << q.second << std::endl; } resp.setBody(body.str()); }); ...
The websocket API is built upon a factory pattern, where the websocket session is implemented by the user of the siesta framework, see example below.
You will need a compiler supporting C++11 and C99, and CMake version 3.11 or newer. Ninja is the recommended build system.
If you need TLS support, set the SIESTA_ENABLE_TLS CMake variable to ON. This will make CMake download ARM mbedTLS upon configuration and enable TLS support for NNG.
Note: When using ARM mbedTLS, the license will change to Apache 2.0.
To build in a Linux environment:
$ mkdir build
$ cd build
$ cmake -GNinja ..
$ ninja
$ ninja test
#include <siesta/server.h> #include <chrono> #include <iostream> #include <thread> using namespace siesta; int main(int argc, char** argv) { try { // Use "https://...." for a secure server auto server = server::createServer("http://127.0.0.1:9080"); server->start(); std::cout << "Server started, listening on port " << server->port() << std::endl; server::TokenHolder h; // Must hold on to the returned Token, otherwise the route will // be removed when the Token is destroyed. h += server->addRoute( HttpMethod::GET, "/", [](const server::rest::Request&, server::rest::Response& resp) { resp.setBody("Hello, World!"); }); // Run the server forever while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } catch (std::exception& e) { std::cerr << e.what() << std::endl; return -1; } return 0; }
#include <siesta/client.h> #include <chrono> #include <iostream> #include <thread> using namespace siesta; int main(int argc, char** argv) { try { auto f = client::getRequest("http://127.0.0.1:9080/"); auto response = f.get(); std::cout << response << std::endl; } catch (std::exception& e) { std::cerr << e.what() << std::endl; return -1; } return 0; }
#include <siesta/server.h> using namespace siesta; #include <chrono> #include <iostream> #include <thread> int main(int argc, char** argv) { try { if (argc < 2) { std::cout << "Options: [address] base_path" << std::endl; return 1; } std::string addr = "http://127.0.0.1"; const char* base_path = argv[1]; if (argc > 2) { addr = argv[1]; base_path = argv[2]; } auto server = server::createServer(addr); server->start(); std::cout << "Server started, listening on port " << server->port() << std::endl; server::TokenHolder h; h += server->addDirectory("/", base_path); std::cout << "Serving folder '" << base_path << "' under URI '/'" << std::endl; while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } catch (std::exception& e) { std::cerr << e.what() << std::endl; return -1; } return 0; }
#include <siesta/server.h> using namespace siesta; #include <chrono> #include <iostream> #include <thread> #include "ctrl_c_handler.h" // Dummy struct to show how to supply a websocket connection with an "owner" struct WebsocketData { }; struct WebsocketConnection : server::websocket::Reader { WebsocketData& owner; server::websocket::Writer& writer; WebsocketConnection(WebsocketData& owner, server::websocket::Writer& w) : owner(owner), writer(w) { std::cout << "Stream connected (" << this << ")" << std::endl; } ~WebsocketConnection() { std::cout << "Stream disconnected (" << this << ")" << std::endl; } void onMessage(const std::string& data) override { // Just echo back received data std::cout << "Echoing back '" << data << "' (" << this << ")" << std::endl; writer.send(data); } // The websocket factory method static server::websocket::Reader* create(WebsocketData& owner, server::websocket::Writer& w) { return new WebsocketConnection(owner, w); } }; int main(int argc, char** argv) { ctrlc::set_signal_handler(); try { std::string addr = "http://127.0.0.1:9080"; if (argc > 1) { addr = argv[1]; } { auto server = server::createServer(addr); server->start(); std::cout << "Server started, listening on port " << server->port() << std::endl; WebsocketData my_data; auto token = server->addTextWebsocket("/test", std::bind(&WebsocketConnection::create, my_data, std::placeholders::_1)); while (!ctrlc::signalled()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } std::cout << "Server stopped!" << std::endl; } catch (std::exception& e) { std::cerr << e.what() << std::endl; return -1; } return 0; }
#include <siesta/client.h> using namespace siesta; #include <chrono> #include <iostream> #include <thread> int main(int argc, char** argv) { try { std::string addr = "ws://127.0.0.1:9080/test"; if (argc > 1) { addr = argv[1]; } auto on_open = [](client::websocket::Writer&) { std::cout << "Client connected!" << std::endl; }; auto on_close = [](client::websocket::Writer&, const std::string& error) { std::cout << "Error: " << error << std::endl; }; auto on_close = [](client::websocket::Writer&) { std::cout << "Client disconnected!" << std::endl; }; auto on_message = [](client::websocket::Writer&, const std::string& data) { std::cout << "Received: " << data << std::endl; }; auto client = client::websocket::connect(addr, on_message, on_open, on_error, on_close); while (true) { std::string input; std::getline(std::cin, input); client->send(input); } } catch (std::exception& e) { std::cerr << e.what() << std::endl; return 1; } return 0; }