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 <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 
24 namespace restinio
25 {
26 
27 //
28 // make_date_field_value()
29 //
30 
31 //! Format a timepoint to a string of a propper format.
32 inline std::string
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 
48 inline std::string
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 
58 template < typename Response_Builder >
60 {
61  public:
64 
65  base_response_builder_t( base_response_builder_t && ) noexcept = default;
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 ) }
78  {
80  }
81 
82  //! Accessors for header.
83  //! \{
85  header() noexcept
86  {
87  return m_header;
88  }
89 
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  {
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  {
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  {
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  {
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  {
200  return upcast_reference();
201  }
202 
203  Response_Builder &&
204  connection_keep_alive() && noexcept
205  {
206  return std::move( this->connection_keep_alive() );
207  }
208 
209  protected:
210  std::size_t
211  calculate_status_line_size() const noexcept
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 &
230  upcast_reference() noexcept
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.
241 template < typename Response_Output_Strategy >
243 {
244  response_builder_t() = delete;
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 */
257 template <>
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.
305  {
306  if( m_connection )
307  {
308  const response_output_flags_t
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 
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 
340  return restinio::request_accepted();
341  }
342 
343  private:
344  self_type_t &
345  set_body_impl( writable_item_t & body, std::size_t body_size )
346  {
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  {
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 
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 */
401 template <>
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.
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,
502  response_parts_attr_t::final_parts,
503  std::move( wscb ) );
504  }
505  else
506  {
507  throw_done_must_be_called_once();
508  }
509 
510  return restinio::request_accepted();
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 
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  {
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  {
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 
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  */
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  */
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 */
629 template <>
632 {
633  public:
634  using base_type_t =
636  using self_type_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,
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.
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,
711  response_parts_attr_t::final_parts,
712  std::move( wscb ) );
713  }
714  else
715  {
716  throw_done_must_be_called_once();
717  }
718 
719  return restinio::request_accepted();
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();
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
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 
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  const char * format_string = "{:X}\r\n";
818  for( auto & chunk : m_chunks )
819  {
820  bufs.emplace_back(
821  fmt::format(
822  format_string,
823  asio_ns::buffer_size( chunk.buf() ) ) );
824 
825  // Now include "\r\n"-ending for a previous chunk to format string.
826  format_string = "\r\n{:X}\r\n";
827 
828  bufs.emplace_back( std::move( chunk ) );
829 
830  }
831 
832  const char * const ending_representation = "\r\n" "0\r\n\r\n";
833  const char * appendix_begin = ending_representation + 2;
834  const char * appendix_end = appendix_begin;
835 
836  if( !m_chunks.empty() )
837  {
838  // Add "\r\n"part to appendix.
839  appendix_begin -= 2;
840  // bufs.emplace_back( const_buffer( rn_ending, 2 ) );
841  }
842 
843  if( add_zero_chunk )
844  {
845  // Add "0\r\n\r\n"part to appendix.
846  appendix_end += 5;
847  }
848 
849  if( appendix_begin != appendix_end )
850  {
851  bufs.emplace_back( const_buffer(
852  appendix_begin,
853  static_cast<std::size_t>(appendix_end - appendix_begin)
854  ) );
855  }
856 
857  m_chunks.clear();
858 
859  return bufs;
860  }
861 
862  //! Flag used by flush() function.
863  bool m_header_was_sent{ false };
864 
865  //! Saved keep_alive attr actual at the point
866  //! a header data was sent.
867  /*!
868  It is neccessary to guarantee that all parts of response
869  will have the same response-connection-attr
870  (keep-alive or close);
871  */
873 
874  //! Chunks accumulator.
876 };
877 
878 } /* namespace restinio */
Response_Builder && append_header(http_field_t field_id, std::string field_value) &&
Add header field.
void send_ready_data(const impl::connection_handle_t &conn, response_parts_attr_t response_parts_attr, write_status_cb_t wscb)
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)
virtual ~base_response_builder_t()=default
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.
base_response_builder_t & operator=(base_response_builder_t &&) noexcept=default
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.
base_response_builder_t(base_response_builder_t &&) noexcept=default
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.
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.
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
void send_ready_data(const impl::connection_handle_t &conn, response_parts_attr_t response_parts_attr, write_status_cb_t wscb)
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)
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.