1
\$\begingroup\$

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);
}
\$\endgroup\$

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.