belle

An HTTP / Websocket library in C++17 using Boost.Beast and Boost.ASIO.


belle

/

example

/

server

/

http

/

src

/

main.cc

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
// belle http example

#include "belle.hh"
namespace Belle = OB::Belle;

#include <ctime>

#include <string>
#include <sstream>
#include <iomanip>
#include <iostream>
#include <mutex>

// prototypes
std::string http_routes(Belle::Server::Http_Routes& routes);

// return string containing all http routes
std::string http_routes(Belle::Server::Http_Routes& routes)
{
  std::stringstream res;

  for (auto const& e : routes)
  {
    res << "Route:  " << (*e).first << "\nMethod:";

    for (auto const& m : (*e).second)
    {
      if (static_cast<Belle::Method>(m.first) == Belle::Method::unknown)
      {
        res << " ALL";
      }
      else
      {
        res << " " << Belle::http::to_string(static_cast<Belle::Method>(m.first));
      }
    }

    res << "\n\n";
  }

  return res.str();
}

int main(int argc, char *argv[])
{
  // init the server
  Belle::Server app;

  // set the listening address
  std::string address {"127.0.0.1"};
  app.address(address);

  // set the listening port
  int port {8080};
  app.port(port);

  // set the number of threads
  // default value is 1 single thread
  app.threads(std::thread::hardware_concurrency());

  // multithreading can be enabled on an http only server
  // websocket upgrades must be disabled to use multithreading
  // the websocket channel implementation is not thread safe
  // default value is true
  app.websocket(false);

  // mutex for iostream
  std::mutex io_mutex;

  // enable serving static files from a public directory
  // if the path is relative, make sure to run the program
  // in the right working directory
  app.public_dir("../public");

  // set default http headers
  Belle::Headers headers;
  headers.set(Belle::Header::server, "Belle");
  headers.set(Belle::Header::cache_control, "private; max-age=0");
  app.http_headers(headers);

  // handle the following signals
  app.signals({SIGINT, SIGTERM});

  // set the on signal callback
  app.on_signal([&](auto ec, auto sig)
  {
    // print out the signal received
    std::cerr << "\nSignal " << sig << "\n";

    // get the io_context and safely stop the server
    app.io().stop();
  });

  // handle route GET '/'
  app.on_http("/", Belle::Method::get, [](Belle::Server::Http_Ctx& ctx)
  {
    // the response string
    std::string res {R"(
      <a href="/">home</a><br>
      <a href="/method">methods</a><br>
      <a href="/params?key=value&blank=&query=test&page=2">query parameters</a><br>
      <a href="/regex/hello">regex route</a><br>
      <a href="/error">custom error</a><br>
      <a href="/index.html">static page</a><br>
    )"};

    // set http response headers
    ctx.res.set("content-type", "text/html");

    // set the http status code
    // the http status code can be either an integer or
    // its respective enum representation
    // the default status is 200 OK
    ctx.res.result(Belle::Status::ok);

    // set the http body
    ctx.res.body() = std::move(res);
  });

  // handle route ALL '/method'
  // matches all methods
  app.on_http("/method", [](Belle::Server::Http_Ctx& ctx)
  {
  // get the request method type as a string
    std::stringstream res; res
    << "HTTP Method\n"
    << "method: " << ctx.req.method_string() << "\n";

    // set http response headers
    ctx.res.set("content-type", "text/plain");

    // set the http status code
    ctx.res.result(Belle::Status::ok);

    // echo back the matched http method
    ctx.res.body() = res.str();
  });

  // handle route POST '/post'
  // echo back the posted data
  app.on_http("/post", Belle::Method::post, [](Belle::Server::Http_Ctx& ctx)
  {
    // get the request body data
    std::stringstream res; res
    << "Post Data\n"
    << "Body: " << ctx.req.body() << "\n";

    // set http response headers
    ctx.res.set("content-type", "text/plain");

    // set the http status code
    ctx.res.result(Belle::Status::ok);

    // echo back the request body
    ctx.res.body() = res.str();
  });

  // handle route GET '/params'
  // with query parameters
  // ex. http://localhost:8080/params?q=test&page=2
  app.on_http("/params", Belle::Method::get, [](Belle::Server::Http_Ctx& ctx)
  {
    // stringstream to hold the response
    std::stringstream res;
    res << "Query Parameters\n";

    // access the query parameters
    for (auto const& [key, val] : ctx.req.params())
    {
      // add each key value pair to the response
      res << key << " | " << val << "\n";
    }

    // set http response headers
    ctx.res.set("content-type", "text/plain");

    // set the http status code
    ctx.res.result(Belle::Status::ok);

    // echo back the query parameters
    ctx.res.body() = res.str();
  });

  // handle route GET '/regex/([a-z]+)'
  // match a regex path
  // one or more lowercase characters in the range of a-z
  // ex. http://localhost:8080/regex/hello
  // ex. http://localhost:8080/regex/belle
  app.on_http("^/regex/([a-z]+)$", Belle::Method::get, [](Belle::Server::Http_Ctx& ctx)
  {
    // access the path regex capture groups
    // using req.path() which is a vector of strings
    // index 0 contains the matched path, minus any query parameters
    // index 1 to n contain the value of the capture groups if any
    // the full path with query parameters is in req.target()
    std::string path {ctx.req.path().at(0)};
    std::string match {ctx.req.path().at(1)};

    // stringstream to hold the response
    std::stringstream res; res
    << "Regex Captures\n"
    << "path:   " << path << "\n"
    << "match: " << match << "\n";

    // set http response headers
    ctx.res.set("content-type", "text/plain");

    // set the http status code
    ctx.res.result(Belle::Status::ok);

    // echo back the captured path parameter
    ctx.res.body() = res.str();
  });

  // trigger the custom error callback
  app.on_http("/error", Belle::Method::get, [](Belle::Server::Http_Ctx& ctx)
  {
    // the thrown value sets the http status code and
    // calls the Belle::Server::on_http_error callback
    // the http status code can be either an integer or
    // its respective enum representation
    throw Belle::Status::internal_server_error;
  });

  // set custom error callback
  app.on_http_error([](Belle::Server::Http_Ctx& ctx)
  {
    // stringstream to hold the response
    // get the http status code represented as an int and string
    std::stringstream res; res
    << "Custom Error\n"
    << "Status: " << ctx.res.result_int() << "\n"
    << "Reason: " << ctx.res.result() << "\n";

    // set http response headers
    ctx.res.set("content-type", "text/plain");

    // send the custom error response
    ctx.res.body() = res.str();
  });

  // set http connect callback
  // called at the beginning of every request
  app.on_http_connect([&io_mutex](Belle::Server::Http_Ctx& ctx)
  {
    // acquire lock
    std::scoped_lock lock {io_mutex};

    // print http request headers
    std::cerr << ctx.req.base();
  });

  // set http disconnect callback
  // called at the end of every request
  app.on_http_disconnect([&io_mutex](Belle::Server::Http_Ctx& ctx)
  {
    // access http request headers
    std::string ip {std::string(ctx.req["X-Real-IP"]).empty() ? "localhost" : ctx.req["X-Real-IP"]};
    std::string ua {std::string(ctx.req["user-agent"])};
    std::string rf {std::string(ctx.req["referer"])};

    // get the current time
    std::time_t t {std::time(nullptr)};
    std::tm tm {*std::localtime(&t)};

    // acquire lock
    std::scoped_lock lock {io_mutex};

    // log output
    std::cerr
    // the current timestamp
    << "[" << std::put_time(&tm, "%H:%M:%S %e %b %Y") << "] "
    // the ip address
    << "[" << ip << "] "
    // the http status code as an integer
    << "[" << ctx.res.result_int() << "] "
    // the http method as a string
    << "[" << ctx.req.method_string() << "] "
    // the full request path as a string
    << "[" << ctx.req.target().to_string() << "] "
    // the http referer header
    << "[" << rf << "] "
    // the http user-agent header
    << "[" << ua << "]\n\n";
  });

  // print out the address and port
  // along with all registered http routes
  // followed by the log output
  std::cout
  << "Server: " << address << ":" << port << "\n\n"
  << "Navigate to the following url:\n"
  << "  http://" << address << ":" << port << "/\n\n"
  << "Try running the following commands:\n"
  << "  curl -X PUT http://" << address << ":" << port << "/method\n"
  << "  curl -X POST --data 'post body message here' http://"
  << address << ":" << port << "/post\n\n"
  << "Begin Routes>\n\n"
  << http_routes(app.http_routes())
  << "Begin Log>\n\n";

  // start the server
  app.listen();

  // the server blocks until a signal is received

  return 0;
}
Back to Top