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