virthttp  0.0
libvirt http interface
request_handler.hpp
Go to the documentation of this file.
1 #pragma once
2 #include <boost/beast.hpp>
3 #include <rapidjson/document.h>
4 #include <rapidjson/stringbuffer.h>
5 #include <rapidjson/writer.h>
6 #include "../general_store.hpp"
7 #include "../handler.hpp"
8 #include "../handlers/async/async_handler.hpp"
10 #include "urlparser.hpp"
11 
12 // This function produces an HTTP response for the given
13 // request. The type of the response object depends on the
14 // contents of the request, so the interface requires the
15 // caller to pass a generic lambda for receiving the response.
16 template <class Body, class Allocator, class Send>
17 void handle_request(GeneralStore& gstore, boost::beast::http::request<Body, boost::beast::http::basic_fields<Allocator>>&& req, Send&& send) {
18  // Returns a bad request response
19  const auto bad_request = [&](boost::beast::string_view why) {
20  boost::beast::http::response<boost::beast::http::string_body> res{boost::beast::http::status::bad_request, req.version()};
21  res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
22  res.set(boost::beast::http::field::content_type, "text/html");
23  res.keep_alive(req.keep_alive());
24  res.body() = why;
25  res.prepare_payload();
26  return res;
27  };
28 
29  // Returns a not found response
30  const auto not_found = [&](boost::beast::string_view target) {
31  boost::beast::http::response<boost::beast::http::string_body> res{boost::beast::http::status::not_found, req.version()};
32  res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
33  res.set(boost::beast::http::field::content_type, "text/html");
34  res.keep_alive(req.keep_alive());
35  res.body() = "The resource '" + std::string{target} + "' was not found.";
36  res.prepare_payload();
37  return res;
38  };
39 
40  // Returns a server error response
41  auto const server_error = [&req](boost::beast::string_view what) {
42  boost::beast::http::response<boost::beast::http::string_body> res{boost::beast::http::status::internal_server_error, req.version()};
43  res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
44  res.set(boost::beast::http::field::content_type, "text/html");
45  res.keep_alive(req.keep_alive());
46  res.body() = "An error occurred: '" + std::string{what} + "'";
47  res.prepare_payload();
48  return res;
49  };
50 
51  auto req_method = req.method();
52  logger.info("Received from a Session: HTTP ", boost::beast::http::to_string(req.method()), ' ', req.target());
53 
54  // Respond to HEAD request
55  if (req_method == boost::beast::http::verb::head) {
56  http::response<http::empty_body> res{http::status::ok, req.version()};
57  res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
58  res.set(http::field::content_type, "application/json");
59  res.content_length(0);
60  res.keep_alive(req.keep_alive());
61  return send(std::move(res));
62  }
63 
64  constexpr std::array supported_methods = {boost::beast::http::verb::get, boost::beast::http::verb::patch, boost::beast::http::verb::post,
65  boost::beast::http::verb::delete_};
66  // Make sure we can handle the method
67  if (std::find(supported_methods.begin(), supported_methods.end(), req_method) == supported_methods.end())
68  return send(bad_request("Unknown/Unsupported HTTP-method"));
69 
70  // Request path must be absolute and not contain "..".
71  if (req.target().empty() || req.target()[0] != '/' || req.target().find("..") != boost::beast::string_view::npos)
72  return send(bad_request("Illegal request-target"));
73 
74  const auto forward_packid = [&](auto& res) noexcept {
75  if (const auto pakid = req["X-Packet-ID"]; !pakid.empty())
76  res.set("X-Packet-ID", pakid);
77  };
78 
79  auto target = TargetParser{req.target()};
80  const auto& path_parts = target.getPathParts();
81 
82  if (path_parts.empty())
83  return send(bad_request("No module name specified"));
84 
85  // Handle cases where the client wants to retrieve an async result
86  if (path_parts[0] == "async") {
87  if (path_parts.size() != 2)
88  return send(bad_request("Invalid request target"));
89 
90  if (auto opt = target.getBool("async"); opt && *opt)
91  return send(bad_request("Async retrieve cannot be async'ed"));
92 
93  auto [code, body] = handle_async_retrieve<TransportProto::HTTP1>(gstore, path_parts[1]);
94 
95  boost::beast::http::response<boost::beast::http::string_body> res{code, req.version()};
96  res.content_length(body.size());
97  res.body() = std::move(body);
98  res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
99  res.set(boost::beast::http::field::content_type, "application/json");
100  forward_packid(res);
101  res.keep_alive(req.keep_alive());
102  return send(std::move(res));
103  }
104 
105  if (auto opt = target.getBool("async"); opt && *opt) {
106  auto launch_res = gstore.async_store.launch([&gstore, target = std::move(target), req = std::move(req)]() {
107  auto buf = handle_json(gstore, req, target);
108  return std::string{buf.GetString(), buf.GetLength()};
109  });
110 
111  if (!launch_res)
112  return send(server_error("Unable to enqueue async request"));
113 
114  constexpr auto n_bytes = sizeof(AsyncStore::IndexType) * 2;
115 
116  boost::beast::http::response<boost::beast::http::string_body> res{boost::beast::http::status::ok, req.version()};
117  res.body() = std::string{hex_encode_id(*launch_res).data(), n_bytes};
118  res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
119  res.set(boost::beast::http::field::content_type, "application/json");
120  forward_packid(res);
121  res.content_length(n_bytes);
122  res.keep_alive(req.keep_alive());
123  return send(std::move(res));
124  }
125 
126  auto buffer = handle_json(gstore, req, std::move(target));
127 
128  // Build the path to the requested file
129  /*
130  std::string path = path_cat(doc_root, req.target());
131  if(req.target().back() == '/')
132  path.append("index.html");
133 
134  // Attempt to open the file
135  beast::error_code ec;
136  http::file_body::value_type body;
137  body.open(path.c_str(), beast::file_mode::scan, ec);
138 
139 
140  // Handle the case where the file doesn't exist
141  if(ec == beast::errc::no_such_file_or_directory)
142  return send(not_found(req.target()));
143 
144  // Handle an unknown error
145  if(ec)
146  return send(server_error(ec.message()));
147 
148  // Cache the size since we need it after the move
149  auto const size = body.size();
150  */
151 
152  boost::beast::http::response<boost::beast::http::string_body> res{boost::beast::http::status::ok, req.version()};
153  res.body() = std::string{buffer.GetString()};
154  res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
155  res.set(boost::beast::http::field::content_type, "application/json");
156  forward_packid(res);
157  handle_compression(static_cast<const boost::beast::http::basic_fields<Allocator>&>(req), static_cast<boost::beast::http::fields&>(res),
158  res.body());
159  res.content_length(std::size_t{res.body().size()});
160  res.keep_alive(req.keep_alive());
161  return send(std::move(res));
162 }
Logger logger
Definition: logger.hpp:58
bool handle_compression(const boost::beast::http::basic_fields< Allocator > &in_head, boost::beast::http::basic_fields< Allocator > &out_head, std::string &body)
Definition: compression.hpp:39
AsyncStore async_store
Definition: general_store.hpp:10
void info(Ts...msg)
Definition: logger.hpp:34
void handle_request(GeneralStore &gstore, boost::beast::http::request< Body, boost::beast::http::basic_fields< Allocator >> &&req, Send &&send)
Definition: request_handler.hpp:17
rapidjson::StringBuffer handle_json(GeneralStore &gstore, const http::request< Body, http::basic_fields< Allocator >> &req, const TargetParser &target)
Definition: handler.hpp:25
std::uint32_t IndexType
Type used as the key to elems.
Definition: async_store.hpp:16
constexpr const std::vector< std::string_view > & getPathParts() const noexcept
Definition: urlparser.hpp:83
constexpr InputIt find(InputIt first, InputIt last, const T &value)
Definition: cexpr_algs.hpp:4
std::optional< IndexType > launch(Fcn &&fcn, std::optional< std::chrono::seconds > expire_opt=std::nullopt)
Definition: async_store.hpp:52
constexpr std::array< char, sizeof(std::uint32_t)*2 > hex_encode_id(std::uint32_t id) noexcept
Definition: async_handler.hpp:54
Definition: urlparser.hpp:36
Definition: general_store.hpp:5