Implementing REST APIs in C++ with POCOPRO Remoting
Starting with the 2016.1 release of the POCOPRO frameworks a new feature has been added to the Remoting NG toolkit that makes it easy to implement RESTful web services in C++. Remoting NG is a distributed objects and web services framework for C++, based on annotated C++ class definitions and a code generator. Remoting NG enables distributed applications using high-level object-based inter-process communication, remote method invocation or web services based on JSON-RPC or SOAP. For example, a (simplistic) remote service for user account management (using the TCP-based transport protocol, JSON-RPC or SOAP) could be implemented as follows:
//@ serialize
struct User
{
std::string name;
Poco::Optional<std::string> password;
Poco::Optional<std::set<std::string>> permissions;
Poco::Optional<std::set<std::string>> roles;
};
//@ remote
class UserManager
{
public:
UserManager(/* ... */);
~UserManager();
void addUser(const User& user);
void updateUser(const User& user);
User getUser(const std::string& username);
void deleteUser(const std::string& username);
private:
// ...
};
Based on the above declarations, the Remoting NG code generator is used to generate generic code for serialization/deserialization and remote invocation (proxy, skeleton, etc.) that allows the UserManager class to become a remote service (based on the TCP transport) or web service using SOAP or JSON-RPC 2.0. To learn more about how everything works, please see the overview and tutorials (part 2, part 3) in the documentation. Starting with the 2016.1 release, Remoting NG can also be used to implement a REST web service. For this to work, the service class must be implemented in a different way. In fact, multiple classes will be used that cover different aspects of the web service according to REST principles. A typical implementation could consist of the following API endpoints:
Retrieving a list of users
This is achieved with a GET request to the users collection:
GET /api/1.0/users HTTP/1.1
Host: localhost:80
The response looks like:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: nnn
[
{
"name": "admin",
"permissions:
[
],
"roles":
[
"administrator"
]
},
{
...
}
]
Using query parameters, the result can be filtered or restricted:
GET /api/1.0/users?maxResults=10&start=10 HTTP/1.1
Host: localhost:80
Creating a user
This is achieved with a POST request to the users collection:
POST /api/1.0/users HTTP/1.1
Host: localhost:80
Content-Type: application/json
Content-Length: nnn
{
"name": "admin",
"password": "s3cr3t",
"permissions": [
],
"roles": [
"administrator"
]
}
Updating and deleting a user
We'd also like to be able to update and delete user accounts:
PATCH /api/1.0/users/admin HTTP/1.1
Host: localhost:80
Content-Type: application/json
Content-Length: nnn
{
"permissions": [
"somePermission"
]
}
DELETE /api/1.0/users/admin HTTP/1.1
Host: localhost:80
To keep things simple, we'll leave the supported operations at that. A real service could also add endpoints for nested objects such as permissions or roles. Implementing such a web service is straightforward with Remoting. We need to provide two C++ classes, one for handling the user collection, and the other for handling a single user account. The class declarations are shown in the following (we're using the User struct from above, but make the name field optional):
//@ serialize
struct User
{
Poco::Optional<std::string> name;
Poco::Optional<std::string> password;
Poco::Optional<std::set<std::string>> permissions;
Poco::Optional<std::set<std::string>> roles;
};
//@ remote
//@ path="/api/1.0/users"
class UserCollectionEndpoint
{
public:
UserCollectionEndpoint(/* ... */);
~UserCollectionEndpoint();
User post(const User& user);
/// Create a new user.
//@ $maxResults={in=query, mandatory=false}
//@ $start={in=query, mandatory=false}
std::vector get(int maxResults = 0, int start = 0);
/// Return a list of user objects, starting with
/// the given start index, and return at most
/// maxResults items.
private:
// ...
};
//@ remote
//@ path="/api/1.0/users/{name}"
class UserEndpoint
{
public:
UserEndpoint(/* ... */);
~UserEndpoint();
//@ $name={in=path}
User put(const std::string& name, const User& user);
/// Update a user (calls patch()).
//@ $name={in=path}
User patch(const std::string& name, const User& user);
/// Update a user.
//@ $name={in=path}
User get(const std::string& name);
/// Retrieve a user by name.
//@ $name={in=path}
void delete_(const std::string& name);
/// Delete a user.
private:
// ...
};
What can be seen in the above code samples is that we only have to implement member functions for the HTTP methods (GET, POST, PUT, PATCH, DELETE) that out API supports. We don't have to deal with the details of parsing or creating JSON, or handling request URIs and query strings, or the intricacies of HTTP requests. The Remoting REST framework does all these things automatically, using code generated by the Remoting code generator, based on the annotations in the header files. For example, for the UserCollectionEndpoint::post() method, the parameter is automatically serialized and deserialized to/from JSON, whereas for UserEndpoint::put(), the name of the user is extracted from the request path (note the placeholder {name} in the path annotation). Parameters passed in a query can also be handled, as seen in UserCollectionEndpoint::get(). Parameters can also be passed in HTTP request/response headers or in a form. Furthermore, HTTP authentication (basic, digest) is supported, as well as HTTPS.
As can be seen, implementing REST web service in C++ really becomes easy, given a powerful toolkit like the POCOPRO Remoting. For more information, please refer to the documentation. To try it out yourself, get a free evaluation version for Windows, Linux or OS X.
(Post updated 2016-09-02)