I have a web application I created that has a backend written in C++ with its own built-in web server. I want to deploy it behind Apache's reverse proxy (or as an extension to the Apache web server (would that be different?), so I want to know how to do it correctly considering my situation.
I read the Reverse Proxy Guide article on the Apache website, but there are still confusions that I have. For instance, do I really need to add a balancer set when I really only have one web server I need to do this for? And I need an email address for the server that I know I'll be able to get emails on, but I don't have a host name aside from the one I'm setting up a virtual host for (and that host name doesn't exist outside of being a virtual host). I'm confident only in using the same email address as the one for this Outlook account. I also need to know where to add the ProxyPass directive if I do have to set up a reverse proxy. And do I also need the ProxyPassReverse directive along with that? In my C++ source file, I have two environment variables for the API keys that it needs to work correctly; since it's a Google Maps application with a currency conversion form on it, it needs a Google Maps API Key and an Access Key for the currency API. On lines 130 and 133, I use std::getenv() to get the values in the environment variables. So also need to know how to make that work in Apache. I read about the environment variable directive in the httpd.conf file, but I also read that it's not the same thing as the OS-specific environment variables that I'm using. The files are attached to this message, along with httpd.conf, httpd-vhosts.conf and proxy-html.conf. I'm linking to the Gist with the JavaScript code here, since attaching it doesn't seem to work well (there's a red circle with a slash appearing on the file): https://gist.github.com/DragonOsman/c6e8fb15343544e662f474c5a526d1c2 . Just so you guys know, I'm using this on my own laptop PC. I don't like how I'll have to keep it on at all times when the application is online, but have no other way to do this as of right now.
#map {
height: 100%;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
form {
text-align: center;
}
#search-input {
white-space: nowrap;
}
#search-input {
background-color: #fff;
font-family: Roboto;
font-size: 15px;
font-weight: 300;
margin-left: 12px;
padding: 0 11px 0 13px;
text-overflow: ellipsis;
width: 400px;
}
#search-input:focus {
border-color: #4d90fe;
}
Title: DragonOsman Currency Converter

// Osman Zakir
// 3 / 16 / 2018
// Google Maps GUI + Currency Converter Web Application
// This application uses a Google Map as its UI. The user's geolocation is
taken if the browser has permission, and an info window
// is opened at that location which displays a HTML form. The form has an
input element to type in the amount of money in the base
// currency to convert, two dropdown menus populated with a list of currencies
requested from the currency API, and a button to submit
// the form. By default, the base currency is USD and the resulting currency is
the currency used at the place that the info window is
// opened on.
// Google's Geocoder and Reverse Geocoding Service returns status
"ZERO_RESULTS" for Western Sahara, Wake Island, and Kosovo. Both dropdown
// menus switch to AED in that situation. The status means that there are no
results to show even though reverse geocodng did work.
// This C++ application is the web server for the application. It acts as both
a servera and a client, as it also has to query the currency API,
// currencylayer.com, on its currency conversion endpoint and get the
conversion result to return to the front-end code. It also holds two
// environment variables, one to hold the Google Maps API Key and the other to
hold the currencylayer.com API access key.
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/connect.hpp>
#include <cstdlib>
#include <map>
#include <cctype>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <nlohmann/json.hpp>
#include <jinja2cpp/template.h>
#include <jinja2cpp/value.h>
#include <jinja2cpp/template_env.h.h>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
using nlohmann::json; // from
<nlohmann/json.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
//------------------------------------------------------------------------------
// Function to return a reasonable mime type based on the extension of a file.
boost::beast::string_view mime_type(boost::beast::string_view path);
// This class represents a cache for storing results from the
// currency exchange API used by currencylayer.com
class cache_storage
{
public:
cache_storage(const std::chrono::seconds &duration)
: m_cache{}, m_duration{ duration }
{
}
// This function queries the currency API after making sure
// that the stored result(s) is/are old enough
// It also makes a new query to the API if needed
const json &query(const std::map<std::string, std::string> &query_data,
const char *accesskey);
private:
std::map<const std::map<std::string, std::string>,
std::pair<std::chrono::time_point<std::chrono::steady_clock>, json>> m_cache;
std::chrono::seconds m_duration;
};
// Parse POST body
std::map<std::string, std::string> parse(const std::string &data);
// Perform currency conversion
double convert(const std::string &from_currency, std::string &to_currency,
const double money_amount, const char *accesskey);
// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string path_cat(boost::beast::string_view base, boost::beast::string_view
path);
// This function produces an HTTP response for the given
// request. The type of the response object depends on the
// contents of the request, so the interface requires the
// caller to pass a generic lambda for receiving the response.
template<class Body, class Allocator, class Send>
void handle_request(boost::beast::string_view doc_root, http::request<Body,
http::basic_fields<Allocator>> &&req,
Send &&send, const char *accesskey, const char *apikey);
//------------------------------------------------------------------------------
// Report a failure
void fail(boost::system::error_code ec, const char *what);
// This is the C++11 equivalent of a generic lambda.
// The function object is used to send an HTTP message.
template<class Stream>
struct send_lambda
{
Stream &stream_;
bool &close_;
boost::system::error_code &ec_;
explicit
send_lambda(Stream &stream, bool &close,
boost::system::error_code &ec)
: stream_{ stream }, close_{ close }, ec_{ ec }
{
}
template<bool isRequest, class Body, class Fields>
void operator()(http::message<isRequest, Body, Fields> &&msg) const;
};
// Handles an HTTP server connection
void do_session(tcp::socket socket, const std::string &doc_root, const char
*accesskey, const char *apikey);
//------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 4)
{
std::cerr <<
"Usage: currency_converter <address> <port>
<doc_root>\n" <<
"Example:\n" <<
" currency_converter 0.0.0.0 8080 .\n";
return EXIT_FAILURE;
}
const auto address = boost::asio::ip::make_address(argv[1]);
const auto port = static_cast<unsigned
short>(std::atoi(argv[2]));
const std::string doc_root = argv[3];
// The io_context is required for all I/O
boost::asio::io_context ioc{ 1 };
// Google API Key
char *apikey = std::getenv("apikey");
// Access key for currencylayer.com's currency exchange API
char *accesskey = std::getenv("accesskey");
// The acceptor receives incoming connections
tcp::acceptor acceptor{ ioc, { address, port } };
std::cout << "Starting server at " << address << ':' << port <<
"...\n";
for (;;)
{
// This will receive the new connection
tcp::socket socket{ ioc };
// Block until we get a connection
acceptor.accept(socket);
// Launch the session, transferring ownership of the
socket
std::thread([=, socket = std::move(socket)]() mutable {
do_session(std::move(socket), doc_root,
accesskey, apikey);
}).detach();
}
}
catch (const std::runtime_error &e)
{
std::cerr << "Line 154: Error: " << e.what() << '\n';
return EXIT_FAILURE;
}
catch (const std::exception &e)
{
std::cerr << "Line 159: Error: " << e.what() << '\n';
return EXIT_FAILURE + 1;
}
}
// Function to return a reasonable mime type based on the extension of a file.
boost::beast::string_view mime_type(boost::beast::string_view path)
{
using boost::beast::iequals;
const auto ext = [&path]
{
const auto pos = path.rfind(".");
if (pos == boost::beast::string_view::npos)
{
return boost::beast::string_view{};
}
return path.substr(pos);
}();
if (iequals(ext, ".htm"))
{
return "text/html";
}
if (iequals(ext, ".html"))
{
return "text/html";
}
if (iequals(ext, ".php"))
{
return "text/html";
}
if (iequals(ext, ".css"))
{
return "text/css";
}
if (iequals(ext, ".txt"))
{
return "text/plain";
}
if (iequals(ext, ".js"))
{
return "application/javascript";
}
if (iequals(ext, ".json"))
{
return "application/json";
}
if (iequals(ext, ".xml"))
{
return "application/xml";
}
if (iequals(ext, ".swf"))
{
return "application/x-shockwave-flash";
}
if (iequals(ext, ".flv"))
{
return "video/x-flv";
}
if (iequals(ext, ".png"))
{
return "image/png";
}
if (iequals(ext, ".jpe"))
{
return "image/jpeg";
}
if (iequals(ext, ".jpeg"))
{
return "image/jpeg";
}
if (iequals(ext, ".jpg"))
{
return "image/jpeg";
}
if (iequals(ext, ".gif"))
{
return "image/gif";
}
if (iequals(ext, ".bmp"))
{
return "image/bmp";
}
if (iequals(ext, ".ico"))
{
return "image/vnd.microsoft.icon";
}
if (iequals(ext, ".tiff"))
{
return "image/tiff";
}
if (iequals(ext, ".tif"))
{
return "image/tiff";
}
if (iequals(ext, ".svg"))
{
return "image/svg+xml";
}
if (iequals(ext, ".svgz"))
{
return "image/svg+xml";
}
return "application/text";
}
// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string path_cat(boost::beast::string_view base, boost::beast::string_view
path)
{
if (base.empty())
{
return path.to_string();
}
std::string result = base.to_string();
#if BOOST_MSVC
constexpr char path_separator = '\\';
if (result.back() == path_separator)
{
result.resize(result.size() - 1);
}
result.append(path.data(), path.size());
for (auto &c : result)
{
if (c == '/')
{
c = path_separator;
}
}
#else
constexpr char path_separator = '/';
if (result.back() == path_separator)
{
result.resize(result.size() - 1);
}
result.append(path.data(), path.size());
#endif
return result;
}
// Parse POST body
// Function uses state machine to parse POST body. Newlines
// and spaces are ignored. If it sees a quote, it'll read the next
// stuff until it encounters a space into the value string. The values
// are added to the parsed_values vector and that vector is returned back
std::map<std::string, std::string> parse(const std::string &data)
{
enum class States
{
Start,
Name,
Ignore,
Value
};
std::map<std::string, std::string> parsed_values;
std::string name;
std::string value;
States state = States::Start;
for (char c : data)
{
switch (state)
{
case States::Start:
if (c == '"')
{
state = States::Name;
}
break;
case States::Name:
if (c != '"')
{
name += c;
}
else
{
state = States::Ignore;
}
break;
case States::Ignore:
if (!isspace(c))
{
state = States::Value;
value += c;
}
break;
case States::Value:
if (c != '\n')
{
value += c;
}
else
{
parsed_values.insert(std::make_pair(name,
value));
name = "";
value = "";
state = States::Start;
}
break;
}
}
return parsed_values;
}
// This function produces an HTTP response for the given
// request. The type of the response object depends on the
// contents of the request, so the interface requires the
// caller to pass a generic lambda for receiving the response.
template<class Body, class Allocator, class Send>
void handle_request(boost::beast::string_view doc_root, http::request<Body,
http::basic_fields<Allocator>> &&req,
Send &&send, const char *accesskey, const char *apikey)
{
// Returns a bad request response
const auto bad_request = [&req](boost::beast::string_view why)
{
http::response<http::string_body> res{
http::status::bad_request, req.version() };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = why.to_string();
res.prepare_payload();
return res;
};
// Returns a not found response
const auto not_found = [&req](boost::beast::string_view target)
{
http::response<http::string_body> res{ http::status::not_found,
req.version() };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = "The resource '" + target.to_string() + "' was not
found.";
res.prepare_payload();
return res;
};
// Returns a server error response
const auto server_error = [&req](boost::beast::string_view what)
{
http::response<http::string_body> res{
http::status::internal_server_error, req.version() };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = "An error occurred: '" + what.to_string() + "'";
res.prepare_payload();
return res;
};
// Make sure we can handle the method
if (req.method() != http::verb::get &&
req.method() != http::verb::head &&
req.method() != http::verb::post)
{
return send(bad_request("Unknown HTTP-method"));
}
// Request path must be absolute and not contain "..".
if (req.target().empty() ||
req.target()[0] != '/' ||
req.target().find("..") != boost::beast::string_view::npos)
{
return send(bad_request("Illegal request-target"));
}
// Build the path to the requested file
std::string path;
if (req.target() != "/?q=accesskey")
{
path = path_cat(doc_root, req.target());
if (req.target().back() == '/')
{
path.append("index.html");
}
}
// Attempt to open the file
boost::beast::error_code ec;
http::file_body::value_type body;
if (req.target() != "/?q=accesskey" && req.target() != "/")
{
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
}
// Handle the case where the file doesn't exist
if (ec == boost::system::errc::no_such_file_or_directory)
{
return send(not_found(req.target()));
}
// Handle an unknown error
if (ec)
{
return send(server_error(ec.message()));
}
// Respond to HEAD request
if (req.method() == http::verb::head)
{
http::response<http::empty_body> res{ http::status::ok,
req.version() };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, mime_type(path));
res.content_length(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
// Respond to GET request
else if (req.method() == http::verb::get)
{
if (req.target() == "/?q=accesskey")
{
http::response<http::string_body> res{
std::piecewise_construct,
std::make_tuple(std::move(std::string{
accesskey })),
std::make_tuple(http::status::ok,
req.version()) };
res.set(http::field::server,
BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "plain/text");
res.content_length(res.body().size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
else if (req.target() == "/")
{
jinja2::Template tpl;
tpl.LoadFromFile(path.c_str());
jinja2::ValuesMap params = { { "apikey",
std::string(apikey) } };
http::response<http::string_body> res{
std::piecewise_construct,
std::make_tuple(std::move(tpl.RenderAsString(params))),
std::make_tuple(http::status::ok,
req.version()) };
res.set(http::field::server,
BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.set(http::field::content_length,
tpl.RenderAsString(params).size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
else
{
http::response<http::file_body> res{
std::piecewise_construct,
std::make_tuple(std::move(body)),
std::make_tuple(http::status::ok, req.version()) };
res.set(http::field::server,
BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, mime_type(path));
res.content_length(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
}
// Respond to POST request
else if (req.method() == http::verb::post)
{
boost::beast::string_view content_type =
req[http::field::content_type];
if (content_type.find("multipart/form-data") ==
std::string::npos &&
content_type.find("application/x-www-form-urlencoded")
== std::string::npos)
{
return send(bad_request("Bad request"));
}
std::map<std::string, std::string> parsed_value =
parse(req.body());
double money_amount =
std::stod(parsed_value["currency_amount"]);
std::string to_currency = parsed_value["to_currency"];
std::string from_currency = parsed_value["from_currency"];
std::string to_abbr = to_currency.substr(0, 3);
std::string from_abbr = from_currency.substr(0, 3);
double conversion_result = convert(from_abbr, to_abbr,
money_amount, accesskey);
http::response<http::string_body> res{
std::piecewise_construct,
std::make_tuple(std::move(std::to_string(conversion_result))),
std::make_tuple(http::status::ok, req.version()) };
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/plain");
res.content_length(res.body().size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
}
// Report a failure
void fail(boost::system::error_code ec, const char *what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
template<class Stream>
template<bool isRequest, class Body, class Fields>
void send_lambda<Stream>::operator()(http::message<isRequest, Body, Fields>
&&msg) const
{
// Determine if we should close the connection after
close_ = msg.need_eof();
// We need the serializer here because the serializer requires
// a non-const file_body, and the message oriented version of
// http::write only works with const messages.
http::serializer<isRequest, Body, Fields> sr{ msg };
http::write(stream_, sr, ec_);
}
// Handles an HTTP server connection
void do_session(tcp::socket socket, const std::string &doc_root, const char
*accesskey, const char *apikey)
{
bool close = false;
boost::system::error_code ec;
// This buffer is required to persist across reads
boost::beast::flat_buffer buffer;
// This lambda is used to send messages
send_lambda<tcp::socket> lambda{ socket, close, ec };
for (;;)
{
// Read a request
http::request<http::string_body> req;
http::read(socket, buffer, req, ec);
if (ec == http::error::end_of_stream)
{
break;
}
if (ec)
{
std::cerr << "Lines 583 and 584:\n";
return fail(ec, "read");
}
// Send the response
handle_request(doc_root, std::move(req), lambda, accesskey,
apikey);
if (ec)
{
std::cerr << "Lines 591 and 592:\n";
return fail(ec, "write");
}
if (close)
{
// This means we should close the connection, usually
because
// the response indicated the "Connection: close"
semantic.
break;
}
}
// Send a TCP shutdown
socket.shutdown(tcp::socket::shutdown_send, ec);
// At this point the connection is closed gracefully
}
// Perform currency conversion
double convert(const std::string &from_currency, std::string &to_currency,
const double money_amount, const char *accesskey)
{
using namespace std::chrono_literals;
std::vector<std::string> currencies{
"AED","AFN","ALL","AMD","ANG","AOA","ARS","AUD","AWG","AZN","BAM","BBD","BDT","BGN","BHD","BIF","BMD","BND","BOB","BRL","BSD","BTC","BTN","BWP",
"BYN","BYR","BZD","CAD","CDF","CHF","CLF","CLP","CNY","COP","CRC","CUC","CUP","CVE","CZK","DJF","DKK","DOP","DZD","EGP","ERN","ETB","EUR","FJD",
"FKP","GBP","GEL","GGP","GHS","GIP","GMD","GNF","GTQ","GYD","HKD","HNL","HRK","HTG","HUF","IDR","ILS","IMP","INR","IQD","IRR","ISK","JEP","JMD",
"JOD","JPY","KES","KGS","KHR","KMF","KPW","KRW","KWD","KYD","KZT","LAK","LBP","LKR","LRD","LSL","LTL","LVL","LYD","MAD","MDL","MGA","MKD","MMK",
"MNT","MOP","MRO","MUR","MVR","MWK","MXN","MYR","MZN","NAD","NGN","NIO","NOK","NPR","NZD","OMR","PAB","PEN","PGK","PHP","PKR","PLN","PYG","QAR",
"RON","RSD","RUB","RWF","SAR","SBD","SCR","SDG","SEK","SGD","SHP","SLL","SOS","SRD","STD","SVC","SYP","SZL","THB","TJS","TMT","TND","TOP","TRY",
"TTD","TWD","TZS","UAH","UGX","USD","UYU","UZS","VEF","VND","VUV","WST","XAF","XAG","XAU","XCD","XDR","XOF","XPF","YER","ZAR","ZMK","ZMW","ZWL"
};
std::map<std::string, std::string> query_data{
std::make_pair("from_currency", from_currency), std::make_pair("to_currency",
to_currency) };
cache_storage cache{ 1h };
json j_res = cache.query(query_data, accesskey);
double result = 0, rate = 0;
try
{
rate = j_res["quotes"][from_currency +
to_currency].get<double>();
}
catch (const json::exception &e)
{
std::cerr << "Line 635: Error: " << e.what() << '\n';
}
if (std::find(currencies.begin(), currencies.end(), to_currency) !=
currencies.end() &&
std::find(currencies.begin(), currencies.end(), from_currency)
!= currencies.end())
{
result = money_amount * rate;
}
return result;
}
// This function queries the currency API after making sure
// that the stored result(s) is/are old enough
// It also makes a new query to the API if needed
const json &cache_storage::query(const std::map<std::string, std::string>
&query_data, const char *accesskey)
{
auto found = m_cache.find(query_data);
boost::beast::error_code ec;
try
{
if (found == m_cache.end() || (std::chrono::steady_clock::now()
- found->second.first) > m_duration)
{
std::string host{ "apilayer.net" }, api_endpoint{
"/api/live" },
key{ accesskey }, source{
query_data.at(std::string("from_currency")) }, currency_param{
query_data.at(std::string("to_currency")) };
std::string target;
if (query_data.at(std::string("from_currency")) !=
"USD")
{
target = api_endpoint + "?access_key=" +
accesskey + "&source=" + source + "¤cies=" + currency_param + "&format=1";
}
else
{
target = api_endpoint + "?access_key=" +
accesskey + "¤cies=" + currency_param + "&format=1";
}
std::string port{ "80" };
int version = 11;
// The io_context is required for all IO
boost::asio::io_context ioc;
// These objects perform our IO
tcp::resolver resolver{ ioc };
tcp::socket socket{ ioc };
// Look up the domain name
const auto results = resolver.resolve(host, port);
// Make the connection on the IP address we get from a
lookup
boost::asio::connect(socket, results.begin(),
results.end());
// Set up an HTTP GET request message
http::request<http::string_body> req{ http::verb::get,
target, version };
req.set(http::field::host, host);
req.set(http::field::content_type, "application/json");
req.set(http::field::accept, "application/json");
req.set(http::field::user_agent,
BOOST_BEAST_VERSION_STRING);
// Send the HTTP request to the remote host
http::write(socket, req);
// This buffer is used for reading and must be persisted
boost::beast::flat_buffer buffer;
// Declare a container to hold the response
http::response<http::string_body> res;
// Receive the HTTP response
http::read(socket, buffer, res, ec);
found = m_cache.insert_or_assign(found, query_data,
std::make_pair(std::chrono::steady_clock::now(), json::parse(res.body())));
}
return found->second.second;
}
catch (const std::exception &e)
{
std::cerr << "Line 709: Error: " << e.what() << '\n';
}
catch (const boost::beast::error_code &ec)
{
std::cerr << "Line 713: Error: " << ec.message() << '\n';
}
return json{ nullptr };
}
httpd.conf
Description: httpd.conf
httpd-vhosts.conf
Description: httpd-vhosts.conf
proxy-html.conf
Description: proxy-html.conf
--------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
