Using the server architecture to provide a REST interface. So I can send/recieve JSON that is put/retrieved from a mongo server.
The Fir class MongoRest is similar in style to the WebServer I just posted. The MongoServer class builds on some code I previously posted here to accesses a Mongo DB.
MongoRest.cpp (includes main)
#include "MongoServer.h"
#include "NisseServer/NisseServer.h"
#include "NisseServer/PyntControl.h"
#include "NisseHTTP/HTTPHandler.h"
#include "NisseHTTP/Request.h"
#include "NisseHTTP/Response.h"
#include <ThorsSocket/Server.h>
#include <filesystem>
namespace TASock = ThorsAnvil::ThorsSocket;
namespace TANS = ThorsAnvil::Nisse::Server;
namespace TANH = ThorsAnvil::Nisse::HTTP;
namespace MRest = ThorsAnvil::Nisse::Examples::MongoRest;
namespace FS = std::filesystem;
class MongoRest: public TANS::NisseServer
{
TANH::HTTPHandler http;
TANS::PyntControl control;
MRest::MongoServer& mongoServer;
TASock::ServerInit getServerInit(std::optional<FS::path> certPath, int port)
{
if (!certPath.has_value()) {
return TASock::ServerInfo{port};
}
TASock::CertificateInfo certificate{FS::canonical(FS::path(*certPath) /= "fullchain.pem"),
FS::canonical(FS::path(*certPath) /= "privkey.pem")
};
TASock::SSLctx ctx{TASock::SSLMethodType::Server, certificate};
return TASock::SServerInfo{port, std::move(ctx)};
}
public:
MongoRest(int poolSize, int port, std::optional<FS::path> certPath, MRest::MongoServer& ms)
: TANS::NisseServer(poolSize)
, control(*this)
, mongoServer(ms)
{
// CRUD Person Interface
http.addPath(TANH::Method::POST, "/person/", [&](TANH::Request& request, TANH::Response& response) {mongoServer.personCreate(request, response);});
http.addPath(TANH::Method::GET, "/person/Id-{id}", [&](TANH::Request& request, TANH::Response& response) {mongoServer.personGet(request, response);});
http.addPath(TANH::Method::PUT, "/person/Id-{id}", [&](TANH::Request& request, TANH::Response& response) {mongoServer.personUpdate(request, response);});
http.addPath(TANH::Method::DELETE, "/person/Id-{id}", [&](TANH::Request& request, TANH::Response& response) {mongoServer.personDelete(request, response);});
// Search Person Interface
http.addPath(TANH::Method::GET, "/person/findByName/{first}/{last}",[&](TANH::Request& request, TANH::Response& response) {mongoServer.personFindByName(request, response);});
http.addPath(TANH::Method::GET, "/person/findByTel/{tel}", [&](TANH::Request& request, TANH::Response& response) {mongoServer.personFindByTel(request, response);});
http.addPath(TANH::Method::GET, "/person/findByZip/{zip}", [&](TANH::Request& request, TANH::Response& response) {mongoServer.personFindByZip(request, response);});
listen(getServerInit(certPath, port), http);
listen(TASock::ServerInfo{port+2}, control);
}
};
int main(int argc, char* argv[])
{
if (argc != 6 && argc != 7)
{
std::cerr << "Usage: MongoRest <port> <MongoHost> <MongoUser> <MongoPass> <MongoDB> [<certificateDirectory>]\n";
return 1;
}
try
{
int port = std::stoi(argv[1]);
std::string mongoHost = argv[2];
std::string mongoUser = argv[3];
std::string mongoPass = argv[4];
std::string mongoDB = argv[5];
std::optional<FS::path> certPath;
if (argc == 7) {
certPath = FS::canonical(argv[6]);
}
static constexpr int poolSize = 6;
static constexpr int mongoConnectionCount = 12;
std::cout << "Nisse MongoRest: Port: " << port << " MongoHost: >" << mongoHost << "< Mongo User: >" << mongoUser << "< MongoPass: >" << mongoPass << "< MongoDB: >" << mongoDB << "< Certificate Path: >" << (argc == 6 ? "NONE" : argv[6]) << "<\n";
MRest::MongoServer mongoServer{mongoConnectionCount, mongoHost, 27017, mongoUser, mongoPass, mongoDB};
MongoRest server{poolSize, port, certPath, mongoServer};
server.run();
}
catch (std::exception const& e)
{
std::cerr << "Exception: " << e.what() << "\n";
throw;
}
catch (...)
{
std::cerr << "Exception: Unknown\n";
throw;
}
}
MongoServer.h
#ifndef THORSANVIL_NISSE_EXAMPLES_MONGO_SERVER_H
#define THORSANVIL_NISSE_EXAMPLES_MONGO_SERVER_H
#include "MongoConnectionPool.h"
#include "NisseHTTP/Request.h"
#include "NisseHTTP/Response.h"
#include "ThorsMongo/ThorsMongo.h"
namespace NHTTP = ThorsAnvil::Nisse::HTTP;
namespace TAMongo = ThorsAnvil::DB::Mongo;
namespace ThorsAnvil::Nisse::Examples::MongoRest
{
class MongoServer
{
MongoConnectionPool mongoPool;
public:
MongoServer(int poolSize, std::string const& host, int port, std::string const& user, std::string const& password, std::string const& db);
// CRUD
void personCreate(NHTTP::Request& request, NHTTP::Response& response);
void personGet(NHTTP::Request& request, NHTTP::Response& response);
void personUpdate(NHTTP::Request& request, NHTTP::Response& response);
void personDelete(NHTTP::Request& request, NHTTP::Response& response);
// FIND
void personFindByName(NHTTP::Request& request, NHTTP::Response& response);
void personFindByTel(NHTTP::Request& request, NHTTP::Response& response);
void personFindByZip(NHTTP::Request& request, NHTTP::Response& response);
private:
TAMongo::ObjectID getIdFromRequest(NHTTP::Request& request);
void requestFailed(NHTTP::Response& response, std::initializer_list<std::string> messages);
};
}
#endif
MongoServer.cpp
#include "MongoServer.h"
#include "ThorsMongo/MongoUtil.h"
#include "NisseServer/AsyncStream.h"
#include "ThorSerialize/Traits.h"
#include "ThorSerialize/JsonThor.h"
#include <ranges>
using namespace ThorsAnvil::Nisse::Examples::MongoRest;
namespace TAMongo = ThorsAnvil::DB::Mongo;
namespace TANS = ThorsAnvil::Nisse::Server;
namespace TAJson = ThorsAnvil::Serialize;
// Structures for data stored in the AddressBook collection of the DB.
struct Address
{
std::string street1;
std::string street2;
std::string city;
std::string state;
std::string zip;
};
struct Telephone
{
std::string type;
std::string number;
};
struct ContactInfo
{
Address address;
Telephone telephone;
};
struct Name
{
std::string first;
std::string last;
};
struct Person
{
TAMongo::ObjectID _id;
Name name;
int age;
ContactInfo contactInfo;
};
// Declarations for the Serializer code.
// This allows JSON and BSON to be coded and decoded into the above classes.
ThorsAnvil_MakeTrait(Address, street1, street2, city, state, zip);
ThorsAnvil_MakeTrait(Telephone, type, number);
ThorsAnvil_MakeTrait(ContactInfo, address, telephone);
ThorsAnvil_MakeTrait(Name, first, last);
ThorsAnvil_MakeTrait(Person, _id, name, age, contactInfo);
// Declarations to create Field accesses objects.
// The field access objects are used to create MONGO expressiones.
ThorsMongo_CreateFieldAccess(Person, _id);
ThorsMongo_CreateFieldAccess(Person, name, first);
ThorsMongo_CreateFieldAccess(Person, name, last);
ThorsMongo_CreateFieldAccess(Person, contactInfo, telephone, number);
ThorsMongo_CreateFieldAccess(Person, contactInfo, address, zip);
// Create Very Simple expressions to compare objects on the Mongo DB.
// All of these use the `Eq` class to compare for equality.
// They use the field access objects declared above.
using FindById = ThorsMongo_FilterFromAccess(Eq, Person, _id);
using FindByFName = ThorsMongo_FilterFromAccess(Eq, Person, name, first);
using FindByLName = ThorsMongo_FilterFromAccess(Eq, Person, name, last);
using FindByName = TAMongo::QueryOp::And<FindByFName, FindByLName>;
using FindByTel = ThorsMongo_FilterFromAccess(Eq, Person, contactInfo, telephone, number);
using FindByZip = ThorsMongo_FilterFromAccess(Eq, Person, contactInfo, address, zip);
void MongoServer::requestFailed(NHTTP::Response& response, std::initializer_list<std::string> messages)
{
response.setStatus(404);
NHTTP::HeaderResponse headers;
for (auto const& message: messages) {
headers.add("Error", message);
}
response.addHeaders(headers);
}
void requestStreamRange(NHTTP::Response& response, TAMongo::FindRange<Person>& result)
{
std::ostream& output = response.body(NHTTP::Encoding::Chunked);
output << '[';
std::string sep = "";
for (auto const& p: result)
{
output << sep << TAJson::jsonExporter(p);
sep = ", ";
}
utput << ']';
}
MongoServer::MongoServer(int poolSize, std::string const& host, int port, std::string const& user, std::string const& password, std::string const& db)
: mongoPool(poolSize, host, port, user, password, db)
{}
TAMongo::ObjectID MongoServer::getIdFromRequest(NHTTP::Request& request)
{
TAMongo::ObjectID result;
std::stringstream ss(std::move(request.variables()["id"]));
ss >> TAJson::jsonImporter(result);
return result;
}
void MongoServer::personCreate(NHTTP::Request& request, NHTTP::Response& response)
{
TAMongo::ThorsMongo& mongo = mongoPool.getConnection();
TANS::AsyncStream async(mongo.getStream().getSocket(), request.getContext(), TANS::EventType::Write);
Person person;
request.body() >> TAJson::jsonImporter(person);
auto result = mongo["test"]["AddressBook"].insert(std::tie(person));
if (!result) {
return requestFailed(response, {result.getHRErrorMessage()});
}
response.body(NHTTP::Encoding::Chunked) << TAJson::jsonExporter(result);
}
void MongoServer::personGet(NHTTP::Request& request, NHTTP::Response& response)
{
TAMongo::ThorsMongo& mongo = mongoPool.getConnection();
TANS::AsyncStream async(mongo.getStream().getSocket(), request.getContext(), TANS::EventType::Write);
TAMongo::ObjectID id = getIdFromRequest(request);
auto range = mongo["test"]["AddressBook"].find<Person>(FindById{id});
if (!range) {
return requestFailed(response, {range.getHRErrorMessage()});
}
for (auto const& person: range)
{
response.body(NHTTP::Encoding::Chunked) << TAJson::jsonExporter(person);
return;
}
// Only get here if there are no items in the range.
requestFailed(response, {"No Value Found"});
}
void MongoServer::personUpdate(NHTTP::Request& request, NHTTP::Response& response)
{
TAMongo::ThorsMongo& mongo = mongoPool.getConnection();
TANS::AsyncStream async(mongo.getStream().getSocket(), request.getContext(), TANS::EventType::Write);
TAMongo::ObjectID id = getIdFromRequest(request);
Person person;
request.body() >> TAJson::jsonImporter(person);
auto result = mongo["test"]["AddressBook"].findAndReplaceOne<Person>(FindById{id}, person);
if (!result) {
return requestFailed(response, {result.getHRErrorMessage()});
}
if (!result.value)
{
// No original value.
response.body(NHTTP::Encoding::Chunked) << "{}";
}
else
{
// This was the value we replaced.
response.body(NHTTP::Encoding::Chunked) << TAJson::jsonExporter(*result.value);
}
}
void MongoServer::personDelete(NHTTP::Request& request, NHTTP::Response& response)
{
TAMongo::ThorsMongo& mongo = mongoPool.getConnection();
TANS::AsyncStream async(mongo.getStream().getSocket(), request.getContext(), TANS::EventType::Write);
TAMongo::ObjectID id = getIdFromRequest(request);
auto result = mongo["test"]["AddressBook"].findAndRemoveOne<Person>(FindById{id});
if (!result) {
return requestFailed(response, {result.getHRErrorMessage()});
}
if (!result.value)
{
// No original value.
response.body(NHTTP::Encoding::Chunked) << "{}";
}
else
{
// This was the value we removed.
response.body(NHTTP::Encoding::Chunked) << TAJson::jsonExporter(*result.value);
}
}
void MongoServer::personFindByName(NHTTP::Request& request, NHTTP::Response& response)
{
TAMongo::ThorsMongo& mongo = mongoPool.getConnection();
TANS::AsyncStream async(mongo.getStream().getSocket(), request.getContext(), TANS::EventType::Write);
std::string first = request.variables()["first"];
std::string last = request.variables()["last"];
TAMongo::FindRange<Person> result;
if (first == "" && last == "")
{
response.setStatus(400);
}
if (first == "")
{
result = mongo["test"]["AddressBook"].find<Person>(FindByLName{last});
}
else if (last == "")
{
result = mongo["test"]["AddressBook"].find<Person>(FindByFName{first});
}
else
{
result = mongo["test"]["AddressBook"].find<Person>(FindByName{first, last});
}
if (!result)
{
return requestFailed(response, {result.getHRErrorMessage()});
}
requestStreamRange(response, result);
}
void MongoServer::personFindByTel(NHTTP::Request& request, NHTTP::Response& response)
{
TAMongo::ThorsMongo& mongo = mongoPool.getConnection();
TANS::AsyncStream async(mongo.getStream().getSocket(), request.getContext(), TANS::EventType::Write);
std::string tel = request.variables()["tel"];
auto result = mongo["test"]["AddressBook"].find<Person>(FindByTel{tel});
if (!result) {
return requestFailed(response, {result.getHRErrorMessage()});
}
requestStreamRange(response, result);
}
void MongoServer::personFindByZip(NHTTP::Request& request, NHTTP::Response& response)
{
TAMongo::ThorsMongo& mongo = mongoPool.getConnection();
TANS::AsyncStream async(mongo.getStream().getSocket(), request.getContext(), TANS::EventType::Write);
std::string tel = request.variables()["zip"];
auto result = mongo["test"]["AddressBook"].find<Person>(FindByZip{tel});
if (!result) {
return requestFailed(response, {result.getHRErrorMessage()});
}
requestStreamRange(response, result);
}