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