RESTinio
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 <fmt/format.h>
15 #include <fmt/time.h>
16 
17 #include <restinio/common_types.hpp>
18 #include <restinio/http_headers.hpp>
19 #include <restinio/os.hpp>
20 #include <restinio/sendfile.hpp>
21 #include <restinio/impl/connection_base.hpp>
22 
23 #include <restinio/impl/header_helpers.hpp>
24 
25 namespace restinio
26 {
27 
28 //
29 // make_date_field_value()
30 //
31 
32 //! Format a timepoint to a string of a propper format.
33 inline std::string
35 {
36  const auto tpoint = make_gmtime( t );
37 
38  std::array< char, 64 > buf;
39  // TODO: is there a faster way to get time string?
40  strftime(
41  buf.data(),
42  buf.size(),
43  "%a, %d %b %Y %H:%M:%S GMT",
44  &tpoint );
45 
46  return std::string{ buf.data() };
47 }
48 
49 inline std::string
51 {
52  return make_date_field_value( std::chrono::system_clock::to_time_t( tp ) );
53 }
54 
55 //
56 // base_response_builder_t
57 //
58 
59 template < typename Response_Builder >
61 {
62  public:
65 
68 
70  {}
71 
73  http_status_line_t status_line,
74  impl::connection_handle_t connection,
75  request_id_t request_id,
76  bool should_keep_alive )
77  : m_header{ std::move( status_line ) }
80  {
82  }
83 
84  //! Accessors for header.
85  //! \{
87  header() noexcept
88  {
89  return m_header;
90  }
91 
93  header() const noexcept
94  {
95  return m_header;
96  }
97  //! \}
98 
99  //! Add header field.
100  Response_Builder &
102  std::string field_name,
103  std::string field_value ) &
104  {
106  std::move( field_name ),
107  std::move( field_value ) );
108  return upcast_reference();
109  }
110 
111  //! Add header field.
112  Response_Builder &&
114  std::string field_name,
115  std::string field_value ) &&
116  {
117  return std::move( this->append_header(
118  std::move( field_name ),
119  std::move( field_value ) ) );
120  }
121 
122  //! Add header field.
123  Response_Builder &
124  append_header( http_header_field_t http_header_field ) &
125  {
127  return upcast_reference();
128  }
129 
130  //! Add header field.
131  Response_Builder &&
132  append_header( http_header_field_t http_header_field ) &&
133  {
134  return std::move( this->append_header(
135  std::move( http_header_field ) ) );
136  }
137 
138  //! Add header field.
139  Response_Builder &
141  http_field_t field_id,
142  std::string field_value ) &
143  {
145  field_id,
146  std::move( field_value ) );
147  return upcast_reference();
148  }
149 
150  //! Add header field.
151  Response_Builder &&
153  http_field_t field_id,
154  std::string field_value ) &&
155  {
156  return std::move( this->append_header(
157  field_id,
158  std::move( field_value ) ) );
159  }
160 
161 
162  //! Add header `Date` field.
163  Response_Builder &
165  std::chrono::system_clock::time_point tp =
166  std::chrono::system_clock::now() ) &
167  {
169  return upcast_reference();
170  }
171 
172  //! Add header `Date` field.
173  Response_Builder &&
175  std::chrono::system_clock::time_point tp =
176  std::chrono::system_clock::now() ) &&
177  {
178  return std::move( this->append_header_date_field( tp ) );
179  }
180 
181  //! Set connection close.
182  Response_Builder &
183  connection_close() & noexcept
184  {
185  m_header.should_keep_alive( false );
186  return upcast_reference();
187  }
188 
189  //! Set connection close.
190  Response_Builder &&
191  connection_close() && noexcept
192  {
193  return std::move( this->connection_close() );
194  }
195 
196 
197  //! Set connection keep-alive.
198  Response_Builder &
200  {
202  return upcast_reference();
203  }
204 
205  Response_Builder &&
206  connection_keep_alive() && noexcept
207  {
208  return std::move( this->connection_keep_alive() );
209  }
210 
211  protected:
212  std::size_t
213  calculate_status_line_size() const noexcept
214  {
215  // "HTTP/1.1 *** <reason-phrase>"
216  return 8 + 1 + 3 + 1 + m_header.status_line().reason_phrase().size();
217  }
218 
220 
223 
224  void
226  {
227  throw exception_t{ "done() cannot be called twice" };
228  }
229 
230  private:
231  Response_Builder &
232  upcast_reference() noexcept
233  {
234  return static_cast< Response_Builder & >( *this );
235  }
236 };
237 
238 //
239 // response_builder_t
240 //
241 
242 //! Forbid arbitrary response_builder_t instantiations.
243 template < typename Response_Output_Strategy >
245 {
246  response_builder_t() = delete;
247 };
248 
249 //! Tag type for RESTinio controlled output response builder.
251 
252 //! Simple standard response builder.
253 /*!
254  Requires user to set header and body.
255  Content length is automatically calculated.
256  Once the data is ready, the user calls done() method
257  and the resulting response is scheduled for sending.
258 */
259 template <>
262 {
263  public:
264  using base_type_t =
266  using self_type_t =
268 
269  response_builder_t( response_builder_t && ) = default;
270 
271  // Reuse construstors from base.
272  using base_type_t::base_type_t;
273 
274  //! Set body.
275  self_type_t &
276  set_body( writable_item_t body ) &
277  {
278  auto size = body.size();
279  return set_body_impl( body, size );
280  }
281 
282  //! Set body.
283  self_type_t &&
284  set_body( writable_item_t body ) &&
285  {
286  return std::move( this->set_body( std::move( body ) ) );
287  }
288 
289  //! Append body.
290  self_type_t &
291  append_body( writable_item_t body_part ) &
292  {
293  auto size = body_part.size();
294  return append_body_impl( body_part, size );
295  }
296 
297  //! Append body.
298  self_type_t &&
299  append_body( writable_item_t body_part ) &&
300  {
301  return std::move( this->append_body( std::move( body_part ) ) );
302  }
303 
304  //! Complete response.
307  {
308  if( m_connection )
309  {
310  const response_output_flags_t
311  response_output_flags{
312  response_parts_attr_t::final_parts,
313  response_connection_attr( m_header.should_keep_alive() ) };
314 
315  m_header.content_length( m_body_size );
316 
318 
319  m_response_parts[ 0 ] =
320  writable_item_t{ impl::create_header_string( m_header ) };
321 
322  write_group_t wg{ std::move( m_response_parts ) };
323  wg.status_line_size( calculate_status_line_size() );
324 
325  if( wscb )
326  {
327  wg.after_write_notificator( std::move( wscb ) );
328  }
329 
330  auto conn = std::move( m_connection );
331 
332  conn->write_response_parts(
333  m_request_id,
334  response_output_flags,
335  std::move( wg ) );
336  }
337  else
338  {
339  throw_done_must_be_called_once();
340  }
341 
342  return restinio::request_accepted();
343  }
344 
345  private:
346  self_type_t &
347  set_body_impl( writable_item_t & body, std::size_t body_size )
348  {
350 
351  // Leave only buf that is reserved for header,
352  // so forget all the previous data.
353  m_response_parts.resize( 1 );
354 
355  if( 0 < body_size )
356  {
357  m_response_parts.emplace_back( std::move( body ) );
358  }
359 
360  m_body_size = body_size;
361 
362  return *this;
363  }
364 
365  self_type_t &
366  append_body_impl( writable_item_t & body_part, std::size_t append_size )
367  {
369 
370  if( 0 < append_size )
371  {
372  m_response_parts.emplace_back( std::move( body_part ) );
373  m_body_size += append_size;
374  }
375 
376  return *this;
377  }
378 
379  void
381  {
382  if( 0 == m_response_parts.size() )
383  {
384  m_response_parts.reserve( 2 );
385  m_response_parts.emplace_back();
386  }
387  }
388 
391 };
392 
393 //! Tag type for user controlled output response builder.
395 
396 //! User controlled response output builder.
397 /*!
398  This type of output allows user
399  to send body divided into parts.
400  But it is up to user to set the correct
401  Content-Length field.
402 */
403 template <>
406 {
407  public:
408  using base_type_t =
410  using self_type_t =
412 
413  response_builder_t( response_builder_t && ) = default;
414 
415  // Reuse construstors from base.
416  using base_type_t::base_type_t;
417 
418  //! Manualy set content length.
419  self_type_t &
420  set_content_length( std::size_t content_length ) &
421  {
422  m_header.content_length( content_length );
423  return *this;
424  }
425 
426  //! Manualy set content length.
427  self_type_t &&
428  set_content_length( std::size_t content_length ) &&
429  {
430  return std::move( this->set_content_length( content_length ) );
431  }
432 
433  //! Set body (part).
434  self_type_t &
435  set_body( writable_item_t body ) &
436  {
437  auto size = body.size();
438  return set_body_impl( body, size );
439  }
440 
441  //! Set body (part).
442  self_type_t &&
443  set_body( writable_item_t body ) &&
444  {
445  return std::move( this->set_body( std::move( body ) ) );
446  }
447 
448  //! Append body.
449  self_type_t &
450  append_body( writable_item_t body_part ) &
451  {
452  auto size = body_part.size();
453 
454  if( 0 == size )
455  return *this;
456 
457  return append_body_impl( body_part );
458  }
459 
460  //! Append body.
461  self_type_t &&
462  append_body( writable_item_t body_part ) &&
463  {
464  return std::move( this->append_body( std::move( body_part ) ) );
465  }
466 
467  //! Flush ready outgoing data.
468  /*!
469  Schedules for sending currently ready data.
470  */
471  self_type_t &
472  flush( write_status_cb_t wscb = write_status_cb_t{} ) &
473  {
474  if( m_connection )
475  {
476  send_ready_data(
477  m_connection,
478  response_parts_attr_t::not_final_parts,
479  std::move( wscb ) );
480  }
481 
482  return *this;
483  }
484 
485  //! Flush ready outgoing data.
486  self_type_t &&
487  flush( write_status_cb_t wscb = write_status_cb_t{} ) &&
488  {
489  return std::move( this->flush( std::move( wscb ) ) );
490  }
491 
492  //! Complete response.
495  {
496  if( m_connection )
497  {
498  send_ready_data(
499  std::move( m_connection ),
500  response_parts_attr_t::final_parts,
501  std::move( wscb ) );
502  }
503  else
504  {
505  throw_done_must_be_called_once();
506  }
507 
508  return restinio::request_accepted();
509  }
510 
511  private:
512  void
514  impl::connection_handle_t conn,
515  response_parts_attr_t response_parts_attr,
516  write_status_cb_t wscb )
517  {
518  std::size_t status_line_size{ 0 };
519 
520  if( !m_header_was_sent )
521  {
522  m_should_keep_alive_when_header_was_sent =
523  m_header.should_keep_alive();
524 
526 
527  m_response_parts[ 0 ] =
528  writable_item_t{ impl::create_header_string( m_header ) };
529 
530  m_header_was_sent = true;
531  status_line_size = calculate_status_line_size();
532  }
533 
534  if( !m_response_parts.empty() ||
535  wscb ||
536  response_parts_attr_t::final_parts == response_parts_attr )
537  {
538  const response_output_flags_t
539  response_output_flags{
540  response_parts_attr,
541  response_connection_attr( m_should_keep_alive_when_header_was_sent ) };
542 
543  write_group_t wg{ std::move( m_response_parts ) };
544  wg.status_line_size( status_line_size );
545 
546  if( wscb )
547  {
548  wg.after_write_notificator( std::move( wscb ) );
549  }
550 
551  conn->write_response_parts(
552  m_request_id,
553  response_output_flags,
554  std::move( wg ) );
555  }
556  }
557 
558  self_type_t &
559  set_body_impl( writable_item_t & body, std::size_t body_size )
560  {
562 
563  // Leave only buf that is reserved for header,
564  // so forget all the previous data.
565  if( !m_header_was_sent )
566  m_response_parts.resize( 1 );
567  else
568  m_response_parts.resize( 0 );
569 
570  if( 0 < body_size )
571  {
572  // if body is not empty:
573  m_response_parts.emplace_back( std::move( body ) );
574  }
575 
576  return *this;
577  }
578 
579  self_type_t &
580  append_body_impl( writable_item_t & body_part )
581  {
583 
584  m_response_parts.emplace_back( std::move( body_part ) );
585  return *this;
586  }
587 
588  void
590  {
591  if( !m_header_was_sent && 0 == m_response_parts.size() )
592  {
593  m_response_parts.reserve( 2 );
594  m_response_parts.emplace_back();
595  }
596  }
597 
598 
599  //! Flag used by flush() function.
600  bool m_header_was_sent{ false };
601 
602  //! Saved keep_alive attr actual at the point
603  //! a header data was sent.
604  /*!
605  It is neccessary to guarantee that all parts of response
606  will have the same response-connection-attr
607  (keep-alive or close);
608  */
610 
611  //! Body accumulator.
612  /*!
613  For this type of output it contains a part of a body.
614  On each flush it is cleared.
615  */
617 };
618 
619 //! Tag type for chunked output response builder.
621 
622 //! Chunked transfer encoding output builder.
623 /*!
624  This type of output sets transfer-encoding to chunked
625  and expects user to set body using chunks of data.
626 */
627 template <>
630 {
631  public:
632  using base_type_t =
634  using self_type_t =
636 
638  http_status_line_t status_line,
639  impl::connection_handle_t connection,
640  request_id_t request_id,
641  bool should_keep_alive )
642  : base_type_t{
643  std::move( status_line ),
644  std::move( connection ),
645  request_id,
647  {
648  m_chunks.reserve( 4 );
649  }
650 
651  response_builder_t( response_builder_t && ) = default;
652 
653  //! Append current chunk.
654  self_type_t &
655  append_chunk( writable_item_t chunk ) &
656  {
657  auto size = chunk.size();
658 
659  if( 0 != size )
660  m_chunks.emplace_back( std::move( chunk ) );
661 
662  return *this;
663  }
664 
665  //! Append current chunk.
666  self_type_t &&
667  append_chunk( writable_item_t chunk ) &&
668  {
669  return std::move( this->append_chunk( std::move( chunk ) ) );
670  }
671 
672  //! Flush ready outgoing data.
673  /*!
674  Schedules for sending currently ready data.
675  */
676  self_type_t &
677  flush( write_status_cb_t wscb = write_status_cb_t{} ) &
678  {
679  if( m_connection )
680  {
681  send_ready_data(
682  m_connection,
683  response_parts_attr_t::not_final_parts,
684  std::move( wscb ) );
685  }
686 
687  return *this;
688  }
689 
690  //! Flush ready outgoing data.
691  self_type_t &&
692  flush( write_status_cb_t wscb = write_status_cb_t{} ) &&
693  {
694  return std::move( this->flush( std::move( wscb ) ) );
695  }
696 
697  //! Complete response.
700  {
701  if( m_connection )
702  {
703  send_ready_data(
704  std::move( m_connection ),
705  response_parts_attr_t::final_parts,
706  std::move( wscb ) );
707  }
708  else
709  {
710  throw_done_must_be_called_once();
711  }
712 
713  return restinio::request_accepted();
714  }
715 
716  private:
717  void
719  impl::connection_handle_t conn,
720  response_parts_attr_t response_parts_attr,
721  write_status_cb_t wscb )
722  {
723  std::size_t status_line_size{ 0 };
724  if( !m_header_was_sent )
725  {
726  status_line_size = calculate_status_line_size();
728  }
729 
730  auto bufs = create_bufs( response_parts_attr_t::final_parts == response_parts_attr );
731  m_header_was_sent = true;
732 
733  const response_output_flags_t
734  response_output_flags{
735  response_parts_attr,
736  response_connection_attr( m_should_keep_alive_when_header_was_sent ) };
737 
738  // We have buffers or at least we have after-write notificator.
739  if( !bufs.empty() || wscb )
740  {
741  write_group_t wg{ std::move( bufs ) };
742  wg.status_line_size( status_line_size );
743 
744  if( wscb )
745  {
746  wg.after_write_notificator( std::move( wscb ) );
747  }
748 
749  conn->write_response_parts(
750  m_request_id,
751  response_output_flags,
752  std::move( wg ) );
753  }
754  }
755 
756  void
758  {
759  m_should_keep_alive_when_header_was_sent =
760  m_header.should_keep_alive();
761 
762  constexpr const char value[] = "chunked";
763  if( !m_header.has_field( restinio::http_field::transfer_encoding ) )
764  {
765  m_header.set_field(
766  restinio::http_field::transfer_encoding,
767  std::string{ value, impl::ct_string_len( value ) } );
768  }
769  else
770  {
771  auto & current_value =
772  m_header.get_field( restinio::http_field::transfer_encoding );
773  if( std::string::npos == current_value.find( value ) )
774  {
775  constexpr const char comma_value[] = ",chunked";
776  m_header.append_field(
777  restinio::http_field::transfer_encoding,
778  std::string{
779  comma_value,
780  impl::ct_string_len( comma_value ) } );
781  }
782  }
783  }
784 
787  {
788  writable_items_container_t bufs;
789 
790  std::size_t reserve_size = 2 * m_chunks.size() + 1;
791 
792  if( !m_header_was_sent )
793  {
794  ++reserve_size;
795  }
796  if( add_zero_chunk )
797  {
798  ++reserve_size;
799  }
800 
801  bufs.reserve( reserve_size );
802 
803  if( !m_header_was_sent )
804  {
805  bufs.emplace_back(
806  impl::create_header_string(
807  m_header,
808  impl::content_length_field_presence_t::skip_content_length ) );
809  }
810 
811  const char * format_string = "{:X}\r\n";
812  for( auto & chunk : m_chunks )
813  {
814  bufs.emplace_back(
815  fmt::format(
816  format_string,
817  asio_ns::buffer_size( chunk.buf() ) ) );
818 
819  // Now include "\r\n"-ending for a previous chunk to format string.
820  format_string = "\r\n{:X}\r\n";
821 
822  bufs.emplace_back( std::move( chunk ) );
823 
824  }
825 
826  const char * const ending_representation = "\r\n" "0\r\n\r\n";
827  const char * appendix_begin = ending_representation + 2;
828  const char * appendix_end = appendix_begin;
829 
830  if( !m_chunks.empty() )
831  {
832  // Add "\r\n"part to appendix.
833  appendix_begin -= 2;
834  // bufs.emplace_back( const_buffer( rn_ending, 2 ) );
835  }
836 
837  if( add_zero_chunk )
838  {
839  // Add "0\r\n\r\n"part to appendix.
840  appendix_end += 5;
841  }
842 
843  if( appendix_begin != appendix_end )
844  {
845  bufs.emplace_back( const_buffer( appendix_begin, appendix_end - appendix_begin ) );
846  }
847 
848  m_chunks.clear();
849 
850  return bufs;
851  }
852 
853  //! Flag used by flush() function.
854  bool m_header_was_sent{ false };
855 
856  //! Saved keep_alive attr actual at the point
857  //! a header data was sent.
858  /*!
859  It is neccessary to guarantee that all parts of response
860  will have the same response-connection-attr
861  (keep-alive or close);
862  */
864 
865  //! Chunks accumulator.
867 };
868 
869 } /* namespace restinio */
Response_Builder && append_header(http_field_t field_id, std::string field_value) &&
Add header field.
std::size_t calculate_status_line_size() const noexcept
Response_Builder & append_header(http_field_t field_id, std::string field_value) &
Add header field.
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 && set_body(writable_item_t body) &&
Set body.
self_type_t & set_body_impl(writable_item_t &body, std::size_t body_size)
writable_items_container_t create_bufs(bool add_zero_chunk)
Response_Builder && connection_close() &&noexcept
Set connection close.
Response_Builder & connection_keep_alive() &noexcept
Set connection keep-alive.
bool m_should_keep_alive_when_header_was_sent
Saved keep_alive attr actual at the point a header data was sent.
self_type_t && append_chunk(writable_item_t chunk) &&
Append current chunk.
self_type_t && flush(write_status_cb_t wscb=write_status_cb_t{}) &&
Flush ready outgoing data.
self_type_t && set_content_length(std::size_t content_length) &&
Manualy set content length.
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.
base_response_builder_t(http_status_line_t status_line, impl::connection_handle_t connection, request_id_t request_id, bool should_keep_alive)
writable_items_container_t m_response_parts
Body accumulator.
Response_Builder && append_header_date_field(std::chrono::system_clock::time_point tp=std::chrono::system_clock::now()) &&
Add header Date field.
self_type_t & flush(write_status_cb_t wscb=write_status_cb_t{}) &
Flush ready outgoing data.
self_type_t & append_body(writable_item_t body_part) &
Append body.
self_type_t && append_body(writable_item_t body_part) &&
Append body.
self_type_t && append_body(writable_item_t body_part) &&
Append body.
Response_Builder & append_header_date_field(std::chrono::system_clock::time_point tp=std::chrono::system_clock::now()) &
Add header Date field.
bool m_should_keep_alive_when_header_was_sent
Saved keep_alive attr actual at the point a header data was sent.
http_response_header_t & header() noexcept
Accessors for header.
base_response_builder_t(base_response_builder_t &&)=default
base_response_builder_t & operator=(base_response_builder_t &&)=default
bool m_header_was_sent
Flag used by flush() function.
self_type_t & set_body(writable_item_t body) &
Set body.
self_type_t & set_content_length(std::size_t content_length) &
Manualy set content length.
void send_ready_data(impl::connection_handle_t conn, response_parts_attr_t response_parts_attr, write_status_cb_t wscb)
Forbid arbitrary response_builder_t instantiations.
self_type_t & append_body_impl(writable_item_t &body_part, std::size_t append_size)
response_builder_t(response_builder_t &&)=default
self_type_t & append_body_impl(writable_item_t &body_part)
Tag type for user controlled output response builder.
self_type_t & set_body_impl(writable_item_t &body, std::size_t body_size)
self_type_t && flush(write_status_cb_t wscb=write_status_cb_t{}) &&
Flush ready outgoing data.
Response_Builder & connection_close() &noexcept
Set connection close.
Tag type for RESTinio controlled output response builder.
Response_Builder & append_header(http_header_field_t http_header_field) &
Add header field.
impl::connection_handle_t m_connection
Response_Builder & upcast_reference() noexcept
request_handling_status_t done(write_status_cb_t wscb=write_status_cb_t{})
Complete response.
Response_Builder && connection_keep_alive() &&noexcept
self_type_t && set_body(writable_item_t body) &&
Set body (part).
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 (part).
writable_items_container_t m_chunks
Chunks accumulator.
base_response_builder_t & operator=(const base_response_builder_t &)=delete
Tag type for chunked output response builder.
Response_Builder && append_header(http_header_field_t http_header_field) &&
Add header field.
std::string make_date_field_value(std::chrono::system_clock::time_point tp)
void send_ready_data(impl::connection_handle_t conn, response_parts_attr_t response_parts_attr, write_status_cb_t wscb)
self_type_t & flush(write_status_cb_t wscb=write_status_cb_t{}) &
Flush ready outgoing data.
base_response_builder_t(const base_response_builder_t &)=delete
const http_response_header_t & header() const noexcept
std::enable_if< std::is_same< Parameter_Container, query_string_params_t >::value||std::is_same< Parameter_Container, router::route_params_t >::value, optional_t< Value_Type > >::type opt_value(const Parameter_Container &params, string_view_t key)
Gets the value of a parameter specified by key wrapped in optional_t<Value_Type> if parameter exists ...
Definition: value_or.hpp:64
self_type_t & append_body(writable_item_t body_part) &
Append body.