• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
10 #ifndef BOOST_BEAST_HTTP_BASIC_FILE_BODY_HPP
11 #define BOOST_BEAST_HTTP_BASIC_FILE_BODY_HPP
12 
13 #include <boost/beast/core/detail/config.hpp>
14 #include <boost/beast/core/error.hpp>
15 #include <boost/beast/core/file_base.hpp>
16 #include <boost/beast/http/message.hpp>
17 #include <boost/assert.hpp>
18 #include <boost/optional.hpp>
19 #include <algorithm>
20 #include <cstdio>
21 #include <cstdint>
22 #include <utility>
23 
24 namespace boost {
25 namespace beast {
26 namespace http {
27 
28 //[example_http_file_body_1
29 
30 /** A message body represented by a file on the filesystem.
31 
32     Messages with this type have bodies represented by a
33     file on the file system. When parsing a message using
34     this body type, the data is stored in the file pointed
35     to by the path, which must be writable. When serializing,
36     the implementation will read the file and present those
37     octets as the body content. This may be used to serve
38     content from a directory as part of a web service.
39 
40     @tparam File The implementation to use for accessing files.
41     This type must meet the requirements of <em>File</em>.
42 */
43 template<class File>
44 struct basic_file_body
45 {
46     // Make sure the type meets the requirements
47     static_assert(is_file<File>::value,
48         "File type requirements not met");
49 
50     /// The type of File this body uses
51     using file_type = File;
52 
53     // Algorithm for storing buffers when parsing.
54     class reader;
55 
56     // Algorithm for retrieving buffers when serializing.
57     class writer;
58 
59     // The type of the @ref message::body member.
60     class value_type;
61 
62     /** Returns the size of the body
63 
64         @param body The file body to use
65     */
66     static
67     std::uint64_t
68     size(value_type const& body);
69 };
70 
71 //]
72 
73 //[example_http_file_body_2
74 
75 /** The type of the @ref message::body member.
76 
77     Messages declared using `basic_file_body` will have this type for
78     the body member. This rich class interface allow the file to be
79     opened with the file handle maintained directly in the object,
80     which is attached to the message.
81 */
82 template<class File>
83 class basic_file_body<File>::value_type
84 {
85     // This body container holds a handle to the file
86     // when it is open, and also caches the size when set.
87 
88     friend class reader;
89     friend class writer;
90     friend struct basic_file_body;
91 
92     // This represents the open file
93     File file_;
94 
95     // The cached file size
96     std::uint64_t file_size_ = 0;
97 
98 public:
99     /** Destructor.
100 
101         If the file is open, it is closed first.
102     */
103     ~value_type() = default;
104 
105     /// Constructor
106     value_type() = default;
107 
108     /// Constructor
109     value_type(value_type&& other) = default;
110 
111     /// Move assignment
112     value_type& operator=(value_type&& other) = default;
113 
114     /// Return the file
file()115     File& file()
116     {
117         return file_;
118     }
119 
120     /// Returns `true` if the file is open
121     bool
is_open() const122     is_open() const
123     {
124         return file_.is_open();
125     }
126 
127     /// Returns the size of the file if open
128     std::uint64_t
size() const129     size() const
130     {
131         return file_size_;
132     }
133 
134     /// Close the file if open
135     void
136     close();
137 
138     /** Open a file at the given path with the specified mode
139 
140         @param path The utf-8 encoded path to the file
141 
142         @param mode The file mode to use
143 
144         @param ec Set to the error, if any occurred
145     */
146     void
147     open(char const* path, file_mode mode, error_code& ec);
148 
149     /** Set the open file
150 
151         This function is used to set the open file. Any previously
152         set file will be closed.
153 
154         @param file The file to set. The file must be open or else
155         an error occurs
156 
157         @param ec Set to the error, if any occurred
158     */
159     void
160     reset(File&& file, error_code& ec);
161 };
162 
163 template<class File>
164 void
165 basic_file_body<File>::
166 value_type::
close()167 close()
168 {
169     error_code ignored;
170     file_.close(ignored);
171 }
172 
173 template<class File>
174 void
175 basic_file_body<File>::
176 value_type::
open(char const * path,file_mode mode,error_code & ec)177 open(char const* path, file_mode mode, error_code& ec)
178 {
179     // Open the file
180     file_.open(path, mode, ec);
181     if(ec)
182         return;
183 
184     // Cache the size
185     file_size_ = file_.size(ec);
186     if(ec)
187     {
188         close();
189         return;
190     }
191 }
192 
193 template<class File>
194 void
195 basic_file_body<File>::
196 value_type::
reset(File && file,error_code & ec)197 reset(File&& file, error_code& ec)
198 {
199     // First close the file if open
200     if(file_.is_open())
201     {
202         error_code ignored;
203         file_.close(ignored);
204     }
205 
206     // Take ownership of the new file
207     file_ = std::move(file);
208 
209     // Cache the size
210     file_size_ = file_.size(ec);
211 }
212 
213 // This is called from message::payload_size
214 template<class File>
215 std::uint64_t
216 basic_file_body<File>::
size(value_type const & body)217 size(value_type const& body)
218 {
219     // Forward the call to the body
220     return body.size();
221 }
222 
223 //]
224 
225 //[example_http_file_body_3
226 
227 /** Algorithm for retrieving buffers when serializing.
228 
229     Objects of this type are created during serialization
230     to extract the buffers representing the body.
231 */
232 template<class File>
233 class basic_file_body<File>::writer
234 {
235     value_type& body_;      // The body we are reading from
236     std::uint64_t remain_;  // The number of unread bytes
237     char buf_[4096];        // Small buffer for reading
238 
239 public:
240     // The type of buffer sequence returned by `get`.
241     //
242     using const_buffers_type =
243         net::const_buffer;
244 
245     // Constructor.
246     //
247     // `h` holds the headers of the message we are
248     // serializing, while `b` holds the body.
249     //
250     // Note that the message is passed by non-const reference.
251     // This is intentional, because reading from the file
252     // changes its "current position" which counts makes the
253     // operation logically not-const (although it is bitwise
254     // const).
255     //
256     // The BodyWriter concept allows the writer to choose
257     // whether to take the message by const reference or
258     // non-const reference. Depending on the choice, a
259     // serializer constructed using that body type will
260     // require the same const or non-const reference to
261     // construct.
262     //
263     // Readers which accept const messages usually allow
264     // the same body to be serialized by multiple threads
265     // concurrently, while readers accepting non-const
266     // messages may only be serialized by one thread at
267     // a time.
268     //
269     template<bool isRequest, class Fields>
270     writer(header<isRequest, Fields>& h, value_type& b);
271 
272     // Initializer
273     //
274     // This is called before the body is serialized and
275     // gives the writer a chance to do something that might
276     // need to return an error code.
277     //
278     void
279     init(error_code& ec);
280 
281     // This function is called zero or more times to
282     // retrieve buffers. A return value of `boost::none`
283     // means there are no more buffers. Otherwise,
284     // the contained pair will have the next buffer
285     // to serialize, and a `bool` indicating whether
286     // or not there may be additional buffers.
287     boost::optional<std::pair<const_buffers_type, bool>>
288     get(error_code& ec);
289 };
290 
291 //]
292 
293 //[example_http_file_body_4
294 
295 // Here we just stash a reference to the path for later.
296 // Rather than dealing with messy constructor exceptions,
297 // we save the things that might fail for the call to `init`.
298 //
299 template<class File>
300 template<bool isRequest, class Fields>
301 basic_file_body<File>::
302 writer::
writer(header<isRequest,Fields> & h,value_type & b)303 writer(header<isRequest, Fields>& h, value_type& b)
304     : body_(b)
305 {
306     boost::ignore_unused(h);
307 
308     // The file must already be open
309     BOOST_ASSERT(body_.file_.is_open());
310 
311     // Get the size of the file
312     remain_ = body_.file_size_;
313 }
314 
315 // Initializer
316 template<class File>
317 void
318 basic_file_body<File>::
319 writer::
init(error_code & ec)320 init(error_code& ec)
321 {
322     // The error_code specification requires that we
323     // either set the error to some value, or set it
324     // to indicate no error.
325     //
326     // We don't do anything fancy so set "no error"
327     ec = {};
328 }
329 
330 // This function is called repeatedly by the serializer to
331 // retrieve the buffers representing the body. Our strategy
332 // is to read into our buffer and return it until we have
333 // read through the whole file.
334 //
335 template<class File>
336 auto
337 basic_file_body<File>::
338 writer::
get(error_code & ec)339 get(error_code& ec) ->
340     boost::optional<std::pair<const_buffers_type, bool>>
341 {
342     // Calculate the smaller of our buffer size,
343     // or the amount of unread data in the file.
344     auto const amount =  remain_ > sizeof(buf_) ?
345         sizeof(buf_) : static_cast<std::size_t>(remain_);
346 
347     // Handle the case where the file is zero length
348     if(amount == 0)
349     {
350         // Modify the error code to indicate success
351         // This is required by the error_code specification.
352         //
353         // NOTE We use the existing category instead of calling
354         //      into the library to get the generic category because
355         //      that saves us a possibly expensive atomic operation.
356         //
357         ec = {};
358         return boost::none;
359     }
360 
361     // Now read the next buffer
362     auto const nread = body_.file_.read(buf_, amount, ec);
363     if(ec)
364         return boost::none;
365 
366     if (nread == 0)
367     {
368         ec = error::short_read;
369         return boost::none;
370     }
371 
372     // Make sure there is forward progress
373     BOOST_ASSERT(nread != 0);
374     BOOST_ASSERT(nread <= remain_);
375 
376     // Update the amount remaining based on what we got
377     remain_ -= nread;
378 
379     // Return the buffer to the caller.
380     //
381     // The second element of the pair indicates whether or
382     // not there is more data. As long as there is some
383     // unread bytes, there will be more data. Otherwise,
384     // we set this bool to `false` so we will not be called
385     // again.
386     //
387     ec = {};
388     return {{
389         const_buffers_type{buf_, nread},    // buffer to return.
390         remain_ > 0                         // `true` if there are more buffers.
391         }};
392 }
393 
394 //]
395 
396 //[example_http_file_body_5
397 
398 /** Algorithm for storing buffers when parsing.
399 
400     Objects of this type are created during parsing
401     to store incoming buffers representing the body.
402 */
403 template<class File>
404 class basic_file_body<File>::reader
405 {
406     value_type& body_;  // The body we are writing to
407 
408 public:
409     // Constructor.
410     //
411     // This is called after the header is parsed and
412     // indicates that a non-zero sized body may be present.
413     // `h` holds the received message headers.
414     // `b` is an instance of `basic_file_body`.
415     //
416     template<bool isRequest, class Fields>
417     explicit
418     reader(header<isRequest, Fields>&h, value_type& b);
419 
420     // Initializer
421     //
422     // This is called before the body is parsed and
423     // gives the reader a chance to do something that might
424     // need to return an error code. It informs us of
425     // the payload size (`content_length`) which we can
426     // optionally use for optimization.
427     //
428     void
429     init(boost::optional<std::uint64_t> const&, error_code& ec);
430 
431     // This function is called one or more times to store
432     // buffer sequences corresponding to the incoming body.
433     //
434     template<class ConstBufferSequence>
435     std::size_t
436     put(ConstBufferSequence const& buffers,
437         error_code& ec);
438 
439     // This function is called when writing is complete.
440     // It is an opportunity to perform any final actions
441     // which might fail, in order to return an error code.
442     // Operations that might fail should not be attempted in
443     // destructors, since an exception thrown from there
444     // would terminate the program.
445     //
446     void
447     finish(error_code& ec);
448 };
449 
450 //]
451 
452 //[example_http_file_body_6
453 
454 // We don't do much in the reader constructor since the
455 // file is already open.
456 //
457 template<class File>
458 template<bool isRequest, class Fields>
459 basic_file_body<File>::
460 reader::
reader(header<isRequest,Fields> & h,value_type & body)461 reader(header<isRequest, Fields>& h, value_type& body)
462     : body_(body)
463 {
464     boost::ignore_unused(h);
465 }
466 
467 template<class File>
468 void
469 basic_file_body<File>::
470 reader::
init(boost::optional<std::uint64_t> const & content_length,error_code & ec)471 init(
472     boost::optional<std::uint64_t> const& content_length,
473     error_code& ec)
474 {
475     // The file must already be open for writing
476     BOOST_ASSERT(body_.file_.is_open());
477 
478     // We don't do anything with this but a sophisticated
479     // application might check available space on the device
480     // to see if there is enough room to store the body.
481     boost::ignore_unused(content_length);
482 
483     // The error_code specification requires that we
484     // either set the error to some value, or set it
485     // to indicate no error.
486     //
487     // We don't do anything fancy so set "no error"
488     ec = {};
489 }
490 
491 // This will get called one or more times with body buffers
492 //
493 template<class File>
494 template<class ConstBufferSequence>
495 std::size_t
496 basic_file_body<File>::
497 reader::
put(ConstBufferSequence const & buffers,error_code & ec)498 put(ConstBufferSequence const& buffers, error_code& ec)
499 {
500     // This function must return the total number of
501     // bytes transferred from the input buffers.
502     std::size_t nwritten = 0;
503 
504     // Loop over all the buffers in the sequence,
505     // and write each one to the file.
506     for(auto it = net::buffer_sequence_begin(buffers);
507         it != net::buffer_sequence_end(buffers); ++it)
508     {
509         // Write this buffer to the file
510         net::const_buffer buffer = *it;
511         nwritten += body_.file_.write(
512             buffer.data(), buffer.size(), ec);
513         if(ec)
514             return nwritten;
515     }
516 
517     // Indicate success
518     // This is required by the error_code specification
519     ec = {};
520 
521     return nwritten;
522 }
523 
524 // Called after writing is done when there's no error.
525 template<class File>
526 void
527 basic_file_body<File>::
528 reader::
finish(error_code & ec)529 finish(error_code& ec)
530 {
531     // This has to be cleared before returning, to
532     // indicate no error. The specification requires it.
533     ec = {};
534 }
535 
536 //]
537 
538 #if ! BOOST_BEAST_DOXYGEN
539 // operator<< is not supported for file_body
540 template<bool isRequest, class File, class Fields>
541 std::ostream&
542 operator<<(std::ostream&, message<
543     isRequest, basic_file_body<File>, Fields> const&) = delete;
544 #endif
545 
546 } // http
547 } // beast
548 } // boost
549 
550 #endif
551