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