1. Introduction

HTTP requests received by a Crails server are first directed through the request pipe. The pipe consists of request parsers and handlers. The request run through each of these handlers sequentially, until one of the parser or handler responds to or rejects the query.

You can configure which parsers and handlers are enabled in your server, and in which order they should run, by editing the config/request_pipe.cpp, which looks like this:

#include <crails/request_handlers/file.hpp>
#include <crails/request_handlers/action.hpp>
#include <crails/request_parsers/url_parser.hpp>
#include <crails/request_parsers/form_parser.hpp>
#include <crails/request_parsers/multipart_parser.hpp>
#include "server.hpp"

using namespace Crails;

void ApplicationServer::initialize_request_pipe()
{
  add_request_parser(new RequestUrlParser);
  add_request_parser(new RequestFormParser);
  add_request_parser(new RequestMultipartParser);
  add_request_handler(new ActionRequestHandler);
  add_request_handler(new FileRequestHandler);
}

The request pipe first runs the parsers, then the handlers. In both case, they will run in the same order as the calls for add_request_parser and add_request_handler.

1.1 Adding support for queries in json format

Crails comes with a request parser that can load the json body of a request into the query's params object. To add this request parser, you would need to include its header file, and register it by calling add_request_parser from the initialize_request_pipe method.

In the following example, we use the json request parser with only the action request handler, to configure our crails server as a lightweight web service.

#include <crails/request_parsers/json_parser.hpp>
#include <crails/request_handlers/action.hpp>
#include "server.hpp"

using namespace Crails;

void ApplicationServer::initialize_request_pipe()
{
  add_request_parser(new RequestJsonParser);
  add_request_handler(new ActionRequestHandler);
}

2. Writing your own request handler

There are cases in which you may want to intercept queries before they reach the application's router. Or perhaps routing isn't a good strategy for you, and you want to replace ActionRequestHandler with a solution which better fits your needs.

That's when writing a request handler becomes a viable option. Writing request handlers is pretty straightforward. Here's an example:

#pragma once
#include <crails/request_handler.hpp>

class MyRequestHandler : public Crails::RequestHandler
{
public:
  MyRequestHandler() : Crails::RequestHandler("my request handler's name")
  {
  }

  void operator()(Crails::Context& context, std::function<void(bool) callback) const override
  {
    if (context.params["uri"].as<string>() == "/my_request_handler")
    {
      context.response.set_response(
        Crails::HttpStatus::ok,
        "Hi ! This is your very own request handler speaking !"
      );
      callback(true);
    }
    else
      callback(false);
  }
};

Simple stuff, really: overload operator(), receive a Crails::Context and a callback object.

The callback must be called at least once, and only once.
Calling the callback with the value true will cause the request pipe to stop running the requests through the handlers, and send directly the response.
Calling the callback with the value false will pass the request on to the next request handler, or if there are no more handlers to run, will respond to the query with a 404 not found status.

The request pipe can work asynchronously. You may safely invoke the callback from any thread. However, to make sure that the Crails::Context object doesn't get deleted, you must keep around a reference to its shared pointer:

  auto context_ptr = context.shared_from_this();
  some_asynchronous_function([context_ptr, callback]()
  {
    callback(true);
  });
Request parsers and handlers are stateless. This is essential, as they may be running from several threads at once.

3. Writing your own parser

The process to writing a request parser is very similar to the request handler. Let's try a practical use case where we'll implement a partial YAML parser. First steps first, let's scaffold our request parser:

#pragma once
#include <crails/request_parser.hpp>

class MyRequestParser : public Crails::RequestParser
{
public:
  void operator()(
    Crails::Context& context,
    std::function<void(Crails::RequestParser::Status)> callback) const
  {
    callback(Continue);
  }
};

While this is similar to a request handler, you'll notice that the callback takes a different kind of parameter. This parameter is used by parsers to tell the request pipe whether the request is completely parsed, or if other parsers may take a look at it.

There are three values you may send to the callback:

3.1 Accepting a request

We only want our parser to run when the client is sending us a YAML body. Other requests must not be treated by our parser. To achieve that, we will check the "Accept" header on the request by using content_type_matches, a helper method provided by Crails::RequestParser:

#pragma once
#include <crails/request_parser.hpp>

class MyRequestParser : public Crails::RequestParser
{
public:
  void operator()(
    Crails::Context& context,
    std::function<void(Crails::RequestParser::Status)> callback) const
  {
    static const std::regex is_yaml("text/yaml", std::regex_constants::optimize);
    bool has_acceptable_type = content_type_matches(params, is_yaml);
    bool has_relevant_method = context.params["method"].as<std::string>() != "GET";

    if (has_acceptable_type && has_relevant_method)
      callback(Stop);
    else
      callback(Continue);
  }
};

By using callback(Stop) when we recognize that a request should be handled by our parser, we stop the parsing process and send the request directly to the request handlers. But we haven't actually parsed the query. Now that we know how to intercept it, let's see how to actually read its contents.

3.2 Reading the body of a request

Since we are going to parse YAML, we will need access to the query's body. However, the request pipe doesn't wait for the request body to arrive before starting to run the parsers and handlers. If your parser needs to wait for the request body to have been delivered, you'll need to use Crails::BodyParser and the wait_for_body helper:

#pragma once
#include <crails/request_parser.hpp>

class MyRequestParser : public Crails::BodyParser
{
public:
  void operator()(
    Crails::Context& context,
    std::function<void(Crails::RequestParser::Status)> callback) const
  {
    static const std::regex is_yaml("text/vnd.yaml", std::regex_constants::optimize);
    bool has_acceptable_type = content_type_matches(params, is_yaml);
    bool has_relevant_method = context.params["method"].as<std::string>() != "GET";

    if (has_acceptable_type && has_relevant_method)
    {
      wait_for_body(context, [&context, callback]()
      {
        bool was_valid_yaml = context.response.get_status_code() != Crails::HttpStatus::bad_request);

        callback(was_valid_yaml ? Stop : Abort);
      });
    }
    else
      callback(Continue);
  }

  void body_received(Crails::Context& context, const std::string& body) const override
  {
    bool success = parse_yaml(context, body);

    if (!success)
      context.response.set_status_code(Crails::HttpStatus::bad_request);
  }

private:
  bool parse_yaml(Crails::Context& context, const std::string& body) const
  {
    // This is where we'll implement our YAML parser
    return false;
  }
};

In this example, we've replace Crails::RequestParser with Crails::BodyParser, a more specific type of parser designed for parsing request bodies, while the former type of parsers are best used to handle the contents of the request headers.

We then use wait_for_body so that our parser only runs if or when the body is available. The parameters for wait_for_body are the request's Context and a callback that will callback to the request pipeline with the parser's status code, after the request has been parsed.

Once the request body has been entirely received, it will be forwarded to the body_received method: this is where we call our own custom method, which we called parse_yaml, in which we will later implement the actual parser.

After body_received returns, the callback we passed as parameter to wait_for_body will be called in turn, and the request pipeline will proceed to either the request handlers or end the request, depending on whether we responded respectively with Stop or Abort.

Your request parser may also respond with the Continue status after parsing a request body, however this may have some wicked repercussions. A request body can only be received a single time: if two body parsers were to use wait_for_body on the same request, the pipeline would hang undefinitely. As such, it is best to make it so that body parsers are always the last parser to run in a pipeline, and do not allow other parsers to run after them.

If the parsing fails, we immediately set a response status (400 bad request). This is later used in the wait_for_body callback to determine whether the parsing was successful or not.

3.3 Fill in the Params object

We've seen how to receive a request body, but we aren't yet doing anything with it. In the following chapter, we'll use the yaml-cpp library to parse our YAML body and store it without the context's params attribute.

The params attribute inherits from the DataTree class, which is used to store data in a structure independent from the query format. Here's how we would store a YAML::Node into a DataTree:

static void load_yaml_tree(YAML::Node node, DataTree& tree)
{
  for (auto it = node.begin() ; it != node.end() ; ++it)
  {
    std::string key = it->first.as<string>();

    tree[key] = it->second.as<string>();
  }
}

This simple function maps a YAML map of strings into our DataTree object. Let's try something a bit more complex, where we can recursively load any structure of YAML:

static void load_yaml_map(YAML::Node node, Data branch);
static void load_yaml_array(YAML::Node node, Data branch);
static void load_yaml_node(YAML::Node node, Data branch);

// We'll first rewrite our function to use `load_yaml_node`, which
// we will design to be recursively callable on any branch of the tree:
static void load_yaml_tree(YAML::Node node, DataTree& tree)
{
  load_yaml_node(node, tree.as_data());
}

// Now we implement `load_yam_node` to filter node by types:
// Sequences and Map will have their own functions, while
// other types will be assigned to the branch as character strings:
static void load_yaml_node(YAML::Node node, Data branch)
{
  switch (node.Type())
  {
  case YAML::NodeType::Null:
    break ;
  case YAML::NodeType::Sequence:
    load_yaml_array(node, branch);
    break ;
  case YAML::NodeType::Map:
    load_yaml_map(node, branch);
    break ;
  default:
    branch = node.as<string>();
    break ;
  }
}

// For maps, we will just register them as new branches
// of the tree:
static void load_yaml_map(YAML::Node node, Data branch)
{
  for (auto it = node.begin() ; it != node.end() ; ++it)
  {
    std::string key = it->first.as<string>();

    load_yaml_node(it->second, branch[key]);
  }
}

// As for arrays/sequences, we store them using the
// `push_back` method of the Data object:
static void load_yaml_array(YAML::Node node, Data branch)
{
  for (auto it = node.begin() ; it != node.end() ; ++it)
    branch.push_back(it->second.as<string>());
}

We now have implemented all the functions needed to store a YAML node into a DataTree. Let's now implement the body_received callback of our MyRequestParser class:

void MyRequestParser::body_received(Crails::Context& context, const std::string& body) const
{
  YAML::Node node = YAML::Load(body);

  load_yaml_tree(node, context.params);
}

Now there's still an issue remaining: if the parsing fail, YAML::Load will throw an exception, and our server will respond with an error 500. That would be inaccurate, as the error lies in the query. Let's update our body_received method to handle parsing errors:

void MyRequestParser::body_received(Crails::Context& context, const std::string& body) const
{
  try
  {
    YAML::Node node = YAML::Load(body);

    load_yaml_tree(node, context.params);
  }
  catch (const std::exception& e)
  {
    context.response.set_status_code(Crails::HttpStatus::bad_request);
  }
}
Note that this is a very abridged support of YAML we've implemented here, for the sake of the examples. Follow this link to see a more complete YAML parser for Crails.

3.4 Streaming a request body

In some very specific cases, you may receive a request body that's too big to reasonably handle in a single step. In which case, you'll want to receive the body asynchronously, chunk by chunk: this can be done by setting a callback on the context's connection object.

#pragma once
#include <crails/request_parser.hpp>

class MyRequestParser : public Crails::RequestParser
{
public:
  void operator()(
    Crails::Context& context,
    std::function<void(Crails::RequestParser::Status)> callback
  ) const
  {
    auto& connection = *context.connection;
    auto& request = connection.get_request();

    if (*(request.content_length()) > 0)
    {
      connection.on_received_body_chunk([this, callback](std::string_view chunk)
      {
        // Insert your handler here
      });
    }
    else
     callback(Continue);
  }
};

This is the basic gist of things. Note that we check for the body's content length first: if you expect for an empty body to arrive, your query will just hang undefinitely, resulting in a memory leak.

Now we can't completely handle the request without knowing when it ends. This requires us to record how many bytes we've read... but since request parsers are stateless, we'll have to create a different object to record our state:

#pragma once
#include <crails/request_parser.hpp>

class MyParserState
{
  int bytes_remaining;
};

Then we'll create a smart pointer towards our state, and store it within our callback for Connection::on_received_body_chunk:

class MyRequestParser : public Crails::RequestParser
{
public:
  void operator()(
    Crails::Context& context,
    std::function<void(Crails::RequestParser::Status)> callback
  ) const
  {
    auto& connection = *context.connection;
    auto& request = connection.get_request();

    if (*(request.content_length()) > 0)
    {
      auto state = std::make_shared<MyParserState>();

      state->bytes_remaining = *request.content_length();
      connection.on_received_body_chunk(
        std::bind(
          &MyRequestParser::read_body_chunk,
          state,
          std::placeholders::_1,
          callback
        )
      );
    }
    else
      callback(Continue);
  }

  ...
};
The on_received_body_chunk callbacks gets removed by the server as soon as a requests completes or gets interrupted, ensuring any object we store in these callbacks get destroyed whenever they're no longer needed.

Now all that's left to do is implement the read_body_chunk method, update our state and call the pipeline's callback once the body has been fully received:

class MyRequestParser : public Crails::RequestParser
{
  ...

private:
  void read_body_chunk(
    std::shared_ptr<MyParserState> state,
    std::string_view chunk,
    std::function<void(Crails::RequestParser::Status)> callback
  ) const
  {
    state->bytes_remaining -= chunk.length();
    if (state->bytes_remaining <= 0)
      callback(Stop);
  }
};

3.5 Set the size limit for request bodies

TODO