Friday, October 16, 2020

C++ REST SDK

C++ REST SDK is for cloud based client server communication using C++ API design [1].




Setting up

For Windows machines, clone vcpkg as follows.

$ git clone https://github.com/Microsoft/vcpkg.git
$ cd vcpkg


Then, open command window as admin and enter

> bootstrap-vcpkg.bat
> vcpkg integrate install
> vcpkg install cpprestsdk cpprestsdk:x64-windows


And for Debian based Linux machines, you can use apt to install cpprestsdk.

$ sudo apt install libcpprest-dev


On some systems, you might need to install the following packages before installing cpprestsdk.

$ sudo apt -y install libboost-atomic-dev libboost-thread-dev libboost-system-dev  
$ sudo apt -y install libboost-date-time-dev libboost-regex-dev 
$ sudo apt -y install libboost-filesystem-dev libboost-random-dev libboost-chrono-dev 
$ sudo apt -y libboost-serialization-dev libwebsocketpp-dev 
$ sudo apt -y install openssl libssl-dev ninja-build g++ git


If you want to build from source, you can build as follows.

$ unzip casablanca.zip
$ cd casablanca
$ mkdir -p build
$ cd build
$ cmake -G Ninja .. -DCMAKE_BUILD_TYPE=Release -DCPPREST_EXCLUDE_WEBSOCKETS=1
$ ninja
$ sudo ninja install
$ sudo ldconfig


Simple GET Client

Let us start building a simple program, getClient.cpp, which is listed below [2, 3]. See comments for code explanation.

// File: getClient.cpp
// Author: Yan Naing Aye

#include <iostream>
#include <cpprest/http_client.h>
#include <cpprest/uri.h>
#include <cpprest/json.h>

using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
using namespace std;
int main(int argc, char* argv[])
{
	// http client
	web::http::client::http_client client(U("https://yan9a.github.io"));

	// the pplx task 'resp' gives a placeholder for a value 
	// that will be available in the future
	pplx::task<web::http::http_response> resp = client.request(
		web::http::methods::GET, U("/rpi/iot/cpprest/getClient/j1.json"));

	// attached a handler to be invoked when resp.is_done() is true
	pplx::task<json::value> jv = resp.then([=]
	(pplx::task<web::http::http_response> task) {
		web::http::http_response response = task.get();

		// Check the status code.
		if (response.status_code() != 200) {
			throw std::runtime_error("Returned " + 
				std::to_string(response.status_code()));
		}
		std::cout << "Returned " + 
			std::to_string(response.status_code())<<endl;

		// Convert the response body to JSON object.
		pplx::task<json::value> jsonObject = response.extract_json();
		return jsonObject;
	});

	// handler to be invoked when json has been extracted
	jv.then([=](json::value jo){
		cout<<"Val:"<<jo.serialize() << endl;
	});

	// Wait until json value is ready
	try {
		jv.wait();
	}
	catch (const std::exception & e) {
		printf("Error exception:%s\n", e.what());
	}
	return 0;
}


For Windows machines using Visual Studio, the library is automatically usable if you have already run "vcpkg integrate install" in the previous step. For Linux machines, make the CMakeLists.txt as follows.

cmake_minimum_required(VERSION 3.9)
project(getClient)

# for RPi
# set ( CMAKE_PREFIX_PATH /usr/lib/arm-linux-gnueabihf/cmake/ )

# for x86_64
# set ( CMAKE_PREFIX_PATH /usr/lib/x86_64-linux-gnu/cmake/)

# find_package(Boost COMPONENTS filesystem system REQUIRED)

find_package(cpprestsdk REQUIRED)
add_executable(getClient getClient.cpp)
target_link_libraries(getClient 
 cpprestsdk::cpprest 
 # ${Boost_SYSTEM_LIBRARY}
 )



Then, you can make a build folder, build the program and run as shown below.

mkdir -p build
cd build
cmake ..
make
./getClient


POST Client

A simple POST client postClient.cpp is shown below.

// File: postClient.cpp
// Author: Yan Naing Aye

#include <iostream>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
#include <cpprest/uri.h>
#include <cpprest/json.h>

using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
using namespace std;

int main(int argc, char* argv[])
{
	json::value jsonObject;
	jsonObject[U("apple")] = json::value::string(U("A"));
	jsonObject[U("banana")] = json::value::string(U("B"));

	// Make a POST request.
	auto requestJson = http_client(U("http://localhost:80"))
		.request(methods::POST, uri_builder(U("/j2.php")).to_string(), 
	// auto requestJson = http_client(U("http://localhost:8080"))
		// .request(methods::POST, uri_builder(U("/cpprest/svrRest")).to_string(),
			jsonObject.serialize(), U("application/json"))
		// Get the response.
		.then([](http_response response) {
				// Check the status code.
				if (response.status_code() != 200) {
					throw std::runtime_error("Returned " + 
					  std::to_string(response.status_code()));
				}
				std::cout << "Returned " + 
					std::to_string(response.status_code())<<endl;
				// Convert the response body to JSON object.
				return response.extract_json();
		})
		// Get the data field.
		.then([](json::value jsonObject) {
            cout<<"Val:"<<jsonObject.serialize() << endl;
			return jsonObject;
		});

		// Wait for the concurrent tasks to finish.
		try {
			requestJson.wait();
		}
		catch (const std::exception & e) {
			printf("Error exception:%s\n", e.what());
		}
	return 0;
}
For the server side, we can create a simple PHP page, j2.php to serve the request from the POST client which is shown below.

<?php
$json = file_get_contents('php://input');
// echo $json."\n";
$data = json_decode($json);
$data->cherry = $data->apple.$data->banana;
header('Content-Type: application/json');
echo json_encode($data);
// curl -X POST "http://localhost/j2.php"
//    -H "accept: */*" -H "Content-Type: application/json"
//    -d "{\"apple\":\"A\",\"banana\":\"B\"}"
?>


After putting j2.php in the home directory of the web server, you can test it with the following command.

curl -X POST "http://localhost/j2.php" \
 -H "accept: */*" -H "Content-Type: application/json" \
 -d "{\"apple\":\"A\",\"banana\":\"B\"}"


As illustrated in the following figure, you will get the reply with added data in the JSON object.


Figure . Post JSON test.


Now, we can use the following CMakeLists.txt, build, and run the postClient program.

cmake_minimum_required(VERSION 3.9)
project(postClient)

# for RPi
# set ( CMAKE_PREFIX_PATH /usr/lib/arm-linux-gnueabihf/cmake/ )

# for x86_64
# set ( CMAKE_PREFIX_PATH /usr/lib/x86_64-linux-gnu/cmake/)

# find_package(Boost COMPONENTS filesystem system REQUIRED)

find_package(cpprestsdk REQUIRED)
add_executable(postClient postClient.cpp)
target_link_libraries(postClient 
 cpprestsdk::cpprest 
 # ${Boost_SYSTEM_LIBRARY}
 )




mkdir -p build
cd build
cmake ..
make
./postClient


CppREST Server

Let us develop a cpprestsdk server side application that can handle four methods - GET, POST, PUT, and DELETE. A very simple example implementation for rest server is shown below [4].

// File: svrRest.cpp
// Author: Yan Naing Aye

#include <iostream>
#include <cpprest/http_listener.h>
#include <cpprest/filestream.h>
#include <cpprest/uri.h>
#include <cpprest/json.h>

using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::experimental::listener; // HTTP listener
using namespace concurrency::streams; // Asynchronous streams
using namespace std;

void handle_get(http_request message){
	cout<<"Handle get: "<<message.to_string()<<endl;
	json::value jsonObject;
	jsonObject[U("apple")] = json::value::string(U("A"));
	jsonObject[U("banana")] = json::value::string(U("B"));
	message.reply(status_codes::OK,jsonObject);
}

void handle_post(http_request message){
	cout<<"Handle post: "<<message.to_string()<<endl;
	json::value jsonObject;
	try{
		message.extract_json()
			.then([&jsonObject](json::value jo){
				cout<<"Val:"<<jo.serialize() << endl;
				jsonObject = jo;
			})
			.wait();
	}
	catch (const std::exception & e) {
		printf("Error exception:%s\n", e.what());
	}
	jsonObject[U("cherry")] = json::value::string(U("C"));
	message.reply(status_codes::OK,jsonObject);
}

void handle_put(http_request message){
	cout<<"Handle post: "<<message.to_string()<<endl;
	string rep = U("PUT handled");
	message.reply(status_codes::OK,rep);
}

void handle_del(http_request message){
	cout<<"Handle delete: "<<message.to_string()<<endl;
	string rep = U("DELETE handled");
	message.reply(status_codes::OK,rep);
}

int main(int argc, char* argv[])
{
	web::http::experimental::listener::http_listener 
		listener(U("http://localhost:8080/cpprest/svrRest"));
	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](){printf("\nStarting svrRest\n");})
			.wait();
		while(true);
	}
	catch (const std::exception & e) {
		printf("Error exception:%s\n", e.what());
	}
	return 0;
}


Similarly, we can use the following CMakeLists.txt, build, and run the svrRest.cpp.

cmake_minimum_required(VERSION 3.9)
project(svrRest)

# for RPi
# set ( CMAKE_PREFIX_PATH /usr/lib/arm-linux-gnueabihf/cmake/ )

# for x86_64
# set ( CMAKE_PREFIX_PATH /usr/lib/x86_64-linux-gnu/cmake/)

# find_package(Boost COMPONENTS filesystem system REQUIRED)

find_package(cpprestsdk REQUIRED)
add_executable(svrRest svrRest.cpp)
target_link_libraries(svrRest
 cpprestsdk::cpprest 
 # ${Boost_SYSTEM_LIBRARY}
 )




mkdir -p build
cd build
cmake ..
make
./svrRest


We can use previous postClient.cpp by changing the url to test the server. Alternatively, we can use the following command to send post request to the server and check the response.

curl -X POST "http://localhost:8080/cpprest/svrRest"  -H "accept: */*" \
-H "Content-Type: application/json"  -d "{\"apple\":\"A\",\"banana\":\"B\"}"


Similarly, we can use getClient.cpp or the following command to test get request.

curl -X GET "http://localhost:8080/cpprest/svrRest"  -H "accept: */*" 


GUI Multithreaded Examples

In this section, I will demonstrate some examples for cpprestsdk using multithreading and GUI. Example GUI client program GUI_Client.cpp can be found at

https://github.com/yan9a/rpi/blob/master/iot/cpprest/GUI_Client/GUI_Client.cpp.


Figure . GUI of the REST client example.


You can select the HTTP request method whether it is GET or POST using the dropdown list. There are also text input boxes to set host name, path and content to submit. It creates a separate thread for each request.

You can also find another example for multithreaded C++ REST server at

https://github.com/yan9a/rpi/blob/master/iot/cpprest/svrRestThread/svrRestThread.cpp.

It uses multiple threads for listeners, and communicates using thread safe events.

References

[1] cpprestsdk. C++ REST SDK. 2020.
url: https://github.com/Microsoft/cpprestsdk.

[2] Atakan SARIOGLU. Quick Start Your REST Client with CppREST. 2019-Nov-08.
url: http://www.atakansarioglu.com/easy-quick-start-cplusplus-rest-client-example-cpprest-tutorial/.

[3] Alexander Karatarakis. Programming with Tasks. 2015-Nov-03.
url: https://github.com/Microsoft/cpprestsdk/wiki/Programming-with-Tasks.

[4] Meenapintu. Restweb. 2019-Mar-17.
url: https://github.com/Meenapintu/Restweb.

No comments:

Post a Comment

Comments are moderated and don't be surprised if your comment does not appear promptly.