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 }; }
Attachment:
httpd.conf
Description: httpd.conf
Attachment:
httpd-vhosts.conf
Description: httpd-vhosts.conf
Attachment:
proxy-html.conf
Description: proxy-html.conf
--------------------------------------------------------------------- To unsubscribe, e-mail: users-unsubscribe@xxxxxxxxxxxxxxxx For additional commands, e-mail: users-help@xxxxxxxxxxxxxxxx