flex_body.hpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. //
  2. // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
  3. //
  4. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  5. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. //
  7. // Official repository: https://github.com/boostorg/beast
  8. //
  9. #ifndef __ASIO2_HTTP_FLEX_BODY_HPP__
  10. #define __ASIO2_HTTP_FLEX_BODY_HPP__
  11. #include <asio2/external/beast.hpp>
  12. #include <asio2/external/assert.hpp>
  13. #include <asio2/http/detail/http_util.hpp>
  14. #ifdef ASIO2_HEADER_ONLY
  15. namespace bho {
  16. #else
  17. namespace boost {
  18. #endif
  19. namespace beast {
  20. namespace http {
  21. /** A message body represented by a file on the filesystem.
  22. Messages with this type have bodies represented by a
  23. file on the file system. When parsing a message using
  24. this body type, the data is stored in the file pointed
  25. to by the path, which must be writable. When serializing,
  26. the implementation will read the file and present those
  27. octets as the body content. This may be used to serve
  28. content from a directory as part of a web service.
  29. @tparam File The implementation to use for accessing files.
  30. This type must meet the requirements of <em>File</em>.
  31. */
  32. template<class TextBody, class FileBody>
  33. struct basic_flex_body
  34. {
  35. // Algorithm for storing buffers when parsing.
  36. class reader;
  37. // Algorithm for retrieving buffers when serializing.
  38. class writer;
  39. // The type of the @ref message::body member.
  40. class value_type;
  41. /** Returns the size of the body
  42. @param body The file body to use
  43. */
  44. static inline
  45. std::uint64_t
  46. size(value_type const& body) noexcept;
  47. };
  48. /** The type of the @ref message::body member.
  49. Messages declared using `basic_flex_body` will have this type for
  50. the body member. This rich class interface allow the file to be
  51. opened with the file handle maintained directly in the object,
  52. which is attached to the message.
  53. */
  54. template<class TextBody, class FileBody>
  55. class basic_flex_body<TextBody, FileBody>::value_type
  56. {
  57. public:
  58. using text_t = typename TextBody::value_type;
  59. using file_t = typename FileBody::value_type;
  60. private:
  61. // This body container holds a handle to the file
  62. // when it is open, and also caches the size when set.
  63. friend class reader;
  64. friend class writer;
  65. friend struct basic_flex_body;
  66. text_t text_;
  67. file_t file_;
  68. std::uint64_t to_text_limit_ = std::uint64_t(8 * 1024 * 1024);
  69. public:
  70. /** Destructor.
  71. If the file is open, it is closed first.
  72. */
  73. ~value_type() = default;
  74. /// Constructor
  75. value_type() = default;
  76. /// Constructor
  77. value_type(value_type const&) = default;
  78. /// Assignment
  79. value_type& operator=(value_type const&) = default;
  80. /// Constructor
  81. value_type(value_type&& other) = default;
  82. /// Move assignment
  83. value_type& operator=(value_type&& other) = default;
  84. inline text_t& text() noexcept
  85. {
  86. return text_;
  87. }
  88. inline text_t& text() const noexcept
  89. {
  90. return const_cast<text_t&>(text_);
  91. }
  92. /// Return the file
  93. inline file_t& file() noexcept
  94. {
  95. return file_;
  96. }
  97. /// Return the file
  98. inline file_t& file() const noexcept
  99. {
  100. return const_cast<file_t&>(file_);
  101. }
  102. /// Returns the size of the text or file
  103. inline std::uint64_t size() const noexcept
  104. {
  105. return (is_file() ? file_.size() : text_.size());
  106. }
  107. inline bool is_text() const noexcept { return !is_file(); }
  108. inline bool is_file() const noexcept { return file_.is_open(); }
  109. inline void set_to_text_limit(std::uint64_t v) noexcept
  110. {
  111. this->to_text_limit_ = v;
  112. }
  113. /// Convert the file body to text body.
  114. inline bool to_text()
  115. {
  116. if (this->is_text())
  117. return true;
  118. if (this->size() > this->to_text_limit_)
  119. return false;
  120. error_code ec;
  121. auto& f = file_.file();
  122. f.seek(0, ec);
  123. if (ec)
  124. return false;
  125. text_.resize(std::size_t(this->size()));
  126. auto const nread = f.read(text_.data(), text_.size(), ec);
  127. if (ec || nread != text_.size())
  128. {
  129. text_.clear();
  130. return false;
  131. }
  132. file_ = file_t{};
  133. return true;
  134. }
  135. };
  136. // This is called from message::payload_size
  137. template<class TextBody, class FileBody>
  138. std::uint64_t
  139. basic_flex_body<TextBody, FileBody>::
  140. size(value_type const& body) noexcept
  141. {
  142. // Forward the call to the body
  143. return body.size();
  144. }
  145. /** Algorithm for retrieving buffers when serializing.
  146. Objects of this type are created during serialization
  147. to extract the buffers representing the body.
  148. */
  149. template<class TextBody, class FileBody>
  150. class basic_flex_body<TextBody, FileBody>::writer
  151. {
  152. public:
  153. using text_writer = typename TextBody::writer;
  154. using file_writer = typename FileBody::writer;
  155. private:
  156. value_type const& body_; // The body we are reading from
  157. text_writer text_writer_;
  158. file_writer file_writer_;
  159. public:
  160. // The type of buffer sequence returned by `get`.
  161. //
  162. using const_buffers_type =
  163. ::asio::const_buffer;
  164. // Constructor.
  165. //
  166. // `h` holds the headers of the message we are
  167. // serializing, while `b` holds the body.
  168. //
  169. // Note that the message is passed by non-const reference.
  170. // This is intentional, because reading from the file
  171. // changes its "current position" which counts makes the
  172. // operation logically not-const (although it is bitwise
  173. // const).
  174. //
  175. // The BodyWriter concept allows the writer to choose
  176. // whether to take the message by const reference or
  177. // non-const reference. Depending on the choice, a
  178. // serializer constructed using that body type will
  179. // require the same const or non-const reference to
  180. // construct.
  181. //
  182. // Readers which accept const messages usually allow
  183. // the same body to be serialized by multiple threads
  184. // concurrently, while readers accepting non-const
  185. // messages may only be serialized by one thread at
  186. // a time.
  187. //
  188. template<bool isRequest, class Fields>
  189. writer(header<isRequest, Fields> const& h, value_type const& b);
  190. // Initializer
  191. //
  192. // This is called before the body is serialized and
  193. // gives the writer a chance to do something that might
  194. // need to return an error code.
  195. //
  196. inline void
  197. init(error_code& ec);
  198. // This function is called zero or more times to
  199. // retrieve buffers. A return value of `std::nullopt`
  200. // means there are no more buffers. Otherwise,
  201. // the contained pair will have the next buffer
  202. // to serialize, and a `bool` indicating whether
  203. // or not there may be additional buffers.
  204. #ifdef ASIO2_HEADER_ONLY
  205. inline std::optional<std::pair<const_buffers_type, bool>>
  206. #else
  207. inline boost::optional<std::pair<const_buffers_type, bool>>
  208. #endif
  209. get(error_code& ec);
  210. };
  211. // Here we just stash a reference to the path for later.
  212. // Rather than dealing with messy constructor exceptions,
  213. // we save the things that might fail for the call to `init`.
  214. //
  215. template<class TextBody, class FileBody>
  216. template<bool isRequest, class Fields>
  217. basic_flex_body<TextBody, FileBody>::
  218. writer::
  219. writer(header<isRequest, Fields> const& h, value_type const& b)
  220. : body_(b)
  221. , text_writer_(const_cast<header<isRequest, Fields>&>(h), const_cast<value_type&>(b).text())
  222. , file_writer_(const_cast<header<isRequest, Fields>&>(h), const_cast<value_type&>(b).file())
  223. {
  224. }
  225. // Initializer
  226. template<class TextBody, class FileBody>
  227. void
  228. basic_flex_body<TextBody, FileBody>::
  229. writer::
  230. init(error_code& ec)
  231. {
  232. // The error_code specification requires that we
  233. // either set the error to some value, or set it
  234. // to indicate no error.
  235. //
  236. // We don't do anything fancy so set "no error"
  237. if (body_.is_file())
  238. file_writer_.init(ec);
  239. else
  240. text_writer_.init(ec);
  241. }
  242. // This function is called repeatedly by the serializer to
  243. // retrieve the buffers representing the body. Our strategy
  244. // is to read into our buffer and return it until we have
  245. // read through the whole file.
  246. //
  247. template<class TextBody, class FileBody>
  248. auto
  249. basic_flex_body<TextBody, FileBody>::
  250. writer::
  251. get(error_code& ec) ->
  252. #ifdef ASIO2_HEADER_ONLY
  253. std::optional<std::pair<const_buffers_type, bool>>
  254. #else
  255. boost::optional<std::pair<const_buffers_type, bool>>
  256. #endif
  257. {
  258. if (body_.is_file())
  259. return file_writer_.get(ec);
  260. else
  261. return text_writer_.get(ec);
  262. }
  263. /** Algorithm for storing buffers when parsing.
  264. Objects of this type are created during parsing
  265. to store incoming buffers representing the body.
  266. */
  267. template<class TextBody, class FileBody>
  268. class basic_flex_body<TextBody, FileBody>::reader
  269. {
  270. public:
  271. using text_reader = typename TextBody::reader;
  272. using file_reader = typename FileBody::reader;
  273. private:
  274. value_type& body_; // The body we are reading from
  275. text_reader text_reader_;
  276. file_reader file_reader_;
  277. public:
  278. // Constructor.
  279. //
  280. // This is called after the header is parsed and
  281. // indicates that a non-zero sized body may be present.
  282. // `h` holds the received message headers.
  283. // `b` is an instance of `basic_flex_body`.
  284. //
  285. template<bool isRequest, class Fields>
  286. explicit
  287. reader(header<isRequest, Fields>&h, value_type& b);
  288. // Initializer
  289. //
  290. // This is called before the body is parsed and
  291. // gives the reader a chance to do something that might
  292. // need to return an error code. It informs us of
  293. // the payload size (`content_length`) which we can
  294. // optionally use for optimization.
  295. //
  296. inline void
  297. #ifdef ASIO2_HEADER_ONLY
  298. init(std::optional<std::uint64_t> const&, error_code& ec);
  299. #else
  300. init(boost::optional<std::uint64_t> const&, error_code& ec);
  301. #endif
  302. // This function is called one or more times to store
  303. // buffer sequences corresponding to the incoming body.
  304. //
  305. template<class ConstBufferSequence>
  306. inline std::size_t
  307. put(ConstBufferSequence const& buffers,
  308. error_code& ec);
  309. // This function is called when writing is complete.
  310. // It is an opportunity to perform any final actions
  311. // which might fail, in order to return an error code.
  312. // Operations that might fail should not be attempted in
  313. // destructors, since an exception thrown from there
  314. // would terminate the program.
  315. //
  316. inline void
  317. finish(error_code& ec);
  318. };
  319. // We don't do much in the reader constructor since the
  320. // file is already open.
  321. //
  322. template<class TextBody, class FileBody>
  323. template<bool isRequest, class Fields>
  324. basic_flex_body<TextBody, FileBody>::
  325. reader::
  326. reader(header<isRequest, Fields>& h, value_type& body)
  327. : body_(body), text_reader_(h, body.text()), file_reader_(h, body.file())
  328. {
  329. }
  330. template<class TextBody, class FileBody>
  331. void
  332. basic_flex_body<TextBody, FileBody>::
  333. reader::
  334. init(
  335. #ifdef ASIO2_HEADER_ONLY
  336. std::optional<std::uint64_t> const& content_length,
  337. #else
  338. boost::optional<std::uint64_t> const& content_length,
  339. #endif
  340. error_code& ec)
  341. {
  342. if (body_.is_file())
  343. file_reader_.init(content_length, ec);
  344. else
  345. text_reader_.init(content_length, ec);
  346. }
  347. // This will get called one or more times with body buffers
  348. //
  349. template<class TextBody, class FileBody>
  350. template<class ConstBufferSequence>
  351. std::size_t
  352. basic_flex_body<TextBody, FileBody>::
  353. reader::
  354. put(ConstBufferSequence const& buffers, error_code& ec)
  355. {
  356. if (body_.is_file())
  357. return file_reader_.put(buffers, ec);
  358. else
  359. return text_reader_.put(buffers, ec);
  360. }
  361. // Called after writing is done when there's no error.
  362. template<class TextBody, class FileBody>
  363. void
  364. basic_flex_body<TextBody, FileBody>::
  365. reader::
  366. finish(error_code& ec)
  367. {
  368. // This has to be cleared before returning, to
  369. // indicate no error. The specification requires it.
  370. if (body_.is_file())
  371. file_reader_.finish(ec);
  372. else
  373. text_reader_.finish(ec);
  374. }
  375. template<class TextBody, class FileBody>
  376. std::ostream&
  377. operator<<(std::ostream& os,
  378. typename basic_flex_body<TextBody, FileBody>::value_type const& body)
  379. {
  380. if (body.is_text())
  381. {
  382. os << body.text();
  383. }
  384. else
  385. {
  386. ASIO2_ASSERT(false);
  387. }
  388. return os;
  389. }
  390. using flex_body = basic_flex_body<http::string_body, http::file_body>;
  391. template<typename = void>
  392. std::ostream&
  393. operator<<(std::ostream& os,
  394. typename flex_body::value_type const& body)
  395. {
  396. if (body.is_text())
  397. {
  398. os << body.text();
  399. }
  400. else
  401. {
  402. ASIO2_ASSERT(false);
  403. }
  404. return os;
  405. }
  406. } // http
  407. } // beast
  408. } // bho
  409. #endif