RESTinio
Loading...
Searching...
No Matches
message_builders.hpp
Go to the documentation of this file.
1/*
2 restinio
3*/
4
8
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 }
95
96
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
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 {
142 m_header.add_field(
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 {
183 m_header.should_keep_alive( false );
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 {
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
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:
262 using base_type_t =
264 using self_type_t =
265 response_builder_t< restinio_controlled_output_t >;
266
268
269 // Reuse construstors from base.
270 using base_type_t::base_type_t;
271
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
281 self_type_t &&
282 set_body( writable_item_t body ) &&
283 {
284 return std::move( this->set_body( std::move( body ) ) );
285 }
286
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
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
304 done( write_status_cb_t wscb = write_status_cb_t{} )
305 {
306 if( m_connection )
307 {
308 const response_output_flags_t
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
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
393
395
401template <>
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 =
409 response_builder_t< user_controlled_output_t >;
410
412
413 // Reuse construstors from base.
414 using base_type_t::base_type_t;
415
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
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 &
433 set_body( writable_item_t body ) &
434 {
435 auto size = body.size();
436 return set_body_impl( body, size );
437 }
438
440 self_type_t &&
441 set_body( writable_item_t body ) &&
442 {
443 return std::move( this->set_body( std::move( body ) ) );
444 }
445
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
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
466
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,
477 std::move( wscb ) );
478 }
479
480 return *this;
481 }
482
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
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 {
540 const response_output_flags_t
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
602 bool m_header_was_sent{ false };
603
606
611 bool m_should_keep_alive_when_header_was_sent{ true };
612
614
618 writable_items_container_t m_response_parts;
619};
620
623
625
629template <>
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 =
637 response_builder_t< chunked_output_t >;
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
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
668 self_type_t &&
669 append_chunk( writable_item_t chunk ) &&
670 {
671 return std::move( this->append_chunk( std::move( chunk ) ) );
672 }
673
675
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,
686 std::move( wscb ) );
687 }
688
689 return *this;
690 }
691
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
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
739 const response_output_flags_t
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
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 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
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.
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
A special wrapper around fmtlib include files.
#define RESTINIO_FMT_FORMAT_STRING(s)
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.
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
@ connection_close
This response says to close connection.
std::tm make_gmtime(std::time_t t)
Definition os.hpp:3
std::vector< writable_item_t > writable_items_container_t
Definition buffers.hpp:703
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).
Tag type for chunked output response builder.
Tag type for RESTinio controlled output response builder.
Tag type for user controlled output response builder.