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 serializer
69 : {
70 : public:
71 : using const_buffers_type = buffers::const_buffer_span;
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 1603 : is_done() const noexcept
195 : {
196 1603 : 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 25 : construct_source(Args&&... args)
262 : {
263 25 : return ws_.emplace<Source>(
264 25 : 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 : detail::workspace&,
274 : Args...>::value>::type* = nullptr>
275 : Source&
276 : construct_source(Args&&... args)
277 : {
278 : return ws_.emplace<Source>(
279 : ws_, std::forward<Args>(args)...);
280 : }
281 :
282 : BOOST_HTTP_PROTO_DECL void start_init(message_view_base const&);
283 : BOOST_HTTP_PROTO_DECL void start_empty(message_view_base const&);
284 : BOOST_HTTP_PROTO_DECL void start_buffers(message_view_base const&);
285 : BOOST_HTTP_PROTO_DECL void start_source(message_view_base const&, source*);
286 :
287 : enum class style
288 : {
289 : empty,
290 : buffers,
291 : source,
292 : stream
293 : };
294 :
295 : // chunked-body = *chunk
296 : // last-chunk
297 : // trailer-section
298 : // CRLF
299 :
300 : static
301 : constexpr
302 : std::size_t
303 : crlf_len_ = 2;
304 :
305 : // chunk = chunk-size [ chunk-ext ] CRLF
306 : // chunk-data CRLF
307 : static
308 : constexpr
309 : std::size_t
310 : chunk_header_len_ =
311 : 16 + // 16 hex digits => 64 bit number
312 : crlf_len_;
313 :
314 : // last-chunk = 1*("0") [ chunk-ext ] CRLF
315 : static
316 : constexpr
317 : std::size_t
318 : last_chunk_len_ =
319 : 1 + // "0"
320 : crlf_len_ +
321 : crlf_len_; // chunked-body termination requires an extra CRLF
322 :
323 : static
324 : constexpr
325 : std::size_t
326 : chunked_overhead_ =
327 : chunk_header_len_ +
328 : crlf_len_ + // closing chunk data
329 : last_chunk_len_;
330 :
331 : detail::workspace ws_;
332 : detail::array_of_const_buffers buf_;
333 : detail::filter* filter_ = nullptr;
334 : source* src_;
335 : context& ctx_;
336 : buffers::circular_buffer tmp0_;
337 : buffers::circular_buffer tmp1_;
338 : detail::array_of_const_buffers prepped_;
339 :
340 : buffers::mutable_buffer chunk_header_;
341 : buffers::mutable_buffer chunk_close_;
342 : buffers::mutable_buffer last_chunk_;
343 :
344 : buffers::circular_buffer* in_ = nullptr;
345 : buffers::circular_buffer* out_ = nullptr;
346 :
347 : buffers::const_buffer* hp_; // header
348 :
349 : style st_;
350 : bool more_;
351 : bool is_done_;
352 : bool is_header_done_;
353 : bool is_chunked_;
354 : bool is_expect_continue_;
355 : bool is_compressed_ = false;
356 : bool filter_done_ = false;
357 : };
358 :
359 : //------------------------------------------------
360 :
361 : /** The type used for caller-provided body data during
362 : serialization.
363 :
364 : @code{.cpp}
365 : http_proto::serializer sr(128);
366 :
367 : http_proto::request req;
368 : auto stream = sr.start_stream(req);
369 :
370 : std::string_view msg = "Hello, world!";
371 : auto n = buffers::copy(
372 : stream.prepare(),
373 : buffers::make_buffer(
374 : msg.data(), msg.size()));
375 :
376 : stream.commit(n);
377 :
378 : auto cbs = sr.prepare().value();
379 : (void)cbs;
380 : @endcode
381 : */
382 : struct serializer::stream
383 : {
384 : /** Constructor.
385 :
386 : The only valid operations on default constructed
387 : streams are assignment and destruction.
388 : */
389 : stream() = default;
390 :
391 : /** Constructor.
392 :
393 : The constructed stream will share the same
394 : serializer as `other`.
395 : */
396 : stream(stream const& other) = default;
397 :
398 : /** Assignment.
399 :
400 : The current stream will share the same serializer
401 : as `other`.
402 : */
403 : stream& operator= (
404 : stream const& other) = default;
405 :
406 : /** A MutableBufferSequence consisting of a buffer pair.
407 : */
408 : using buffers_type =
409 : buffers::mutable_buffer_pair;
410 :
411 : /** Returns the remaining available capacity.
412 :
413 : The returned value represents the available free
414 : space in the backing fixed-sized buffers used by the
415 : serializer associated with this stream.
416 :
417 : The capacity is absolute and does not do any
418 : accounting for any octets required by a chunked
419 : transfer encoding.
420 : */
421 : BOOST_HTTP_PROTO_DECL
422 : std::size_t
423 : capacity() const noexcept;
424 :
425 : /** Returns the number of octets serialized by this
426 : stream.
427 :
428 : The associated serializer stores stream output in its
429 : internal buffers. The stream returns the size of this
430 : output.
431 : */
432 : BOOST_HTTP_PROTO_DECL
433 : std::size_t
434 : size() const noexcept;
435 :
436 : /** Return true if the stream cannot currently hold
437 : additional output data.
438 :
439 : The fixed-sized buffers maintained by the associated
440 : serializer can be sufficiently full from previous
441 : calls to @ref stream::commit.
442 :
443 : This function can be called to determine if the caller
444 : should drain the serializer via @ref serializer::consume calls
445 : before attempting to fill the buffer sequence
446 : returned from @ref stream::prepare.
447 : */
448 : BOOST_HTTP_PROTO_DECL
449 : bool
450 : is_full() const noexcept;
451 :
452 : /** Returns a MutableBufferSequence for storing
453 : serializer input. If `n` bytes are written to the
454 : buffer sequence, @ref stream::commit must be called
455 : with `n` to update the backing serializer's buffers.
456 :
457 : The returned buffer sequence is as wide as is
458 : possible.
459 :
460 : @exception std::length_error Thrown if the stream
461 : has insufficient capacity and a chunked transfer
462 : encoding is being used
463 : */
464 : BOOST_HTTP_PROTO_DECL
465 : buffers_type
466 : prepare() const;
467 :
468 : /** Make `n` bytes available to the serializer.
469 :
470 : Once the buffer sequence returned from @ref stream::prepare
471 : has been filled, the input can be marked as ready
472 : for serialization by using this function.
473 :
474 : @exception std::logic_error Thrown if `commit` is
475 : called with 0.
476 : */
477 : BOOST_HTTP_PROTO_DECL
478 : void
479 : commit(std::size_t n) const;
480 :
481 : /** Indicate that no more data is coming and that the
482 : body should be treated as complete.
483 :
484 : @excpeption std::logic_error Thrown if the stream
485 : has been previously closed.
486 : */
487 : BOOST_HTTP_PROTO_DECL
488 : void
489 : close() const;
490 :
491 : private:
492 : friend class serializer;
493 :
494 : explicit
495 22 : stream(
496 : serializer& sr) noexcept
497 22 : : sr_(&sr)
498 : {
499 22 : }
500 :
501 : serializer* sr_ = nullptr;
502 : };
503 :
504 : //---------------------------------------------------------
505 :
506 : template<
507 : class ConstBufferSequence,
508 : class>
509 : void
510 24 : serializer::
511 : start(
512 : message_view_base const& m,
513 : ConstBufferSequence&& body)
514 : {
515 24 : start_init(m);
516 : auto const& bs =
517 24 : ws_.emplace<ConstBufferSequence>(
518 : std::forward<ConstBufferSequence>(body));
519 :
520 24 : std::size_t n = std::distance(
521 : buffers::begin(bs),
522 : buffers::end(bs));
523 :
524 24 : buf_ = make_array(n);
525 24 : auto p = buf_.data();
526 416 : for(buffers::const_buffer b : buffers::range(bs))
527 392 : *p++ = b;
528 :
529 24 : start_buffers(m);
530 24 : }
531 :
532 : template<
533 : class Source,
534 : class... Args,
535 : class>
536 : Source&
537 25 : serializer::
538 : start(
539 : message_view_base const& m,
540 : Args&&... args)
541 : {
542 : static_assert(
543 : !std::is_abstract<Source>::value, "");
544 : static_assert(
545 : std::is_constructible<Source, Args...>::value ||
546 : std::is_constructible<Source, detail::workspace&, Args...>::value,
547 : "The Source cannot be constructed with the given arguments");
548 :
549 25 : start_init(m);
550 25 : auto& src = construct_source<Source>(
551 : std::forward<Args>(args)...);
552 25 : start_source(m, std::addressof(src));
553 25 : return src;
554 : }
555 :
556 : //------------------------------------------------
557 :
558 : inline
559 : auto
560 99 : serializer::
561 : make_array(std::size_t n) ->
562 : detail::array_of_const_buffers
563 : {
564 : return {
565 99 : ws_.push_array(n,
566 99 : buffers::const_buffer{}),
567 198 : n };
568 : }
569 :
570 : } // http_proto
571 : } // boost
572 :
573 : #endif
|