libzeep

PrevUpHomeNext

REST Controller

The rest_controller class is similar to the html_controller in that it allows you to map a request to a member function. The difference however is that the REST controller translates parameters from HTTP requests into arguments for your method and translates the result of the method back into JSON to be returned to the client. Lets walk through an example again to show how this works.

We begin our example by declaring some shopping cart objects. These are plain structs that also define a serialize method for use with serialization.

struct Item
{
    std::string name;
    uint32_t	count;

    template<typename Archive>
    void serialize(Archive& ar, unsigned long version)
    {
        ar & zeep::make_nvp("name", name)
           & zeep::make_nvp("count", count);
    }
};

struct Cart
{
    int					id;
    std::string			client;
    std::vector<Item>	items;

    template<typename Archive>
    void serialize(Archive& ar, unsigned long version)
    {
        ar & zeep::make_nvp("id", id)
           & zeep::make_nvp("client", client)
           & zeep::make_nvp("items", items);
    }
};

Now we create a REST controller that will handle the creation of a new cart and adding and deleting items from this cart. We use standard CRUD REST syntax for this, so e.g. the cart ID is part of the path in the URI for adding and deleting items.

class shop_rest_controller : public zeep::http::rest_controller
{
  public:
    shop_rest_controller()
        : zeep::http::rest_controller("/cart")
    {
        map_post_request("", &shop_rest_controller::create_cart, "client");
        map_get_request("{id}", &shop_rest_controller::get_cart, "id");
        map_post_request("{id}/item", &shop_rest_controller::add_cart_item, "id", "name");
        map_delete_request("{id}/item", &shop_rest_controller::delete_cart_item, "id", "name");
    }

    int create_cart(const std::string& client)
    {
        int cartID = sNextCartID++;
        m_carts.push_back({ cartID, client });
        return cartID;
    }

    Cart& get_cart(int cartID)
    {
        auto oi = std::find_if(m_carts.begin(), m_carts.end(), [&](auto& o) { return o.id == cartID; });
        if (oi == m_carts.end())
            throw std::invalid_argument("No such cart");
        return *oi;
    }

    Cart add_cart_item(int cartID, const std::string& item)
    {
        Cart& cart = get_cart(cartID);

        auto ii = std::find_if(cart.items.begin(), cart.items.end(), [&](auto& i) { return i.name == item; });
        if (ii == cart.items.end())
            cart.items.push_back({item, 1});
        else
            ii->count += 1;

        return cart;
    }

    Cart delete_cart_item(int cartID, const std::string& item)
    {
        Cart& cart = get_cart(cartID);

        auto ii = std::find_if(cart.items.begin(), cart.items.end(), [&](auto& i) { return i.name == item; });
        if (ii != cart.items.end())
        {
            if (--ii->count == 0)
                cart.items.erase(ii);
        }

        return cart;
    }

  private:
    static int sNextCartID;
    std::vector<Cart> m_carts;
};

The calls to this rest controller are in the scripts/shop.js file. Have a look at that file to see how it works. To give you an idea, this is the snippet that is called after clicking the add link for an item.

addToCart(item) {
  const fd = new FormData();
  fd.append("name", item);
  fetch(`/cart/${this.cartID}/item`, { method: "POST", body: fd})
    .then(r => r.json())
    .then(order => this.updateOrder(order))
    .catch(err => {
      console.log(err);
      alert(`Failed to add ${item} to cart`);
    });
}

The page, script and stylesheet are served by a html_controller.

class shop_html_controller : public zeep::http::html_controller
{
  public:
    shop_html_controller()
    {
        mount("", &shop_html_controller::handle_index);
        mount("{css,scripts}/", &shop_html_controller::handle_file);
    }

    void handle_index(const zeep::http::request& req, const zeep::http::scope& scope, zeep::http::reply& rep)
    {
        get_template_processor().create_reply_from_template("shop.xhtml", scope, rep);
    }
};

And tie everything together.

int main()
{
    1zeep::http::server srv("docroot");

    srv.add_controller(new shop_html_controller());
    srv.add_controller(new shop_rest_controller());

    srv.bind("localhost", 8080);

    // Note that the rest controller above is not thread safe!
    srv.run(1);

    return 0;
}

1

Use the server constructor that takes the path to a docroot so it will construct a template processor


PrevUpHomeNext