Giter Site home page Giter Site logo

siesta's Introduction

Siesta

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.

Features

REST API

URI parameters

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());
            });
...

Queries

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());
            });
...

Websockets

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.

Building

Requirements

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.

Quick start

To build in a Linux environment:

$ mkdir build
$ cd build
$ cmake -GNinja ..
$ ninja
$ ninja test

Examples

Hello World (REST API server)

#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;
}

Hello World (client)

#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;
}

Serving a static directory

#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;
}

Websocket server

#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;
}

Websocket client

#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;
}

siesta's People

Contributors

anton-danielsson avatar draugvar avatar jerem584 avatar robiwano avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

siesta's Issues

Problem with mapping URI

Say I have

holder += httpServer->addRoute(HttpMethod::GET, "/a/:b/:c", foo);
holder += httpServer->addRoute(HttpMethod::GET, "/a", bar);

The above code will work with URI like "/a/bb/cc" being directed to the function foo and "/a" to bar.

However if I switch the order around

holder += httpServer->addRoute(HttpMethod::GET, "/a", bar);
holder += httpServer->addRoute(HttpMethod::GET, "/a/:b/:c", foo);

"/a/bb/cc" can no longer be directed to the correct function.

How to reproduce:

#include <siesta/server.h>
using namespace siesta;

#include <chrono>
#include <iostream>
#include <sstream>
#include <thread>

#include "ctrl_c_handler.h"

void foo(const server::rest::Request& req, server::rest::Response& resp)
{
    std::cout << "This is foo\n" << std::endl;
}
void bar(const server::rest::Request& req, server::rest::Response& resp)
{
    std::cout << "This is bar\n" << std::endl;
}

int main(int argc, char** argv)
{
    ctrlc::set_signal_handler();
    try {
        bool rest_shutdown = false;
        std::string addr   = "http://127.0.0.1:8090";
        if (argc > 1) {
            addr = argv[1];
        }
        auto server = server::createServer(addr);
        server->start();
        std::cout << "Server started, listening on port " << server->port()
                  << std::endl;

        server::TokenHolder h;
        
        h += server->addRoute(HttpMethod::GET, "/a", bar);
        h += server->addRoute(HttpMethod::GET, "/a/:b/:c", foo);
        
        while (!ctrlc::signalled() && !rest_shutdown) {
            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;
}

Problem with nng_stream

THX for you open source lib 'siesta'. I want to create a websocket server use nng, but I have no idea about how to create it until i found your repo. I read the source code and notice that you use nng_stream to solve it. Maybe there is a question when receive data. The repo use 'nng_stream_recv' to receive websocket data, but nng docs said that 'The I/O operation completes as soon as at least one byte has been received, or an error has occurred'. So it can't ensure that i receive whole data in a request?

Support for CORS and OPTIONS requests

I am attempting to create a basic REST API server using Siesta. However, I encountered an issue while trying to enable support for CORS on PUT requests. The browser initiates a preflight request in the form of an OPTIONS request, resulting in an "error 405 Method Not Allowed." Consequently, CORS verification fails.

I have inspected the code at common.h couldn't find explicit support for OPTIONS requests. Is there a way to enable them?

If there is no direct support, are there any workarounds that could address this issue?

Your assistance is greatly appreciated. Thanks in advance!

question about the surveyor pattern

Hi @robiwano ,

Hope you are all well !

I was wondering if it is complicated to extend siesta with the surveyor pattern and replicate what is described in this blog post https://daniel-j-h.github.io/post/distributed-search-nanomsg-bond/ ?

The idea would be to configure rest routes binding several, probably dockerized, respondent clients and aggregate all responses into one. Maybe, using a yaml file to configure the distribution tree ?!

Do you think it is doable ?

Thanks for your inputs and insights on that.

Cheers,
X

I cannot branch and make a pull request

Hi,
I have done some small bug fix/changes in order to compile with clang on macOS(ARM) but I cannot branch and pull any request. How can I contribute to this project?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.