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