RESTinio
Loading...
Searching...
No Matches
message_builders.hpp
Go to the documentation of this file.
1/*
2 restinio
3*/
4
5/*!
6 Builders for messages.
7*/
8
9#pragma once
10
11#include <ctime>
12#include <chrono>
13
14#include <restinio/impl/include_fmtlib.hpp>
15
16#include <restinio/common_types.hpp>
17#include <restinio/http_headers.hpp>
18#include <restinio/os.hpp>
19#include <restinio/sendfile.hpp>
20#include <restinio/impl/connection_base.hpp>
21
22#include <restinio/impl/header_helpers.hpp>
23
24namespace restinio
25{
26
27//
28// make_date_field_value()
29//
30
31//! Format a timepoint to a string of a propper format.
32inline std::string
33make_date_field_value( std::time_t t )
34{
35 const auto tpoint = make_gmtime( t );
36
37 std::array< char, 64 > buf;
38 // TODO: is there a faster way to get time string?
39 strftime(
40 buf.data(),
41 buf.size(),
42 "%a, %d %b %Y %H:%M:%S GMT",
43 &tpoint );
44
45 return std::string{ buf.data() };
46}
47
48inline std::string
49make_date_field_value( std::chrono::system_clock::time_point tp )
50{
51 return make_date_field_value( std::chrono::system_clock::to_time_t( tp ) );
52}
53
54//
55// base_response_builder_t
56//
57
58template < typename Response_Builder >
60{
61 public:
64
67
68 virtual ~base_response_builder_t() = default;
69
71 http_status_line_t status_line,
72 impl::connection_handle_t connection,
73 request_id_t request_id,
74 bool should_keep_alive )
77 , m_request_id{ request_id }
78 {
79 m_header.should_keep_alive( should_keep_alive );
80 }
81
82 //! Accessors for header.
83 //! \{
84 http_response_header_t &
85 header() noexcept
86 {
87 return m_header;
88 }
89
90 const http_response_header_t &
91 header() const noexcept
92 {
93 return m_header;
94 }
95 //! \}
96
97 //! Add header field.
98 Response_Builder &
100 std::string field_name,
101 std::string field_value ) &
102 {
103 m_header.add_field(
104 std::move( field_name ),
105 std::move( field_value ) );
106 return upcast_reference();
107 }
108
109 //! Add header field.
110 Response_Builder &&
112 std::string field_name,
113 std::string field_value ) &&
114 {
115 return std::move( this->append_header(
116 std::move( field_name ),
117 std::move( field_value ) ) );
118 }
119
120 //! Add header field.
121 Response_Builder &
122 append_header( http_header_field_t http_header_field ) &
123 {
124 m_header.add_field( std::move( http_header_field ) );
125 return upcast_reference();
126 }
127
128 //! Add header field.
129 Response_Builder &&
130 append_header( http_header_field_t http_header_field ) &&
131 {
132 return std::move( this->append_header(
133 std::move( http_header_field ) ) );
134 }
135
136 //! Add header field.
137 Response_Builder &
139 http_field_t field_id,
140 std::string field_value ) &
141 {
142 m_header.add_field(
143 field_id,
144 std::move( field_value ) );
145 return upcast_reference();
146 }
147
148 //! Add header field.
149 Response_Builder &&
151 http_field_t field_id,
152 std::string field_value ) &&
153 {
154 return std::move( this->append_header(
155 field_id,
156 std::move( field_value ) ) );
157 }
158
159
160 //! Add header `Date` field.
161 Response_Builder &
163 std::chrono::system_clock::time_point tp =
164 std::chrono::system_clock::now() ) &
165 {
166 m_header.set_field( http_field_t::date, make_date_field_value( tp ) );
167 return upcast_reference();
168 }
169
170 //! Add header `Date` field.
171 Response_Builder &&
173 std::chrono::system_clock::time_point tp =
174 std::chrono::system_clock::now() ) &&
175 {
176 return std::move( this->append_header_date_field( tp ) );
177 }
178
179 //! Set connection close.
180 Response_Builder &
181 connection_close() & noexcept
182 {
183 m_header.should_keep_alive( false );
184 return upcast_reference();
185 }
186
187 //! Set connection close.
188 Response_Builder &&
189 connection_close() && noexcept
190 {
191 return std::move( this->connection_close() );
192 }
193
194
195 //! Set connection keep-alive.
196 Response_Builder &
198 {
199 m_header.should_keep_alive();
200 return upcast_reference();
201 }
202
203 Response_Builder &&
205 {
206 return std::move( this->connection_keep_alive() );
207 }
208
209 protected:
210 std::size_t
212 {
213 // "HTTP/1.1 *** <reason-phrase>"
214 return 8 + 1 + 3 + 1 + m_header.status_line().reason_phrase().size();
215 }
216
217 http_response_header_t m_header;
218
221
222 void
224 {
225 throw exception_t{ "done() cannot be called twice" };
226 }
227
228 private:
229 Response_Builder &
231 {
232 return static_cast< Response_Builder & >( *this );
233 }
234};
235
236//
237// response_builder_t
238//
239
240//! Forbid arbitrary response_builder_t instantiations.
241template < typename Response_Output_Strategy >
243{
245};
246
247//! Tag type for RESTinio controlled output response builder.
249
250//! Simple standard response builder.
251/*!
252 Requires user to set header and body.
253 Content length is automatically calculated.
254 Once the data is ready, the user calls done() method
255 and the resulting response is scheduled for sending.
256*/
257template <>
258class response_builder_t< restinio_controlled_output_t > final
259 : public base_response_builder_t< response_builder_t< restinio_controlled_output_t > >
260{
261 public:
262 using base_type_t =
264 using self_type_t =
266
267 response_builder_t( response_builder_t && ) = default;
268
269 // Reuse construstors from base.
270 using base_type_t::base_type_t;
271
272 //! Set body.
273 self_type_t &
274 set_body( writable_item_t body ) &
275 {
276 auto size = body.size();
277 return set_body_impl( body, size );
278 }
279
280 //! Set body.
281 self_type_t &&
282 set_body( writable_item_t body ) &&
283 {
284 return std::move( this->set_body( std::move( body ) ) );
285 }
286
287 //! Append body.
288 self_type_t &
289 append_body( writable_item_t body_part ) &
290 {
291 auto size = body_part.size();
292 return append_body_impl( body_part, size );
293 }
294
295 //! Append body.
296 self_type_t &&
297 append_body( writable_item_t body_part ) &&
298 {
299 return std::move( this->append_body( std::move( body_part ) ) );
300 }
301
302 //! Complete response.
304 done( write_status_cb_t wscb = write_status_cb_t{} )
305 {
306 if( m_connection )
307 {
309 response_output_flags{
310 response_parts_attr_t::final_parts,
311 response_connection_attr( m_header.should_keep_alive() ) };
312
313 m_header.content_length( m_body_size );
314
315 if_neccessary_reserve_first_element_for_header();
316
317 m_response_parts[ 0 ] =
318 writable_item_t{ impl::create_header_string( m_header ) };
319
320 write_group_t wg{ std::move( m_response_parts ) };
321 wg.status_line_size( calculate_status_line_size() );
322
323 if( wscb )
324 {
325 wg.after_write_notificator( std::move( wscb ) );
326 }
327
328 auto conn = std::move( m_connection );
329
330 conn->write_response_parts(
331 m_request_id,
332 response_output_flags,
333 std::move( wg ) );
334 }
335 else
336 {
337 throw_done_must_be_called_once();
338 }
339
341 }
342
343 private:
344 self_type_t &
345 set_body_impl( writable_item_t & body, std::size_t body_size )
346 {
347 if_neccessary_reserve_first_element_for_header();
348
349 // Leave only buf that is reserved for header,
350 // so forget all the previous data.
351 m_response_parts.resize( 1 );
352
353 if( 0 < body_size )
354 {
355 m_response_parts.emplace_back( std::move( body ) );
356 }
357
358 m_body_size = body_size;
359
360 return *this;
361 }
362
363 self_type_t &
364 append_body_impl( writable_item_t & body_part, std::size_t append_size )
365 {
366 if_neccessary_reserve_first_element_for_header();
367
368 if( 0 < append_size )
369 {
370 m_response_parts.emplace_back( std::move( body_part ) );
371 m_body_size += append_size;
372 }
373
374 return *this;
375 }
376
377 void
378 if_neccessary_reserve_first_element_for_header()
379 {
380 if( m_response_parts.empty() )
381 {
382 m_response_parts.reserve( 2 );
383 m_response_parts.emplace_back();
384 }
385 }
386
387 std::size_t m_body_size{ 0 };
388 writable_items_container_t m_response_parts;
389};
390
391//! Tag type for user controlled output response builder.
393
394//! User controlled response output builder.
395/*!
396 This type of output allows user
397 to send body divided into parts.
398 But it is up to user to set the correct
399 Content-Length field.
400*/
401template <>
402class response_builder_t< user_controlled_output_t > final
403 : public base_response_builder_t< response_builder_t< user_controlled_output_t > >
404{
405 public:
406 using base_type_t =
408 using self_type_t =
410
411 response_builder_t( response_builder_t && ) = default;
412
413 // Reuse construstors from base.
414 using base_type_t::base_type_t;
415
416 //! Manualy set content length.
417 self_type_t &
418 set_content_length( std::size_t content_length ) &
419 {
420 m_header.content_length( content_length );
421 return *this;
422 }
423
424 //! Manualy set content length.
425 self_type_t &&
426 set_content_length( std::size_t content_length ) &&
427 {
428 return std::move( this->set_content_length( content_length ) );
429 }
430
431 //! Set body (part).
432 self_type_t &
433 set_body( writable_item_t body ) &
434 {
435 auto size = body.size();
436 return set_body_impl( body, size );
437 }
438
439 //! Set body (part).
440 self_type_t &&
441 set_body( writable_item_t body ) &&
442 {
443 return std::move( this->set_body( std::move( body ) ) );
444 }
445
446 //! Append body.
447 self_type_t &
448 append_body( writable_item_t body_part ) &
449 {
450 auto size = body_part.size();
451
452 if( 0 == size )
453 return *this;
454
455 return append_body_impl( body_part );
456 }
457
458 //! Append body.
459 self_type_t &&
460 append_body( writable_item_t body_part ) &&
461 {
462 return std::move( this->append_body( std::move( body_part ) ) );
463 }
464
465 //! Flush ready outgoing data.
466 /*!
467 Schedules for sending currently ready data.
468 */
469 self_type_t &
470 flush( write_status_cb_t wscb = write_status_cb_t{} ) &
471 {
472 if( m_connection )
473 {
474 send_ready_data(
475 m_connection,
476 response_parts_attr_t::not_final_parts,
477 std::move( wscb ) );
478 }
479
480 return *this;
481 }
482
483 //! Flush ready outgoing data.
484 self_type_t &&
485 flush( write_status_cb_t wscb = write_status_cb_t{} ) &&
486 {
487 return std::move( this->flush( std::move( wscb ) ) );
488 }
489
490 //! Complete response.
492 done( write_status_cb_t wscb = write_status_cb_t{} )
493 {
494 if( m_connection )
495 {
496 // Note: m_connection should become empty after return
497 // from that method.
498 impl::connection_handle_t old_conn_handle{
499 std::move(m_connection) };
500 send_ready_data(
501 old_conn_handle,
503 std::move( wscb ) );
504 }
505 else
506 {
507 throw_done_must_be_called_once();
508 }
509
511 }
512
513 private:
514 void
515 send_ready_data(
516 const impl::connection_handle_t & conn,
517 response_parts_attr_t response_parts_attr,
518 write_status_cb_t wscb )
519 {
520 std::size_t status_line_size{ 0 };
521
522 if( !m_header_was_sent )
523 {
524 m_should_keep_alive_when_header_was_sent =
525 m_header.should_keep_alive();
526
527 if_neccessary_reserve_first_element_for_header();
528
529 m_response_parts[ 0 ] =
530 writable_item_t{ impl::create_header_string( m_header ) };
531
532 m_header_was_sent = true;
533 status_line_size = calculate_status_line_size();
534 }
535
536 if( !m_response_parts.empty() ||
537 wscb ||
538 response_parts_attr_t::final_parts == response_parts_attr )
539 {
541 response_output_flags{
542 response_parts_attr,
543 response_connection_attr( m_should_keep_alive_when_header_was_sent ) };
544
545 write_group_t wg{ std::move( m_response_parts ) };
546 wg.status_line_size( status_line_size );
547
548 if( wscb )
549 {
550 wg.after_write_notificator( std::move( wscb ) );
551 }
552
553 conn->write_response_parts(
554 m_request_id,
555 response_output_flags,
556 std::move( wg ) );
557 }
558 }
559
560 self_type_t &
561 set_body_impl( writable_item_t & body, std::size_t body_size )
562 {
563 if_neccessary_reserve_first_element_for_header();
564
565 // Leave only buf that is reserved for header,
566 // so forget all the previous data.
567 if( !m_header_was_sent )
568 m_response_parts.resize( 1 );
569 else
570 m_response_parts.resize( 0 );
571
572 if( 0 < body_size )
573 {
574 // if body is not empty:
575 m_response_parts.emplace_back( std::move( body ) );
576 }
577
578 return *this;
579 }
580
581 self_type_t &
582 append_body_impl( writable_item_t & body_part )
583 {
584 if_neccessary_reserve_first_element_for_header();
585
586 m_response_parts.emplace_back( std::move( body_part ) );
587 return *this;
588 }
589
590 void
591 if_neccessary_reserve_first_element_for_header()
592 {
593 if( !m_header_was_sent && m_response_parts.empty() )
594 {
595 m_response_parts.reserve( 2 );
596 m_response_parts.emplace_back();
597 }
598 }
599
600
601 //! Flag used by flush() function.
602 bool m_header_was_sent{ false };
603
604 //! Saved keep_alive attr actual at the point
605 //! a header data was sent.
606 /*!
607 It is neccessary to guarantee that all parts of response
608 will have the same response-connection-attr
609 (keep-alive or close);
610 */
611 bool m_should_keep_alive_when_header_was_sent{ true };
612
613 //! Body accumulator.
614 /*!
615 For this type of output it contains a part of a body.
616 On each flush it is cleared.
617 */
618 writable_items_container_t m_response_parts;
619};
620
621//! Tag type for chunked output response builder.
623
624//! Chunked transfer encoding output builder.
625/*!
626 This type of output sets transfer-encoding to chunked
627 and expects user to set body using chunks of data.
628*/
629template <>
630class response_builder_t< chunked_output_t > final
631 : public base_response_builder_t< response_builder_t< chunked_output_t > >
632{
633 public:
634 using base_type_t =
636 using self_type_t =
638
639 response_builder_t(
640 http_status_line_t status_line,
641 impl::connection_handle_t connection,
642 request_id_t request_id,
643 bool should_keep_alive )
644 : base_type_t{
645 std::move( status_line ),
646 std::move( connection ),
647 request_id,
648 should_keep_alive }
649 {
650 m_chunks.reserve( 4 );
651 }
652
653 response_builder_t( response_builder_t && ) = default;
654
655 //! Append current chunk.
656 self_type_t &
657 append_chunk( writable_item_t chunk ) &
658 {
659 auto size = chunk.size();
660
661 if( 0 != size )
662 m_chunks.emplace_back( std::move( chunk ) );
663
664 return *this;
665 }
666
667 //! Append current chunk.
668 self_type_t &&
669 append_chunk( writable_item_t chunk ) &&
670 {
671 return std::move( this->append_chunk( std::move( chunk ) ) );
672 }
673
674 //! Flush ready outgoing data.
675 /*!
676 Schedules for sending currently ready data.
677 */
678 self_type_t &
679 flush( write_status_cb_t wscb = write_status_cb_t{} ) &
680 {
681 if( m_connection )
682 {
683 send_ready_data(
684 m_connection,
685 response_parts_attr_t::not_final_parts,
686 std::move( wscb ) );
687 }
688
689 return *this;
690 }
691
692 //! Flush ready outgoing data.
693 self_type_t &&
694 flush( write_status_cb_t wscb = write_status_cb_t{} ) &&
695 {
696 return std::move( this->flush( std::move( wscb ) ) );
697 }
698
699 //! Complete response.
701 done( write_status_cb_t wscb = write_status_cb_t{} )
702 {
703 if( m_connection )
704 {
705 // Note: m_connection should become empty after return
706 // from that method.
707 impl::connection_handle_t old_conn_handle{
708 std::move(m_connection) };
709 send_ready_data(
710 old_conn_handle,
712 std::move( wscb ) );
713 }
714 else
715 {
716 throw_done_must_be_called_once();
717 }
718
720 }
721
722 private:
723 void
724 send_ready_data(
725 const impl::connection_handle_t & conn,
726 response_parts_attr_t response_parts_attr,
727 write_status_cb_t wscb )
728 {
729 std::size_t status_line_size{ 0 };
730 if( !m_header_was_sent )
731 {
732 status_line_size = calculate_status_line_size();
733 prepare_header_for_sending();
734 }
735
736 auto bufs = create_bufs( response_parts_attr_t::final_parts == response_parts_attr );
737 m_header_was_sent = true;
738
740 response_output_flags{
741 response_parts_attr,
742 response_connection_attr( m_should_keep_alive_when_header_was_sent ) };
743
744 // We have buffers or at least we have after-write notificator.
745 if( !bufs.empty() || wscb )
746 {
747 write_group_t wg{ std::move( bufs ) };
748 wg.status_line_size( status_line_size );
749
750 if( wscb )
751 {
752 wg.after_write_notificator( std::move( wscb ) );
753 }
754
755 conn->write_response_parts(
756 m_request_id,
757 response_output_flags,
758 std::move( wg ) );
759 }
760 }
761
762 void
763 prepare_header_for_sending()
764 {
765 m_should_keep_alive_when_header_was_sent =
766 m_header.should_keep_alive();
767
768 constexpr const char value[] = "chunked";
769 if( !m_header.has_field( restinio::http_field::transfer_encoding ) )
770 {
771 m_header.add_field(
772 restinio::http_field::transfer_encoding,
773 std::string{ value, impl::ct_string_len( value ) } );
774 }
775 else
776 {
777 auto & current_value =
778 m_header.get_field( restinio::http_field::transfer_encoding );
779 if( std::string::npos == current_value.find( value ) )
780 {
781 constexpr const char comma_value[] = ",chunked";
782 m_header.append_field(
783 restinio::http_field::transfer_encoding,
784 std::string{
785 comma_value,
786 impl::ct_string_len( comma_value ) } );
787 }
788 }
789 }
790
791 writable_items_container_t
792 create_bufs( bool add_zero_chunk )
793 {
794 writable_items_container_t bufs;
795
796 std::size_t reserve_size = 2 * m_chunks.size() + 1;
797
798 if( !m_header_was_sent )
799 {
800 ++reserve_size;
801 }
802 if( add_zero_chunk )
803 {
804 ++reserve_size;
805 }
806
807 bufs.reserve( reserve_size );
808
809 if( !m_header_was_sent )
810 {
811 bufs.emplace_back(
812 impl::create_header_string(
813 m_header,
814 impl::content_length_field_presence_t::skip_content_length ) );
815 }
816
817 // Since fmtlib-8.0.0 compile-time checks of format strings
818 // is enabled by default. If non-constexpr string has to be
819 // passed as format_string then fmt::runtime() function should
820 // be used. But there are some drawbacks:
821 // - fmt::runtime is added only in fmtlib-8.0.0, there is no
822 // such a function in previous versions of fmtlib;
823 // - using fmt::runtime we lack a possibility to check
824 // format_string in compile-time and can have some performance
825 // penalty in run-time.
826 //
827 // Because of that, the following code was rewritten to have different
828 // code fragments for the first item in m_chunks and all successive
829 // items.
830 auto chunk_it = m_chunks.begin();
831 const auto chunk_end = m_chunks.end();
832 if( chunk_it != chunk_end )
833 {
834 bufs.emplace_back(
835 fmt::format(
836 RESTINIO_FMT_FORMAT_STRING( "{:X}\r\n" ),
837 asio_ns::buffer_size( chunk_it->buf() ) ) );
838 bufs.emplace_back( std::move( *chunk_it ) );
839
840 for( ++chunk_it; chunk_it != chunk_end; ++chunk_it )
841 {
842 bufs.emplace_back(
843 fmt::format(
844 RESTINIO_FMT_FORMAT_STRING( "\r\n{:X}\r\n" ),
845 asio_ns::buffer_size( chunk_it->buf() ) ) );
846 bufs.emplace_back( std::move( *chunk_it ) );
847 }
848 }
849
850 const char * const ending_representation = "\r\n" "0\r\n\r\n";
851 const char * appendix_begin = ending_representation + 2;
852 const char * appendix_end = appendix_begin;
853
854 if( !m_chunks.empty() )
855 {
856 // Add "\r\n"part to appendix.
857 appendix_begin -= 2;
858 // bufs.emplace_back( const_buffer( rn_ending, 2 ) );
859 }
860
861 if( add_zero_chunk )
862 {
863 // Add "0\r\n\r\n"part to appendix.
864 appendix_end += 5;
865 }
866
867 if( appendix_begin != appendix_end )
868 {
869 bufs.emplace_back( const_buffer(
870 appendix_begin,
871 static_cast<std::size_t>(appendix_end - appendix_begin)
872 ) );
873 }
874
875 m_chunks.clear();
876
877 return bufs;
878 }
879
880 //! Flag used by flush() function.
881 bool m_header_was_sent{ false };
882
883 //! Saved keep_alive attr actual at the point
884 //! a header data was sent.
885 /*!
886 It is neccessary to guarantee that all parts of response
887 will have the same response-connection-attr
888 (keep-alive or close);
889 */
890 bool m_should_keep_alive_when_header_was_sent{ true };
891
892 //! Chunks accumulator.
893 writable_items_container_t m_chunks;
894};
895
896} /* namespace restinio */
base_response_builder_t & operator=(base_response_builder_t &&) noexcept=default
Response_Builder & upcast_reference() noexcept
Response_Builder && append_header(http_field_t field_id, std::string field_value) &&
Add header field.
Response_Builder && append_header(http_header_field_t http_header_field) &&
Add header field.
impl::connection_handle_t m_connection
Response_Builder && append_header(std::string field_name, std::string field_value) &&
Add header field.
Response_Builder & append_header(std::string field_name, std::string field_value) &
Add header field.
Response_Builder & connection_close() &noexcept
Set connection close.
std::size_t calculate_status_line_size() const noexcept
base_response_builder_t(base_response_builder_t &&) noexcept=default
Response_Builder & append_header(http_header_field_t http_header_field) &
Add header field.
Response_Builder & append_header(http_field_t field_id, std::string field_value) &
Add header field.
Response_Builder & connection_keep_alive() &noexcept
Set connection keep-alive.
const http_response_header_t & header() const noexcept
Response_Builder & append_header_date_field(std::chrono::system_clock::time_point tp=std::chrono::system_clock::now()) &
Add header Date field.
Response_Builder && append_header_date_field(std::chrono::system_clock::time_point tp=std::chrono::system_clock::now()) &&
Add header Date field.
base_response_builder_t(http_status_line_t status_line, impl::connection_handle_t connection, request_id_t request_id, bool should_keep_alive)
base_response_builder_t & operator=(const base_response_builder_t &)=delete
base_response_builder_t(const base_response_builder_t &)=delete
http_response_header_t & header() noexcept
Accessors for header.
Response_Builder && connection_close() &&noexcept
Set connection close.
Response_Builder && connection_keep_alive() &&noexcept
virtual ~base_response_builder_t()=default
Exception class for all exceptions thrown by RESTinio.
Definition exception.hpp:26
exception_t(const char *err)
Definition exception.hpp:29
A single header field.
HTTP response header status line.
Forbid arbitrary response_builder_t instantiations.
Class for storing the buffers used for streaming body (request/response).
Definition buffers.hpp:524
std::size_t size() const
Get the size of the underlying buffer object.
Definition buffers.hpp:616
Group of writable items transported to the context of underlying connection as one solid piece.
Definition buffers.hpp:727
void status_line_size(std::size_t n)
Definition buffers.hpp:798
void after_write_notificator(write_status_cb_t notificator) noexcept
Set after write notificator.
Definition buffers.hpp:836
#define RESTINIO_FMT_FORMAT_STRING(s)
std::shared_ptr< connection_base_t > connection_handle_t
Alias for http connection handle.
unsigned int request_id_t
Request id in scope of single connection.
std::function< void(const asio_ns::error_code &ec) > write_status_cb_t
An alias for a callback to be invoked after the write operation of a particular group of "buffers".
Definition buffers.hpp:714
response_connection_attr_t response_connection_attr(bool should_keep_alive)
constexpr request_handling_status_t request_accepted() noexcept
request_handling_status_t
Request handling status.
http_field_t
C++ enum that repeats nodejs c-style enum.
std::string make_date_field_value(std::time_t t)
Format a timepoint to a string of a propper format.
constexpr const_buffer_t const_buffer(const void *str, std::size_t size) noexcept
Definition buffers.hpp:425
std::string make_date_field_value(std::chrono::system_clock::time_point tp)
response_parts_attr_t
Attribute for parts.
@ final_parts
Final parts (response ands with these parts).
Tag type for chunked output response builder.
Response output flags for buffers commited to response-coordinator.
response_output_flags_t(response_parts_attr_t response_parts, response_connection_attr_t response_connection) noexcept
Tag type for RESTinio controlled output response builder.
Tag type for user controlled output response builder.