RESTinio
message_builders.hpp
Go to the documentation of this file.
1/*
2 restinio
3*/
4
9#pragma once
10
11#include <ctime>
12#include <chrono>
13
15
18#include <restinio/os.hpp>
19#include <restinio/sendfile.hpp>
21
23
24namespace restinio
25{
26
27//
28// make_date_field_value()
29//
30
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
66 base_response_builder_t & operator =( base_response_builder_t && ) noexcept = default;
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 )
75 : m_header{ std::move( status_line ) }
76 , m_connection{ std::move( connection ) }
77 , m_request_id{ request_id }
78 {
79 m_header.should_keep_alive( should_keep_alive );
80 }
81
84 http_response_header_t &
85 header() noexcept
86 {
87 return m_header;
88 }
89
91 header() const noexcept
92 {
93 return m_header;
94 }
96
98 Response_Builder &
100 std::string field_name,
101 std::string field_value ) &
102 {
104 std::move( field_name ),
105 std::move( field_value ) );
106 return upcast_reference();
107 }
108
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
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
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
137 Response_Builder &
139 http_field_t field_id,
140 std::string field_value ) &
141 {
143 field_id,
144 std::move( field_value ) );
145 return upcast_reference();
146 }
147
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
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
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
180 Response_Builder &
181 connection_close() & noexcept
182 {
184 return upcast_reference();
185 }
186
188 Response_Builder &&
189 connection_close() && noexcept
190 {
191 return std::move( this->connection_close() );
192 }
193
194
196 Response_Builder &
198 {
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
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
241template < typename Response_Output_Strategy >
243{
245};
246
249
251
257template <>
259 : public base_response_builder_t< response_builder_t< restinio_controlled_output_t > >
260{
261 public:
266
268
269 // Reuse construstors from base.
270 using base_type_t::base_type_t;
271
275 {
276 auto size = body.size();
277 return set_body_impl( body, size );
278 }
279
281 self_type_t &&
283 {
284 return std::move( this->set_body( std::move( body ) ) );
285 }
286
288 self_type_t &
290 {
291 auto size = body_part.size();
292 return append_body_impl( body_part, size );
293 }
294
296 self_type_t &&
298 {
299 return std::move( this->append_body( std::move( body_part ) ) );
300 }
301
305 {
306 if( m_connection )
307 {
309 response_output_flags{
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
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 };
389};
390
393
395
401template <>
403 : public base_response_builder_t< response_builder_t< user_controlled_output_t > >
404{
405 public:
410
412
413 // Reuse construstors from base.
414 using base_type_t::base_type_t;
415
418 set_content_length( std::size_t content_length ) &
419 {
420 m_header.content_length( content_length );
421 return *this;
422 }
423
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
432 self_type_t &
434 {
435 auto size = body.size();
436 return set_body_impl( body, size );
437 }
438
440 self_type_t &&
442 {
443 return std::move( this->set_body( std::move( body ) ) );
444 }
445
447 self_type_t &
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
459 self_type_t &&
461 {
462 return std::move( this->append_body( std::move( body_part ) ) );
463 }
464
466
469 self_type_t &
471 {
472 if( m_connection )
473 {
474 send_ready_data(
475 m_connection,
477 std::move( wscb ) );
478 }
479
480 return *this;
481 }
482
484 self_type_t &&
486 {
487 return std::move( this->flush( std::move( wscb ) ) );
488 }
489
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
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 ] =
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 &
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
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
602 bool m_header_was_sent{ false };
603
606
611 bool m_should_keep_alive_when_header_was_sent{ true };
612
614
619};
620
623
625
629template <>
631 : public base_response_builder_t< response_builder_t< chunked_output_t > >
632{
633 public:
638
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
654
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
668 self_type_t &&
670 {
671 return std::move( this->append_chunk( std::move( chunk ) ) );
672 }
673
675
678 self_type_t &
680 {
681 if( m_connection )
682 {
683 send_ready_data(
684 m_connection,
686 std::move( wscb ) );
687 }
688
689 return *this;
690 }
691
693 self_type_t &&
695 {
696 return std::move( this->flush( std::move( wscb ) ) );
697 }
698
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
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
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
792 create_bufs( bool add_zero_chunk )
793 {
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(
813 m_header,
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 "{: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 "\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
881 bool m_header_was_sent{ false };
882
885
890 bool m_should_keep_alive_when_header_was_sent{ true };
891
894};
895
896} /* namespace restinio */
897
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 & 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
Exception class for all exceptions thrown by RESTinio.
Definition: exception.hpp:26
A single header field.
void add_field(http_field_t field_id, std::string field_value)
Add a field in the form of id-value pair.
void set_field(http_header_field_t http_header_field)
Set header field via http_header_field_t.
HTTP response header status line.
const std::string & reason_phrase() const noexcept
Chunked transfer encoding output builder.
self_type_t & flush(write_status_cb_t wscb=write_status_cb_t{}) &
Flush ready outgoing data.
self_type_t && flush(write_status_cb_t wscb=write_status_cb_t{}) &&
Flush ready outgoing data.
writable_items_container_t m_chunks
Chunks accumulator.
writable_items_container_t create_bufs(bool add_zero_chunk)
void send_ready_data(const impl::connection_handle_t &conn, response_parts_attr_t response_parts_attr, write_status_cb_t wscb)
response_builder_t(response_builder_t &&)=default
self_type_t && append_chunk(writable_item_t chunk) &&
Append current chunk.
self_type_t & append_chunk(writable_item_t chunk) &
Append current chunk.
request_handling_status_t done(write_status_cb_t wscb=write_status_cb_t{})
Complete response.
response_builder_t(http_status_line_t status_line, impl::connection_handle_t connection, request_id_t request_id, bool should_keep_alive)
self_type_t & append_body_impl(writable_item_t &body_part, std::size_t append_size)
self_type_t && append_body(writable_item_t body_part) &&
Append body.
self_type_t & append_body(writable_item_t body_part) &
Append body.
request_handling_status_t done(write_status_cb_t wscb=write_status_cb_t{})
Complete response.
self_type_t && set_body(writable_item_t body) &&
Set body.
self_type_t & set_body_impl(writable_item_t &body, std::size_t body_size)
self_type_t & set_body(writable_item_t body) &
Set body.
self_type_t & flush(write_status_cb_t wscb=write_status_cb_t{}) &
Flush ready outgoing data.
writable_items_container_t m_response_parts
Body accumulator.
self_type_t & append_body_impl(writable_item_t &body_part)
self_type_t & set_body(writable_item_t body) &
Set body (part).
void send_ready_data(const impl::connection_handle_t &conn, response_parts_attr_t response_parts_attr, write_status_cb_t wscb)
self_type_t & set_content_length(std::size_t content_length) &
Manualy set content length.
self_type_t && set_content_length(std::size_t content_length) &&
Manualy set content length.
self_type_t && set_body(writable_item_t body) &&
Set body (part).
self_type_t && flush(write_status_cb_t wscb=write_status_cb_t{}) &&
Flush ready outgoing data.
request_handling_status_t done(write_status_cb_t wscb=write_status_cb_t{})
Complete response.
self_type_t & set_body_impl(writable_item_t &body, std::size_t body_size)
self_type_t && append_body(writable_item_t body_part) &&
Append body.
self_type_t & append_body(writable_item_t body_part) &
Append body.
Forbid arbitrary response_builder_t instantiations.
Class for storing the buffers used for streaming body (request/response).
Definition: buffers.hpp:496
Group of writable items transported to the context of underlying connection as one solid piece.
Definition: buffers.hpp:692
A special wrapper around fmtlib include files.
std::string create_header_string(const http_response_header_t &h, content_length_field_presence_t content_length_field_presence=content_length_field_presence_t::add_content_length, std::size_t buffer_size=0)
Creates a string for http response header.
std::shared_ptr< connection_base_t > connection_handle_t
Alias for http connection handle.
constexpr std::size_t ct_string_len(const char(&)[N]) noexcept
Compile time c-string length.
std::vector< writable_item_t > writable_items_container_t
Definition: buffers.hpp:668
unsigned int request_id_t
Request id in scope of single connection.
response_connection_attr_t response_connection_attr(bool should_keep_alive)
request_handling_status_t
Request handling status.
http_field_t
C++ enum that repeats nodejs c-style enum.
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:680
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:424
RESTINIO_NODISCARD constexpr request_handling_status_t request_accepted() noexcept
response_parts_attr_t
Attribute for parts.
@ final_parts
Final parts (response ands with these parts).
@ not_final_parts
Intermediate parts (more parts of response to follow).
STL namespace.
std::tm make_gmtime(std::time_t t)
Definition: os_posix.ipp:2
Tag type for chunked output response builder.
bool should_keep_alive() const noexcept
const http_status_line_t & status_line() const noexcept
Response output flags for buffers commited to response-coordinator.
Tag type for RESTinio controlled output response builder.
Tag type for user controlled output response builder.
#define const
Definition: zconf.h:230