Full-fledged client-server example with C++ REST SDK 1.1.0

UPDATE: for an updated version of this demo, using C++ REST SDK 2.10 see this blog post.

In my previous post I shown how you can build a C++ application with the C++ REST SDK that fetches search results from a search engine. In this post, I will go a step further and develop a client-server application from scratch using version 1.1.0 of the SDK. This version features an HTTP listener implementation (still in an experimental phase). Notice that for the time being this 1.1.0 SDK release does not work with Visual Studio 2013 Preview. This samples are built with Visual Studio 2012.

Overview of the problem to solve

The server manages a dictionary of key-value pairs (both strings) and supports several HTTP request methods:

  • GET: retrieves all the key-value pair from the dictionary.
    The response is a JSON object representing key-value pairs (eg. {"one" : "100", "two" : "200"}).
  • POST: retrieves the values of the specified keys from the dictionary.
    The request is a JSON array of strings (eg. ["one", "two", "three"]).
    The response is similar to the GET method, except that only requested keys are returned.
  • PUT: inserts new pairs of key-values in the dictionary. If a key is already found its value is updated.
    The request is a JSON object representing pairs of keys and values (eg. {"one" : "100", "two" : "200"})
    The response is a JSON object representing they key and the result for the action, such as addition or update (eg. {"one" : "<put>", "two" : "<updated>"}).
  • DEL: deletes the specified keys from the dictionary.
    The request is a JSON array of strings (eg. ["one", "two", "three"]).
    The response is a JSON object representing they key and the result for the action, such as success or failure (eg. {"one" : "<deleted>", "two" : "<failed>"}).

Notice that the server implements both GET and POST. The GET method is supposed to request a representation of the specified URI. Though it is theoretically possible that a GET request carries a body, in practice that should be ignored. The C++ REST library actually triggers an exception if you make a GET request with a body. Therefore, GET is used to return the entire content of the dictionary and the POST method, that supports a body, returns only the requested key-value pairs.

The client can make HTTP requests to the server, adding or updating key-values, fetch or delete existing pairs.

All communication, both for the request and the answer, is done using JSON values.

The server implementation

On the server side we have to do the following:

  • instantiate an http_listener object, specifying the URI where it should listen for requests.
  • provide handlers for the HTTP request methods for the listener.
  • open the listener and loop to wait for messages.

The core of the server application is shown below (except for the request handlers).

#include <cpprest/http_listener.h>
#include <cpprest/json.h>
#pragma comment(lib, "cpprest110_1_1")

using namespace web;
using namespace web::http;
using namespace web::http::experimental::listener;

#include <iostream>
#include <map>
#include <set>
#include <string>
using namespace std;

#define TRACE(msg)            wcout << msg
#define TRACE_ACTION(a, k, v) wcout << a << L" (" << k << L", " << v << L")\n"

map dictionary;

/* handlers implementation */

int main()
{
   http_listener listener(L"http://localhost/restdemo");

   listener.support(methods::GET, handle_get);
   listener.support(methods::POST, handle_post);
   listener.support(methods::PUT, handle_put);
   listener.support(methods::DEL, handle_del);

   try
   {
      listener
         .open()
         .then([&listener](){TRACE(L"\nstarting to listen\n");})
         .wait();

      while (true);
   }
   catch (exception const & e)
   {
      wcout << e.what() << endl;
   }

   return 0;
}

In this simple implementation the dictionary is a std::map. Its content is not persisted to disk, it is reloaded each time the server starts.

Let’s now look at the handlers. As mentioned earlier the GET method is a bit different than the others. A GET request should return all the key-value pairs in the server’s dictionary. Its implementation looks like this:

void handle_get(http_request request)
{
   TRACE(L"\nhandle GET\n");

   json::value::field_map answer;

   for(auto const & p : dictionary)
   {
      answer.push_back(make_pair(json::value(p.first), json::value(p.second)));
   }

   request.reply(status_codes::OK, json::value::object(answer));
}

What it does is iterating through the dictionary and putting its key-value pairs into a json::value::field_map. That object is then sent back the the client.

The POST, PUT and DEL methods are a bit more complicated, because they all receive a JSON value specifying either keys to fetch or delete or pairs of key-value to add or update in the dictionary. Since some code would get duplicated several times I have created a generic method for handling requests that takes a function that evaluates the JSON request value and builds the response JSON value.

void handle_request(http_request request, 
                    function<void(json::value &, json::value::field_map &)> action)
{
   json::value::field_map answer;

   request
      .extract_json()
      .then([&answer, &action](pplx::task<json::value> task) {
         try
         {
            auto & jvalue = task.get();

            if (!jvalue.is_null())
            {
               action(jvalue, answer);
            }
         }
         catch (http_exception const & e)
         {
            wcout << e.what() << endl;
         }
      })
      .wait();

   request.reply(status_codes::OK, json::value::object(answer));
}

The handlers for POST, PUT and DEL will then call this generic method providing a lambda with the actual core implementation of each request handling.

void handle_post(http_request request)
{
   TRACE("\nhandle POST\n");

   handle_request(
      request, 
      [](json::value & jvalue, json::value::field_map & answer)
      {
         for (auto const & e : jvalue)
         {
            if (e.second.is_string())
            {
               auto key = e.second.as_string();

               auto pos = dictionary.find(key);
               if (pos == dictionary.end())
               {
                  answer.push_back(make_pair(json::value(key), json::value(L"<nil>")));
               }
               else
               {
                  answer.push_back(make_pair(json::value(pos->first), json::value(pos->second)));
               }
            }
         }
      }
   );
}

void handle_put(http_request request)
{
   TRACE("\nhandle PUT\n");

   handle_request(
      request,
      [](json::value & jvalue, json::value::field_map & answer)
      {
         for (auto const & e : jvalue)
         {
            if (e.first.is_string() && e.second.is_string())
            {
               auto key = e.first.as_string();
               auto value = e.second.as_string();

               if (dictionary.find(key) == dictionary.end())
               {
                  TRACE_ACTION(L"added", key, value);
                  answer.push_back(make_pair(json::value(key), json::value(L"<put>")));
               }
               else
               {
                  TRACE_ACTION(L"updated", key, value);
                  answer.push_back(make_pair(json::value(key), json::value(L"<updated>")));
               }

               dictionary[key] = value;
            }
         }
      }
   );
}

void handle_del(http_request request)
{
   TRACE("\nhandle DEL\n");

   handle_request(
      request,
      [](json::value & jvalue, json::value::field_map & answer)
      {
         set<utility::string_t> keys;
         for (auto const & e : jvalue)
         {
            if (e.second.is_string())
            {
               auto key = e.second.as_string();

               auto pos = dictionary.find(key);
               if (pos == dictionary.end())
               {
                  answer.push_back(make_pair(json::value(key), json::value(L"<failed>")));
               }
               else
               {
                  TRACE_ACTION(L"deleted", pos->first, pos->second);
                  answer.push_back(make_pair(json::value(key), json::value(L"<deleted>")));
                  keys.insert(key);
               }
            }
         }

         for (auto const & key : keys)
            dictionary.erase(key);
      }
   );
}

And that is all with the server.

The client implementation

On the client side we need a http_client object to make HTTP requests to the server. It has an overloaded method request() that allows specifying the request method, a path and a JSON value for instance. A JSON value is not sent if the method is GET (or HEAD). Since for each request the answer is a JSON value, I have created a method called make_request() that dispatches the request and when the response arrives it fetches the JSON value and displays it in the console.

The core of the client code looks like this:

#include <cpprest/http_client.h>
#include <cpprest/json.h>
#pragma comment(lib, "cpprest110_1_1")

using namespace web;
using namespace web::http;
using namespace web::http::client;

#include <iostream>
using namespace std;

void display_field_map_json(json::value & jvalue)
{
   if (!jvalue.is_null())
   {
      for (auto const & e : jvalue)
      {
         wcout << e.first.as_string() << L" : " << e.second.as_string() << endl;
      }
   }
}

pplx::task<http_response> make_task_request(http_client & client, 
                                            method mtd, 
                                            json::value const & jvalue)
{
   return (mtd == methods::GET || mtd == methods::HEAD) ? 
      client.request(mtd, L"/restdemo") : 
      client.request(mtd, L"/restdemo", jvalue);
}

void make_request(http_client & client, method mtd, json::value const & jvalue)
{
   make_task_request(client, mtd, jvalue)
      .then([](http_response response)
      {
         if (response.status_code() == status_codes::OK)
         {
            return response.extract_json();
         }
         return pplx::task_from_result(json::value());
      })
      .then([](pplx::task<json::value> previousTask)
      {
         try
         {
            display_field_map_json(previousTask.get());
         }
         catch (http_exception const & e)
         {
            wcout << e.what() << endl;
         }
      })
      .wait();
}

In the main() function I then just make a series of requests to the server, putting, fetching and deleting key-values from the server’s dictionary.

int main()
{
   http_client client(U("http://localhost"));

   json::value::field_map putvalue;
   putvalue.push_back(make_pair(json::value(L"one"), json::value(L"100")));
   putvalue.push_back(make_pair(json::value(L"two"), json::value(L"200")));

   wcout << L"\nput values\n";
   make_request(client, methods::PUT, json::value::object(putvalue));

   auto getvalue = json::value::array();
   getvalue[0] = json::value(L"one");
   getvalue[1] = json::value(L"two");
   getvalue[2] = json::value(L"three");

   wcout << L"\nget values (POST)\n";
   make_request(client, methods::POST, getvalue);

   auto delvalue = json::value::array();
   delvalue[0] = json::value(L"one");

   wcout << L"\ndelete values\n";
   make_request(client, methods::DEL, delvalue);

   wcout << L"\nget values (POST)\n";
   make_request(client, methods::POST, getvalue);

   wcout << L"\nget values (GET)\n";
   make_request(client, methods::GET, json::value::null());

   return 0;
}

The client and server in action

You need to start the server first and then run the client. The output from running the client is:

put values
one : <put>
two : <put>

get values (POST)
one : 100
two : 200
three : <nil>

delete values
one : <deleted>

get values (POST)
one : <nil>
two : 200
three : <nil>

get values (GET)
two : 200

On the server console the output is:

starting to listen

handle PUT
added (one, 100)
added (two, 200)

handle POST

handle DEL
deleted (one, 100)

handle POST

handle GET

34 Replies to “Full-fledged client-server example with C++ REST SDK 1.1.0”

  1. How to put the listener to listen on 0.0.0.0?

    like http_listener listener(L”http://0.0.0.0:9000/restdemo”);

    not work

  2. [quote]
    IP address 0.0.0.0 is used to designate an invalid address (usually used when a machine is not connected to a TCP/IP network). Why would you expect this to work?
    [/quote]

    Your joking right? There are literally hundreds of reasons why your wrong.

  3. Address 0.0.0.0 is a convention to designate ALL interfaces. In the use case posed by Steven, it would listen on all addresses including 127.0.0.1. This convention has been in use for decades for *nix operating systems..

    It would be helpful to adhere to a widely used convention..

  4. I can’t help but notice the busy wait in the server code. I’m referring to the while (true) statement?
    Why is it there? Any other way?

  5. Hi
    I have this error
    no callable ‘begin’ function found for type ‘web::json::value’

  6. can you share a working sln file solution for this example. did you build it in visual studio 2010? will it work in visual studio 2010? I copied the code and tried to compile there were numerous syntax errors. not to say the code is wrong but probably I don’t know how to set it up on platform.

  7. Hi,
    I am trying to connect the server with client on Android. For that I am using class HttpUrlConnection.
    I am posting data using OutputStreamWriter. But it is not working. Do you have any clues?

    Thanks,

    Abhijeet

  8. I am new to REST API.
    How can I build and test the mentioned application. Can I build it on visual studio 2013 ?

    I think I need to install the C++ SDK first before build.
    Could anybody direct me in getting the right C++ SDK.

    Thanks in advance,
    Praveen

  9. I am also getting the error
    no callable ‘begin’ function found for type ‘web::json::value’
    Could anyone help me. I am using Visual Studio 2013 with Nuget C++ rest sdk 2.7.0.

    Please please reply…its urgent.

  10. Is there any chance you could update your code to work with the current C++ REST SDK version 2.8?

    The JSON classes have changed.

  11. can i use the server implemented (methods) here as a web service by hosting it.
    If so, can someone tell me please, how to do ?

  12. There is no json::value::field_map type in the current stable version of cpprestsdk, so unfortunately I cannot build the example out of the box 🙁 It will be great if you adapt it to the current state of library code. Anyway, huge thanks for the tutorial!

  13. Hi,

    I want to access the C++ Rest Web Service given in this tutorial using Java Client.
    How can i do, can someone help in this regard.

  14. Hello Mario,

    This tutorial was very useful. Do you know any technique to reduce cpu operations? By what I saw, the while loop is consuming alot of cpu operations. Just another question, how the reset is seen by the market today?

    Thank you,
    Francisco

  15. “while(true);” is not good: it takes unnecessary CPU cycles. There should at least something like “Sleep/sleep” in the loop.

    During my test, I also changed the following:

    – `json::value::field_map` to `json::value`
    – `.push_back(make_pair(json::value(L”key”), json::value(L”value”))` to `[L”key”] = json::value(L”value”)`
    – `jvalue` to `jvalue.as_object()` when it is being iterated over

  16. Now, this server uses a different HTTP method for each API. Now, let’s say you have a trading terminal with a massive number of different APIs, how would this problem be solved ?

  17. Very useful, thanks!
    Could you please show how to add unit tests (may be using native c++ unit test framework) to verify these functions and the way to mock http_request/http_response?

  18. Instead of using a busy loop to wait, you should use a blocking wait for user input. That is, replace

    while (true);

    with

    std::string str;
    std::cin >> str;

    Otherwise you end up tying up tons of CPU time in the busy loop.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.