DragonOsman

http_server.h

Jan 22nd, 2018
142
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 9.43 KB | None | 0 0
  1. #ifndef HTTP_SERVER_H
  2. #define HTTP_SERVER_H
  3.  
  4. #include <boost/beast/core.hpp>
  5. #include <boost/beast/http.hpp>
  6. #include <boost/beast/version.hpp>
  7. #include <boost/asio/bind_executor.hpp>
  8. #include <boost/asio/ip/tcp.hpp>
  9. #include <boost/asio/strand.hpp>
  10. #include <boost/config.hpp>
  11. #include <algorithm>
  12. #include <nlohmann/json.hpp>
  13. #include <cstdlib>
  14. #include <functional>
  15. #include <iostream>
  16. #include <memory>
  17. #include <string>
  18. #include <thread>
  19. #include <vector>
  20.  
  21. using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>
  22. namespace http = boost::beast::http;    // from <boost/beast/http.hpp>
  23.  
  24.                                         // Return a reasonable mime type based on the extension of a file.
  25. boost::beast::string_view mime_type(boost::beast::string_view path);
  26.  
  27. // Append an HTTP rel-path to a local filesystem path.
  28. // The returned path is normalized for the platform.
  29. std::string path_cat(boost::beast::string_view base, boost::beast::string_view path);
  30.  
  31. // This class represents a cache for storing results from the
  32. // currency exchange API used by openexchangerates.org
  33. template<typename Query_Data_T, typename Query_Response_T>
  34. class cache_storage
  35. {
  36. public:
  37.    
  38.     cache_storage()
  39.         : m_cache{}
  40.     {  
  41.     }
  42.  
  43.     const Query_Response_T &query(Query_Data_T query_data)
  44.     {
  45.  
  46.     }
  47.  
  48. private:
  49.     std::unordered_map<Query_Data_T, std::pair<std::chrono::time_point<std::chrono::system_clock>, Query_Response_T>> m_cache;
  50. };
  51.  
  52. // This function produces an HTTP response for the given
  53. // request. The type of the response object depends on the
  54. // contents of the request, so the interface requires the
  55. // caller to pass a generic lambda for receiving the response.
  56. template<class Body, class Allocator, class Send>
  57. void handle_request(boost::beast::string_view doc_root, http::request<Body, http::basic_fields<Allocator>>&& req, Send&& send)
  58. {
  59.     // Returns a bad request response
  60.     const auto bad_request =
  61.         [&req](boost::beast::string_view why)
  62.     {
  63.         http::response<http::string_body> res{ http::status::bad_request, req.version() };
  64.         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  65.         res.set(http::field::content_type, "text/html");
  66.         res.keep_alive(req.keep_alive());
  67.         res.body() = why.to_string();
  68.         res.prepare_payload();
  69.         return res;
  70.     };
  71.  
  72.     // Returns a not found response
  73.     const auto not_found =
  74.         [&req](boost::beast::string_view target)
  75.     {
  76.         http::response<http::string_body> res{ http::status::not_found, req.version() };
  77.         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  78.         res.set(http::field::content_type, "text/html");
  79.         res.keep_alive(req.keep_alive());
  80.         res.body() = "The resource '" + target.to_string() + "' was not found.";
  81.         res.prepare_payload();
  82.         return res;
  83.     };
  84.  
  85.     // Returns a server error response
  86.     const auto server_error = [&req](boost::beast::string_view what)
  87.     {
  88.         http::response<http::string_body> res{ http::status::internal_server_error, req.version() };
  89.         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  90.         res.set(http::field::content_type, "text/html");
  91.         res.keep_alive(req.keep_alive());
  92.         res.body() = "An error occurred: '" + what.to_string() + "'";
  93.         res.prepare_payload();
  94.         return res;
  95.     };
  96.  
  97.     // Make sure we can handle the method
  98.     if (req.method() != http::verb::get &&
  99.         req.method() != http::verb::head &&
  100.         req.method() != http::verb::post)
  101.     {
  102.         return send(bad_request("Unknown HTTP-method"));
  103.     }
  104.  
  105.     // Request path must be absolute and not contain "..".
  106.     if (req.target().empty() ||
  107.         req.target()[0] != '/' ||
  108.         req.target().find("..") != boost::beast::string_view::npos)
  109.     {
  110.         return send(bad_request("Illegal request-target"));
  111.     }
  112.  
  113.     // Build the path to the requested file
  114.     std::string path = path_cat(doc_root, req.target());
  115.     if (req.target().back() == '/')
  116.     {
  117.         path.append("index.html");
  118.     }
  119.  
  120.     // In case of POST request, check the path URI
  121.     else if (req.target().front() == '/' && req.target().size() > 1)
  122.     {
  123.         using boost::beast::iequals;
  124.         const auto ext = [&path]
  125.         {
  126.             auto pos = path.rfind(".");
  127.             if (pos == boost::beast::string_view::npos)
  128.             {
  129.                 return boost::beast::string_view{};
  130.             }
  131.             pos = path.find(" ");
  132.             if (pos != boost::beast::string_view::npos)
  133.             {
  134.                 return boost::beast::string_view{};
  135.             }
  136.             return boost::beast::string_view(path.substr(pos));
  137.         }();
  138.         if (!iequals(ext, ".cgi") && !iequals(ext, ".exe"))
  139.         {
  140.             return send(bad_request("Illegal request-target"));
  141.         }
  142.     }
  143.  
  144.     // Attempt to open the file
  145.     boost::beast::error_code ec;
  146.     http::file_body::value_type body;
  147.     body.open(path.c_str(), boost::beast::file_mode::scan, ec);
  148.  
  149.     // Handle the case where the file doesn't exist
  150.     if (ec == boost::system::errc::no_such_file_or_directory)
  151.     {
  152.         return send(not_found(req.target()));
  153.     }
  154.  
  155.     // Handle an unknown error
  156.     if (ec)
  157.     {
  158.         return send(server_error(ec.message()));
  159.     }
  160.  
  161.     // Respond to HEAD request
  162.     if (req.method() == http::verb::head)
  163.     {
  164.         http::response<http::empty_body> res{ http::status::ok, req.version() };
  165.         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  166.         res.set(http::field::content_type, mime_type(path));
  167.         res.content_length(body.size());
  168.         res.keep_alive(req.keep_alive());
  169.         return send(std::move(res));
  170.     }
  171.  
  172.     // Respond to GET request
  173.     else if (req.method() == http::verb::get)
  174.     {
  175.         http::response<http::file_body> res{
  176.             std::piecewise_construct,
  177.             std::make_tuple(std::move(body)),
  178.             std::make_tuple(http::status::ok, req.version()) };
  179.         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  180.         res.set(http::field::content_type, mime_type(path));
  181.         res.content_length(body.size());
  182.         res.keep_alive(req.keep_alive());
  183.         return send(std::move(res));
  184.     }
  185.  
  186.     // Respond to POST request
  187.     else if (req.method() == http::verb::post)
  188.     {
  189.         boost::beast::string_view content_type = req[http::field::content_type];
  190.         if (content_type != "multipart/form-data" && content_type != "application/x-www-form-urlencoded")
  191.         {
  192.             return send(bad_request("Bad request"));
  193.         }
  194.  
  195.        
  196.        
  197.         http::response<http::file_body> res{
  198.             std::piecewise_construct,
  199.             std::make_tuple(std::move(body)),
  200.             std::make_tuple(http::status::ok, req.version()) };
  201.         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
  202.         res.set(http::field::content_type, mime_type(path));
  203.         res.content_length(body.size());
  204.         res.keep_alive(req.keep_alive());
  205.         return send(std::move(res));
  206.     }
  207.     return send(bad_request("Bad request"));
  208. }
  209.  
  210. //------------------------------------------------------------------------------
  211.  
  212. // Report a failure
  213. void fail(boost::system::error_code ec, const char *what);
  214.  
  215. // Handles an HTTP server connection
  216. class session : public std::enable_shared_from_this<session>
  217. {
  218.     // This is the C++11 equivalent of a generic lambda.
  219.     // The function object is used to send an HTTP message.
  220.     struct send_lambda
  221.     {
  222.         session& self_;
  223.  
  224.         explicit send_lambda(session& self)
  225.             : self_{ self }
  226.         {
  227.         }
  228.  
  229.         template<bool isRequest, class Body, class Fields>
  230.         void operator()(http::message<isRequest, Body, Fields>&& msg) const
  231.         {
  232.             // The lifetime of the message has to extend
  233.             // for the duration of the async operation so
  234.             // we use a shared_ptr to manage it.
  235.             auto sp = std::make_shared<http::message<isRequest, Body, Fields>>(std::move(msg));
  236.  
  237.             // Store a type-erased version of the shared
  238.             // pointer in the class to keep it alive.
  239.             self_.res_ = sp;
  240.  
  241.             // Write the response
  242.             http::async_write(self_.socket_, *sp, boost::asio::bind_executor(self_.strand_, std::bind(&session::on_write,
  243.                 self_.shared_from_this(), std::placeholders::_1, std::placeholders::_2, sp->need_eof())));
  244.         }
  245.     };
  246.  
  247.     tcp::socket socket_;
  248.     boost::asio::strand<boost::asio::io_context::executor_type> strand_;
  249.     boost::beast::flat_buffer buffer_;
  250.     std::string const& doc_root_;
  251.     http::request<http::string_body> req_;
  252.     std::shared_ptr<void> res_;
  253.     send_lambda lambda_;
  254.  
  255. public:
  256.     // Take ownership of the socket
  257.     explicit session(tcp::socket socket, const std::string &doc_root)
  258.         : socket_{ std::move(socket) }
  259.         , strand_{ socket_.get_executor() }
  260.         , doc_root_{ doc_root }
  261.         , lambda_{ *this }
  262.     {
  263.     }
  264.  
  265.     // Start the asynchronous operation
  266.     void run()
  267.     {
  268.         do_read();
  269.     }
  270.  
  271.     void do_read()
  272.     {
  273.         // Read a request
  274.         http::async_read(socket_, buffer_, req_, boost::asio::bind_executor(strand_, std::bind(&session::on_read, shared_from_this(),
  275.             std::placeholders::_1, std::placeholders::_2)));
  276.     }
  277.  
  278.     void on_read(boost::system::error_code ec, std::size_t bytes_transferred);
  279.  
  280.     void on_write(boost::system::error_code ec, std::size_t bytes_transferred, bool close);
  281.  
  282.     void do_close()
  283.     {
  284.         // Send a TCP shutdown
  285.         boost::system::error_code ec;
  286.         socket_.shutdown(tcp::socket::shutdown_send, ec);
  287.  
  288.         // At this point the connection is closed gracefully
  289.     }
  290. };
  291.  
  292. //------------------------------------------------------------------------------
  293.  
  294. // Accepts incoming connections and launches the sessions
  295. class listener : public std::enable_shared_from_this<listener>
  296. {
  297.     tcp::acceptor acceptor_;
  298.     tcp::socket socket_;
  299.     const std::string &doc_root_;
  300.  
  301. public:
  302.     listener(boost::asio::io_context& ioc, tcp::endpoint endpoint, const std::string &doc_root);
  303.  
  304.     // Start accepting incoming connections
  305.     void run()
  306.     {
  307.         if (!acceptor_.is_open())
  308.         {
  309.             return;
  310.         }
  311.         do_accept();
  312.     }
  313.  
  314.     void do_accept()
  315.     {
  316.         acceptor_.async_accept(socket_, std::bind(&listener::on_accept, shared_from_this(), std::placeholders::_1));
  317.     }
  318.  
  319.     void on_accept(boost::system::error_code ec);
  320. };
  321.  
  322. #endif
Add Comment
Please, Sign In to add comment