Line data Source code
1 : //
2 : // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.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/cppalliance/http_proto
8 : //
9 :
10 : #ifndef BOOST_HTTP_PROTO_SERIALIZER_HPP
11 : #define BOOST_HTTP_PROTO_SERIALIZER_HPP
12 :
13 : #include <boost/http_proto/detail/config.hpp>
14 : #include <boost/http_proto/context.hpp>
15 : #include <boost/http_proto/filter.hpp>
16 : #include <boost/http_proto/source.hpp>
17 : #include <boost/http_proto/detail/array_of_buffers.hpp>
18 : #include <boost/http_proto/detail/except.hpp>
19 : #include <boost/http_proto/detail/header.hpp>
20 : #include <boost/http_proto/detail/workspace.hpp>
21 : #include <boost/buffers/circular_buffer.hpp>
22 : #include <boost/buffers/flat_buffer.hpp>
23 : #include <boost/buffers/range.hpp>
24 : #include <boost/buffers/type_traits.hpp>
25 : #include <boost/system/result.hpp>
26 : #include <cstdint>
27 : #include <memory>
28 : #include <type_traits>
29 : #include <utility>
30 :
31 : namespace boost {
32 : namespace http_proto {
33 : namespace detail {
34 : // chunked-body = *chunk
35 : // last-chunk
36 : // trailer-section
37 : // CRLF
38 :
39 : static
40 : constexpr
41 : std::size_t
42 : crlf_len_ = 2;
43 :
44 : // chunk = chunk-size [ chunk-ext ] CRLF
45 : // chunk-data CRLF
46 : static
47 : constexpr
48 : std::size_t
49 : chunk_header_len_ =
50 : 16 + // 16 hex digits => 64 bit number
51 : crlf_len_;
52 :
53 : // last-chunk = 1*("0") [ chunk-ext ] CRLF
54 : static
55 : constexpr
56 : std::size_t
57 : last_chunk_len_ =
58 : 1 + // "0"
59 : crlf_len_ +
60 : crlf_len_; // chunked-body termination requires an extra CRLF
61 :
62 : static
63 : constexpr
64 : std::size_t
65 : chunked_overhead_ =
66 : chunk_header_len_ +
67 : crlf_len_ + // closing chunk data
68 : last_chunk_len_;
69 : }
70 :
71 :
72 : #ifndef BOOST_HTTP_PROTO_DOCS
73 : class request;
74 : class response;
75 : class request_view;
76 : class response_view;
77 : class message_view_base;
78 : #endif
79 :
80 : /** A serializer for HTTP/1 messages
81 :
82 : This is used to serialize one or more complete
83 : HTTP/1 messages. Each message consists of a
84 : required header followed by an optional body.
85 : */
86 : class BOOST_SYMBOL_VISIBLE
87 : serializer
88 : {
89 : public:
90 : class const_buffers_type;
91 :
92 : struct stream;
93 :
94 : /** Destructor
95 : */
96 : BOOST_HTTP_PROTO_DECL
97 : ~serializer();
98 :
99 : /** Constructor
100 : */
101 : BOOST_HTTP_PROTO_DECL
102 : serializer();
103 :
104 : /** Constructor
105 : */
106 : BOOST_HTTP_PROTO_DECL
107 : serializer(
108 : serializer&&) noexcept;
109 :
110 : /** Constructor
111 : */
112 : BOOST_HTTP_PROTO_DECL
113 : explicit
114 : serializer(
115 : std::size_t buffer_size);
116 :
117 : BOOST_HTTP_PROTO_DECL
118 : serializer(
119 : context& ctx,
120 : std::size_t buffer_size);
121 :
122 : //--------------------------------------------
123 :
124 : /** Prepare the serializer for a new stream
125 : */
126 : BOOST_HTTP_PROTO_DECL
127 : void
128 : reset() noexcept;
129 :
130 : /** Prepare the serializer for a new message
131 :
132 : The message will not contain a body.
133 : Changing the contents of the message
134 : after calling this function and before
135 : @ref is_done returns `true` results in
136 : undefined behavior.
137 : */
138 : void
139 4 : start(
140 : message_view_base const& m)
141 : {
142 4 : start_empty(m);
143 4 : }
144 :
145 : /** Prepare the serializer for a new message
146 :
147 : Changing the contents of the message
148 : after calling this function and before
149 : @ref is_done returns `true` results in
150 : undefined behavior.
151 :
152 : @par Constraints
153 : @code
154 : is_const_buffers< ConstBuffers >::value == true
155 : @endcode
156 : */
157 : template<
158 : class ConstBufferSequence
159 : #ifndef BOOST_HTTP_PROTO_DOCS
160 : ,class = typename
161 : std::enable_if<
162 : buffers::is_const_buffer_sequence<
163 : ConstBufferSequence>::value
164 : >::type
165 : #endif
166 : >
167 : void
168 : start(
169 : message_view_base const& m,
170 : ConstBufferSequence&& body);
171 :
172 : /** Prepare the serializer for a new message
173 :
174 : Changing the contents of the message
175 : after calling this function and before
176 : @ref is_done returns `true` results in
177 : undefined behavior.
178 : */
179 : template<
180 : class Source,
181 : class... Args
182 : #ifndef BOOST_HTTP_PROTO_DOCS
183 : ,class = typename std::enable_if<
184 : is_source<Source>::value>::type
185 : #endif
186 : >
187 : Source&
188 : start(
189 : message_view_base const& m,
190 : Args&&... args);
191 :
192 : //--------------------------------------------
193 :
194 : /** Create a new stream object associated with the
195 : serializer.
196 :
197 : The returned stream must not outlive its backing
198 : serializer object.
199 :
200 : Streams permit a user to supply input data to
201 : the serializer using a bounded sequence of mutable
202 : buffers.
203 :
204 : \code{.cpp}
205 : std::string msg = "Hello, world!";
206 : std::size_t buf_size = 16 * 1024;
207 : http_proto::serializer sr(buf_size);
208 :
209 : auto stream = sr.start_stream();
210 : auto bufs = stream.prepare();
211 : auto s = buffers::buffer_size(bufs);
212 : auto n = buffers::buffer_copy(
213 : bufs,
214 : buffers::make_buffer(
215 : msg.data(),
216 : std::min(s, msg.size())));
217 : stream.commit(n);
218 :
219 : auto cbs = sr.prepare().value();
220 : // `cbs` contains the serialized octets corresponding
221 : // to our `msg`
222 : \endcode
223 : */
224 : BOOST_HTTP_PROTO_DECL
225 : stream
226 : start_stream(
227 : message_view_base const& m);
228 :
229 : //--------------------------------------------
230 :
231 : /** Return true if serialization is complete.
232 : */
233 : bool
234 78 : is_done() const noexcept
235 : {
236 78 : return is_done_;
237 : }
238 :
239 : /** Return the output area.
240 :
241 : This function will serialize some or
242 : all of the content and return the
243 : corresponding output buffers.
244 :
245 : @par Preconditions
246 : @code
247 : this->is_done() == false
248 : @endcode
249 : */
250 : BOOST_HTTP_PROTO_DECL
251 : auto
252 : prepare() ->
253 : system::result<
254 : const_buffers_type>;
255 :
256 : /** Consume bytes from the output area.
257 : */
258 : BOOST_HTTP_PROTO_DECL
259 : void
260 : consume(std::size_t n);
261 :
262 : private:
263 : static void copy(
264 : buffers::const_buffer*,
265 : buffers::const_buffer const*,
266 : std::size_t n) noexcept;
267 : auto
268 : make_array(std::size_t n) ->
269 : detail::array_of_const_buffers;
270 :
271 : auto
272 : make_array(detail::workspace& ws, std::size_t n) ->
273 : detail::array_of_const_buffers;
274 :
275 : template<
276 : class Source,
277 : class... Args,
278 : typename std::enable_if<
279 : std::is_constructible<
280 : Source,
281 : Args...>::value>::type* = nullptr>
282 : Source&
283 8 : construct_source(Args&&... args)
284 : {
285 8 : return ws_.emplace<Source>(
286 8 : std::forward<Args>(args)...);
287 : }
288 :
289 : template<
290 : class Source,
291 : class... Args,
292 : typename std::enable_if<
293 : std::is_constructible<
294 : Source,
295 : buffered_base::allocator&,
296 : Args...>::value>::type* = nullptr>
297 : Source&
298 : construct_source(Args&&... args)
299 : {
300 : buffered_base::allocator a(
301 : ws_.data(),
302 : (ws_.size() - ws_.space_needed<Source>()) / 2,
303 : false);
304 : auto& src = ws_.emplace<Source>(
305 : a, std::forward<Args>(args)...);
306 : ws_.reserve_front(a.size_used());
307 : return src;
308 : }
309 :
310 : BOOST_HTTP_PROTO_DECL void start_init(message_view_base const&);
311 : BOOST_HTTP_PROTO_DECL void start_empty(message_view_base const&);
312 : BOOST_HTTP_PROTO_DECL void start_buffers(message_view_base const&);
313 : BOOST_HTTP_PROTO_DECL void start_source(message_view_base const&, source*);
314 :
315 : enum class style
316 : {
317 : empty,
318 : buffers,
319 : source,
320 : stream
321 : };
322 :
323 : detail::workspace ws_;
324 : detail::workspace filter_ws_;
325 : detail::array_of_const_buffers buf_;
326 : filter* zlib_filter_ = nullptr;
327 : bool filter_done_ = false;
328 : source* src_;
329 : context* ctx_ = nullptr;
330 : buffers::circular_buffer tmp0_;
331 : buffers::circular_buffer tmp1_;
332 : detail::array_of_const_buffers out_;
333 :
334 : buffers::const_buffer* hp_; // header
335 :
336 : style st_;
337 : bool more_;
338 : bool is_done_;
339 : bool is_chunked_;
340 : bool is_expect_continue_;
341 : bool is_compressed_ = false;
342 : };
343 :
344 : //------------------------------------------------
345 :
346 : /**
347 : A proxy type used to pass bounded input to the
348 : associated serializer.
349 : */
350 : struct serializer::stream
351 : {
352 : /** Default constructor.
353 :
354 : Creates a stream without an associated serializer
355 : object.
356 : */
357 : stream() = default;
358 :
359 : /** Copy constructor.
360 :
361 : The constructed stream will share the same
362 : serializer as `other`.
363 : */
364 : stream(stream const& other) = default;
365 :
366 : /** Assignment operator
367 :
368 : The current stream will share the same serializer
369 : as `other`.
370 : */
371 : stream& operator= (
372 : stream const& other) = default;
373 :
374 : /**
375 : A MutableBufferSequence consisting of a buffer pair.
376 : */
377 : using buffers_type =
378 : buffers::mutable_buffer_pair;
379 :
380 : /**
381 : Returns the remaining available capacity.
382 :
383 : The returned value represents the available free
384 : space in the backing fixed-sized buffers used by the
385 : serializer associated with this stream.
386 :
387 : The capacity is absolute and does not do any
388 : accounting for any octets required by a chunked
389 : transfer encoding.
390 : */
391 : BOOST_HTTP_PROTO_DECL
392 : std::size_t
393 : capacity() const noexcept;
394 :
395 : /**
396 : Returns the number of octets serialized by this
397 : stream.
398 :
399 : The associated serializer stores stream output in its
400 : internal buffers. The stream returns the size of this
401 : output.
402 : */
403 : BOOST_HTTP_PROTO_DECL
404 : std::size_t
405 : size() const noexcept;
406 :
407 : /**
408 : Returns a boolean indicating if the stream can
409 : receive more input.
410 :
411 : The fixed-sized buffers maintained by the associated
412 : serializer can be sufficiently full from previous
413 : calls to \ref stream::commit.
414 :
415 : This function can be called to determine if the user
416 : should drain the serializer via \ref serializer::consume calls
417 : before attempting to fill the buffer sequence
418 : returned from \ref stream::prepare.
419 : */
420 : BOOST_HTTP_PROTO_DECL
421 : bool
422 : is_full() const noexcept;
423 :
424 : /**
425 : Returns a MutableBufferSequence capable of storing
426 : input from the user
427 :
428 : The returned buffer sequence is as wide as is
429 : possible. If a non-chunked transfer encoding is
430 : being used than the returned sequence encompasses
431 : the unused area of the serializer's fixed-sized
432 : buffers.
433 :
434 : If a chunked transer encoding is used then space is
435 : reserved for the chunk header in addition to the
436 : closing CRLF required for the chunk data. In
437 : addition to this, space is also reserved for the
438 : last-chunk.
439 :
440 : This is done so that users can chain calls to
441 : \ref stream::commit and \ref stream::close
442 : without having to drain the serializer via
443 : \ref serializer::consume calls.
444 :
445 : \exception std::length_error Thrown if the stream
446 : has insufficient capacity and a chunked transfer
447 : encoding is being used
448 : */
449 : BOOST_HTTP_PROTO_DECL
450 : buffers_type
451 : prepare() const;
452 :
453 : /**
454 : Serialize and commit `n` bytes.
455 :
456 : Once the sequence returned from \ref prepare has been
457 : filled, the input can be serialized and committed to the
458 : associated serializer's output area via a call to `commit(n)`.
459 :
460 : If a chunked transfer encoding is being used then commit
461 : is responsible for writing the chunk-header and also the
462 : closing CRLF for the chunk-data. `n` denotes the size
463 : of the chunk.
464 :
465 : \exception std::logic_error Thrown if commit is
466 : called with 0. Instead, the closing chunk must be
467 : written by a call to \ref stream::close.
468 : */
469 : BOOST_HTTP_PROTO_DECL
470 : void
471 : commit(std::size_t n) const;
472 :
473 : /**
474 : Close the stream.
475 :
476 : close() writes the last-chunk to the underlying buffers
477 : of the stream's associated serializer, i.e. `0\r\n\r\n`.
478 :
479 : \excpeption std::logic_error Thrown if the stream
480 : has been previously closed.
481 : */
482 : BOOST_HTTP_PROTO_DECL
483 : void
484 : close() const;
485 :
486 : private:
487 : friend class serializer;
488 :
489 : explicit
490 7 : stream(
491 : serializer& sr) noexcept
492 7 : : sr_(&sr)
493 : {
494 7 : }
495 :
496 : serializer* sr_ = nullptr;
497 : };
498 :
499 : //---------------------------------------------------------
500 :
501 : /** A ConstBufferSequence representing the output
502 : */
503 : class serializer::
504 : const_buffers_type
505 : {
506 : std::size_t n_ = 0;
507 : buffers::const_buffer const* p_ = nullptr;
508 :
509 : friend class serializer;
510 :
511 63 : const_buffers_type(
512 : buffers::const_buffer const* p,
513 : std::size_t n) noexcept
514 63 : : n_(n)
515 63 : , p_(p)
516 : {
517 63 : }
518 :
519 : public:
520 : using iterator = buffers::const_buffer const*;
521 : using const_iterator = iterator;
522 : using value_type = buffers::const_buffer;
523 : using reference = buffers::const_buffer;
524 : using const_reference = buffers::const_buffer;
525 : using size_type = std::size_t;
526 : using difference_type = std::ptrdiff_t;
527 :
528 : const_buffers_type() = default;
529 : const_buffers_type(
530 : const_buffers_type const&) = default;
531 : const_buffers_type& operator=(
532 : const_buffers_type const&) = default;
533 :
534 : iterator
535 126 : begin() const noexcept
536 : {
537 126 : return p_;
538 : }
539 :
540 : iterator
541 126 : end() const noexcept
542 : {
543 126 : return p_ + n_;
544 : }
545 : };
546 :
547 : //------------------------------------------------
548 :
549 : template<
550 : class ConstBufferSequence,
551 : class>
552 : void
553 7 : serializer::
554 : start(
555 : message_view_base const& m,
556 : ConstBufferSequence&& body)
557 : {
558 7 : start_init(m);
559 7 : auto& ws = (is_compressed_ ? filter_ws_ : ws_);
560 :
561 : auto const& bs =
562 7 : ws.emplace<ConstBufferSequence>(
563 : std::forward<ConstBufferSequence>(body));
564 :
565 7 : std::size_t n = std::distance(
566 : buffers::begin(bs),
567 : buffers::end(bs));
568 :
569 7 : buf_ = make_array(ws, n);
570 7 : auto p = buf_.data();
571 14 : for(buffers::const_buffer b : buffers::range(bs))
572 7 : *p++ = b;
573 :
574 7 : start_buffers(m);
575 7 : }
576 :
577 : template<
578 : class Source,
579 : class... Args,
580 : class>
581 : Source&
582 8 : serializer::
583 : start(
584 : message_view_base const& m,
585 : Args&&... args)
586 : {
587 : static_assert(
588 : !std::is_abstract<Source>::value,
589 : "The Source must be non-abstract, i.e. implements: `auto on_read(buffers::mutable_buffer b) -> http_proto::results;`");
590 : static_assert(
591 : std::is_constructible<Source, Args...>::value ||
592 : std::is_constructible<Source, buffered_base::allocator&, Args...>::value,
593 : "The Source cannot be constructed with the given arguments");
594 :
595 8 : start_init(m);
596 8 : auto& src = construct_source<Source>(
597 : std::forward<Args>(args)...);
598 8 : start_source(m, std::addressof(src));
599 8 : return src;
600 : }
601 :
602 : //------------------------------------------------
603 :
604 : inline
605 : auto
606 26 : serializer::
607 : make_array(std::size_t n) ->
608 : detail::array_of_const_buffers
609 : {
610 : return {
611 26 : ws_.push_array(n,
612 26 : buffers::const_buffer{}),
613 26 : n };
614 : }
615 :
616 : inline
617 : auto
618 7 : serializer::
619 : make_array(detail::workspace& ws, std::size_t n) ->
620 : detail::array_of_const_buffers
621 : {
622 7 : return {ws.push_array(n, buffers::const_buffer{}), n};
623 : }
624 :
625 : } // http_proto
626 : } // boost
627 :
628 : #endif
|