Created
August 29, 2018 05:25
-
-
Save tvhung83/851db220f26913b597327ed72e65af43 to your computer and use it in GitHub Desktop.
C++ REST + Postgres
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Created by Robert Nguyen on 10/28/17. | |
// | |
#include <algorithm> | |
#include <pistache/http.h> | |
#include <pistache/router.h> | |
#include <pistache/endpoint.h> | |
#include <pistache/serializer/rapidjson.h> | |
#include <tao/postgres/connection_pool.hpp> | |
using namespace std; | |
using namespace Pistache; | |
using namespace rapidjson; | |
void printCookies(const Http::Request& req) { | |
auto cookies = req.cookies(); | |
std::cout << "Cookies: [" << std::endl; | |
const std::string indent(4, ' '); | |
for (const auto& c: cookies) { | |
std::cout << indent << c.name << " = " << c.value << std::endl; | |
} | |
std::cout << "]" << std::endl; | |
} | |
class Movie { | |
public: | |
Movie(unsigned int id, unsigned int mid, const std::string &name, const std::string &title, const std::string &description, | |
const std::string &poster, const std::string &backdrop, const std::string &link, int episode, int sequence, int season_index, | |
int current_episode, const std::string &trailer) : id(id), mid(mid), name(name), title(title), | |
description(description), poster(poster), backdrop(backdrop), | |
link(link), episode(episode), sequence(sequence), | |
season_index(season_index), current_episode(current_episode), | |
trailer(trailer) {} | |
virtual ~Movie() { | |
} | |
template <typename Writer> | |
void Serialize(Writer& writer) const { | |
writer.StartObject(); | |
writer.String("id"); writer.Uint(id); | |
writer.String("mid"); writer.Uint(mid); | |
writer.String("name"); write(writer, name); | |
writer.String("title"); write(writer, title); | |
writer.String("description"); write(writer, description); | |
writer.String("poster"); write(writer, poster); | |
writer.String("backdrop"); write(writer, backdrop); | |
writer.String("link"); write(writer, link); | |
writer.String("episode"); writer.Int(episode); | |
writer.String("sequence"); writer.Int(sequence); | |
writer.String("season_index"); writer.Int(season_index); | |
writer.String("current_episode"); writer.Int(current_episode); | |
writer.String("trailer"); write(writer, trailer); | |
writer.EndObject(); | |
} | |
template <typename Writer> | |
void write(Writer& writer, string value) const { | |
#if RAPIDJSON_HAS_STDSTRING | |
writer.String(value); | |
#else | |
writer.String(value.c_str(), static_cast<SizeType>(value.length())); | |
#endif | |
} | |
private: | |
unsigned id; | |
unsigned mid; | |
std::string name; | |
std::string title; | |
std::string description; | |
std::string poster; | |
std::string backdrop; | |
std::string link; | |
int episode; | |
int sequence; | |
int season_index; | |
int current_episode; | |
std::string trailer; | |
}; | |
namespace Generic { | |
void handleReady(const Rest::Request&, Http::ResponseWriter response) { | |
StringBuffer JSONStrBuffer; | |
Writer<StringBuffer> writer(JSONStrBuffer); | |
writer.StartArray(); | |
const auto pool = tao::postgres::connection_pool::create( "user=foo password=bar dbname=fim" ); | |
const auto conn = pool->connection(); | |
const auto res = conn->execute( "SELECT * FROM hdviet.movies" ); | |
for( const auto& row : res ) { | |
const Movie movie( | |
row[ 0 ].as< unsigned >(), | |
row[ 1 ].as< unsigned >(), | |
row[ 2 ].as< string >(), | |
row[ 3 ].as< string >(), | |
row[ 4 ].as< string >(), | |
row[ 5 ].as< string >(), | |
row[ 6 ].as< string >(), | |
row[ 7 ].as< string >(), | |
row[ 8 ].as< int >(), | |
row[ 9 ].as< int >(), | |
row[ 10 ].as< int >(), | |
row[ 11 ].as< int >(), | |
row[ 12 ].as< string >() | |
); | |
movie.Serialize(writer); | |
} | |
writer.EndArray(); | |
auto mediaType = MIME(Application, Json); | |
mediaType.setParam("charset", "utf-8"); | |
response.headers() | |
.add<Pistache::Http::Header::ContentType>(mediaType); | |
response.send(Http::Code::Ok, JSONStrBuffer.GetString()); | |
} | |
} | |
class StatsEndpoint { | |
public: | |
StatsEndpoint(Address addr) | |
: httpEndpoint(std::make_shared<Http::Endpoint>(addr)) | |
{ } | |
void init(size_t thr = 2) { | |
auto opts = Http::Endpoint::options() | |
.threads(thr) | |
.flags(Tcp::Options::InstallSignalHandler); | |
httpEndpoint->init(opts); | |
setupRoutes(); | |
} | |
void start() { | |
httpEndpoint->setHandler(router.handler()); | |
httpEndpoint->serve(); | |
} | |
void shutdown() { | |
httpEndpoint->shutdown(); | |
} | |
private: | |
void setupRoutes() { | |
using namespace Rest; | |
Routes::Post(router, "/record/:name/:value?", Routes::bind(&StatsEndpoint::doRecordMetric, this)); | |
Routes::Get(router, "/value/:name", Routes::bind(&StatsEndpoint::doGetMetric, this)); | |
Routes::Get(router, "/api/ready", Routes::bind(&Generic::handleReady)); | |
Routes::Get(router, "/auth", Routes::bind(&StatsEndpoint::doAuth, this)); | |
} | |
void doRecordMetric(const Rest::Request& request, Http::ResponseWriter response) { | |
auto name = request.param(":name").as<std::string>(); | |
Guard guard(metricsLock); | |
auto it = std::find_if(metrics.begin(), metrics.end(), [&](const Metric& metric) { | |
return metric.name() == name; | |
}); | |
int val = 1; | |
if (request.hasParam(":value")) { | |
auto value = request.param(":value"); | |
val = value.as<int>(); | |
} | |
if (it == std::end(metrics)) { | |
metrics.push_back(Metric(std::move(name), val)); | |
response.send(Http::Code::Created, std::to_string(val)); | |
} | |
else { | |
auto &metric = *it; | |
metric.incr(val); | |
response.send(Http::Code::Ok, std::to_string(metric.value())); | |
} | |
} | |
void doGetMetric(const Rest::Request& request, Http::ResponseWriter response) { | |
auto name = request.param(":name").as<std::string>(); | |
Guard guard(metricsLock); | |
auto it = std::find_if(metrics.begin(), metrics.end(), [&](const Metric& metric) { | |
return metric.name() == name; | |
}); | |
if (it == std::end(metrics)) { | |
response.send(Http::Code::Not_Found, "Metric does not exist"); | |
} else { | |
const auto& metric = *it; | |
response.send(Http::Code::Ok, std::to_string(metric.value())); | |
} | |
} | |
void doAuth(const Rest::Request& request, Http::ResponseWriter response) { | |
printCookies(request); | |
response.cookies() | |
.add(Http::Cookie("lang", "en-US")); | |
response.send(Http::Code::Ok); | |
} | |
class Metric { | |
public: | |
Metric(std::string name, int initialValue = 1) | |
: name_(std::move(name)) | |
, value_(initialValue) | |
{ } | |
int incr(int n = 1) { | |
int old = value_; | |
value_ += n; | |
return old; | |
} | |
int value() const { | |
return value_; | |
} | |
std::string name() const { | |
return name_; | |
} | |
private: | |
std::string name_; | |
int value_; | |
}; | |
typedef std::mutex Lock; | |
typedef std::lock_guard<Lock> Guard; | |
Lock metricsLock; | |
std::vector<Metric> metrics; | |
std::shared_ptr<Http::Endpoint> httpEndpoint; | |
Rest::Router router; | |
}; | |
int main(int argc, char *argv[]) { | |
Port port(9080); | |
int thr = 2; | |
if (argc >= 2) { | |
port = std::stoi(argv[1]); | |
if (argc == 3) | |
thr = std::stoi(argv[2]); | |
} | |
Address addr(Ipv4::any(), port); | |
cout << "Cores = " << hardware_concurrency() << endl; | |
cout << "Using " << thr << " threads" << endl; | |
StatsEndpoint stats(addr); | |
stats.init(thr); | |
stats.start(); | |
stats.shutdown(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment