RESTinio
express.hpp
Go to the documentation of this file.
1 /*
2  restinio
3 */
4 
5 /*!
6  Express.js style router.
7 */
8 
9 #pragma once
10 
11 #include <restinio/router/impl/target_path_holder.hpp>
12 #include <restinio/router/non_matched_request_handler.hpp>
13 
14 #include <restinio/optional.hpp>
15 
16 #include <restinio/path2regex/path2regex.hpp>
17 
18 #include <restinio/router/std_regex_engine.hpp>
19 #include <restinio/router/method_matcher.hpp>
20 
21 #include <restinio/utils/from_string.hpp>
22 #include <restinio/utils/percent_encoding.hpp>
23 
24 #include <map>
25 #include <vector>
26 
27 namespace restinio
28 {
29 
30 namespace router
31 {
32 
33 namespace impl
34 {
35 
37 
38 } /* namespace impl */
39 
40 //
41 // route_params_t
42 //
43 
44 //! Parameters extracted from route.
45 /*!
46  Holds values of parameters extracted from route.
47 
48  All values are stored as string views,
49  route_params_t instance owns bufers used by these views.
50  And that leads to following limittions:
51  once a copy of a string view is created it is
52  important to use it with respect to life time of route_params_t
53  instance to which this parameter bind belongs.
54  String view is valid during route_params_t
55  instance life time.
56 */
57 class route_params_t final
58 {
59  public:
60  using named_parameters_container_t =
61  std::vector< std::pair< string_view_t, string_view_t > >;
62  using indexed_parameters_container_t =
63  std::vector< string_view_t >;
64 
65  private:
66  friend struct impl::route_params_accessor_t;
67 
68  void
70  std::unique_ptr< char[] > request_target,
71  std::shared_ptr< std::string > key_names_buffer,
72  string_view_t match,
73  named_parameters_container_t named_parameters,
74  indexed_parameters_container_t indexed_parameters )
75  {
76  m_request_target = std::move( request_target );
77  m_key_names_buffer = std::move( key_names_buffer );
78  m_match = match;
79  m_named_parameters = std::move( named_parameters );
80  m_indexed_parameters = std::move( indexed_parameters );
81  }
82 
83  public:
84  route_params_t() = default;
85 
86  route_params_t( route_params_t && ) = default;
87  route_params_t & operator = ( route_params_t && ) = default;
88 
89  route_params_t( const route_params_t & ) = delete;
90  route_params_t & operator = ( const route_params_t & ) = delete;
91 
92  //! Matched route.
93  string_view_t match() const noexcept { return m_match; }
94 
95  //! Get named parameter.
97  operator [] ( string_view_t key ) const
98  {
99  return find_named_parameter_with_check( key ).second;
100  }
101 
102  //! Check parameter.
103  bool
104  has( string_view_t key ) const noexcept
105  {
106  return m_named_parameters.end() != find_named_parameter( key );
107  }
108 
109  //! Get the value of a parameter if it exists.
110  //! @since v.0.4.4
112  get_param( string_view_t key ) const noexcept
113  {
114  const auto it = find_named_parameter( key );
115 
116  return m_named_parameters.end() != it ?
117  optional_t< string_view_t >{ it->second } :
118  optional_t< string_view_t >{ nullopt };
119  }
120 
121  //! Get indexed parameter.
123  operator [] ( std::size_t i ) const
124  {
125  if( i >= m_indexed_parameters.size() )
126  throw exception_t{ fmt::format( "invalid parameter index: {}", i ) };
127 
128  return m_indexed_parameters.at( i );
129  }
130 
131  //! Get number of parameters.
132  //! \{
133  auto named_parameters_size() const noexcept { return m_named_parameters.size(); }
134  auto indexed_parameters_size() const noexcept { return m_indexed_parameters.size(); }
135  //! \}
136 
137  private:
140  {
141  return
142  std::find_if(
143  m_named_parameters.begin(),
144  m_named_parameters.end(),
145  [&]( const auto p ){
146  return key == p.first;
147  } );
148  }
149 
152  {
153  auto it = find_named_parameter( key );
154 
155  if( m_named_parameters.end() == it )
156  throw exception_t{
157  fmt::format(
158  "invalid parameter name: {}",
159  std::string{ key.data(), key.size() } ) };
160 
161  return *it;
162  }
163 
164  //! A raw request target.
165  /*!
166  All parameters values are defined as string views
167  refering parts of this beffer.
168 
169  \note `std::unique_ptr< char[] >` is used here on purpose,
170  because if we consider std::string, then it has an issue
171  when SSO is applied. It is important that
172  parameters that refering buffer are valid after move operations with
173  the buffer. And std::strings with SSO applied cannot guarantee this.
174  Vector on the other hand gives this guarantee.
175  */
177 
178  //! Shared buffer for string_view of named parameterts names.
180 
181  //! Matched pattern.
183 
184  //! Named params.
186 
187  //! Indexed params.
189 };
190 
191 namespace impl
192 {
193 
194 //
195 // route_params_accessor_t
196 //
197 
198 //! Route params private internals accessor.
200 {
201  //! Init parameters with a matched route params.
202  static void
204  route_params_t & rp,
205  std::unique_ptr< char[] > request_target,
206  std::shared_ptr< std::string > key_names_buffer,
207  string_view_t match_,
208  route_params_t::named_parameters_container_t named_parameters,
209  route_params_t::indexed_parameters_container_t indexed_parameters )
210  {
211  rp.match(
212  std::move( request_target ),
213  std::move( key_names_buffer ),
214  match_,
215  std::move( named_parameters ),
216  std::move( indexed_parameters ) );
217  }
218 
219  //! Get values containers for all parameters (used in unit tests).
220  //! \{
221  static const auto &
222  named_parameters( const route_params_t & rp ) noexcept
223  {
224  return rp.m_named_parameters;
225  }
226 
227  static const auto &
228  indexed_parameters( const route_params_t & rp ) noexcept
229  {
230  return rp.m_indexed_parameters;
231  }
232  //! \}
233 };
234 
235 //
236 // route_params_appender_t
237 //
238 
239 //! Helper class for gthering parameters from route.
241 {
242  public:
244  route_params_t::named_parameters_container_t & named_parameters,
245  route_params_t::indexed_parameters_container_t & indexed_parameters )
248  {}
249 
254 
255  void
256  add_named_param( string_view_t key, string_view_t value )
257  {
258  m_named_parameters.emplace_back( key, value );
259  }
260 
261  void
262  add_indexed_param( string_view_t value )
263  {
264  m_indexed_parameters.emplace_back( value );
265  }
266 
267  private:
270 };
271 
274 
275 //
276 // route_matcher_t
277 //
278 
279 //! A matcher for a given path.
280 template < typename Regex_Engine = std_regex_engine_t >
282 {
283  public:
284  using regex_t = typename Regex_Engine::compiled_regex_t;
285  using match_results_t = typename Regex_Engine::match_results_t;
286 
287  //! Creates matcher with a given parameters.
289  http_method_id_t method,
290  regex_t route_regex,
291  std::shared_ptr< std::string > named_params_buffer,
292  param_appender_sequence_t param_appender_sequence )
296  {
298  }
299 
300  /*!
301  * Creates matcher with a given parameters.
302  *
303  * This constructor is intended for cases where method_matcher is
304  * specified as object of class derived from method_matcher_t.
305  *
306  * @since v.0.6.6
307  */
308  template< typename Method_Matcher >
310  Method_Matcher && method_matcher,
311  regex_t route_regex,
312  std::shared_ptr< std::string > named_params_buffer,
313  param_appender_sequence_t param_appender_sequence )
317  {
318  assign(
321  }
322 
323  route_matcher_t() = default;
324  route_matcher_t( route_matcher_t && ) = default;
325 
326  //! Try to match a given request target with this route.
327  bool
329  target_path_holder_t & target_path,
330  route_params_t & parameters ) const
331  {
333  if( Regex_Engine::try_match(
334  target_path.view(),
336  matches ) )
337  {
339 
340  // Data for route_params_t initialization.
341 
343 
344  const string_view_t match{
348 
351 
353 
354  // Std regex and pcre engines handle
355  // trailing groups with empty values differently.
356  // Std despite they are empty includes them in the list of match results;
357  // Pcre on the other hand does not.
358  // So the second for is for pushing empty values
359  std::size_t i = 1;
360  for( ; i < matches.size(); ++i )
361  {
362  const auto & m = matches[ i ];
369  }
370 
371  for( ; i < m_param_appender_sequence.size() + 1; ++i )
372  {
376  }
377 
378  // Init route parameters.
380  parameters,
382  m_named_params_buffer, // Do not move (it is used on each match).
383  std::move( match ),
386 
387  return true;
388  }
389 
390  return false;
391  }
392 
393  inline bool
394  operator () (
395  const http_request_header_t & h,
396  target_path_holder_t & target_path,
397  route_params_t & parameters ) const
398  {
399  return m_method_matcher->match( h.method() ) &&
401  }
402 
403  private:
404  //! HTTP method to match.
406 
407  //! Regex of a given route.
408  regex_t m_route_regex;
409 
410  //! Buffer for named parameters names string views.
412 
413  //! Parameters values.
415 };
416 
417 } /* namespace impl */
418 
419 //
420 // generic_express_request_handler_t
421 //
422 /*!
423  * @brief Type of generic handler for one route.
424  *
425  * Since v.0.6.13 some extra-data can be incorporated into request-object.
426  * In that case request-handler will have a different format in
427  * comparison with previous versions. The type generic_express_request_handler_t
428  * describes a request-handler when extra-data of type @a Extra_Data is
429  * bound to request object.
430  *
431  * @note
432  * If the default extra-data-factory in specified in server's traits
433  * then the old type express_request_handler_t can be used for
434  * the simplicity.
435  *
436  * @since v.0.6.13
437  */
438 template< typename Extra_Data >
443  >;
444 
445 //
446 // express_request_handler_t
447 //
448 /*!
449  * @brief Type of a handler for one route in the case when there is
450  * no extra-data in request object.
451  *
452  * Since v.0.6.13 it's just an alias for generic_express_request_handler_t
453  * for the case when the default extra-data-factory is used in
454  * server's traits.
455  */
458 
459 //
460 // generic_express_route_entry_t
461 //
462 
463 //! A single generic express route entry.
464 /*!
465  Might be helpful for use without express_router_t,
466  if only a single route is needed.
467  It gives the same help with route parameters.
468 */
469 template<
470  typename Regex_Engine,
471  typename Extra_Data_Factory >
473 {
474  public:
476  typename Extra_Data_Factory::data_t
477  >;
479  typename Extra_Data_Factory::data_t
480  >;
481 
482  private:
483  using matcher_init_data_t =
487 
488  template< typename Method_Matcher >
490  Method_Matcher && method_matcher,
491  matcher_init_data_t matcher_data,
492  actual_request_handler_t handler )
493  : m_matcher{
498  , m_handler{ std::move( handler ) }
499  {}
500 
501  public:
503  const generic_express_route_entry_t & ) = delete;
505  const generic_express_route_entry_t & ) = delete;
506 
507  generic_express_route_entry_t() = default;
509  generic_express_route_entry_t && ) = default;
510 
512  operator=( generic_express_route_entry_t && ) = default;
513 
514  template< typename Method_Matcher >
516  Method_Matcher && method_matcher,
517  string_view_t route_path,
518  const path2regex::options_t & options,
519  actual_request_handler_t handler )
523  route_path,
524  options ),
525  std::move( handler ) }
526  {}
527 
528  template< typename Method_Matcher >
530  Method_Matcher && method_matcher,
531  string_view_t route_path,
532  actual_request_handler_t handler )
535  route_path,
537  std::move( handler ) }
538  {}
539 
540  //! Checks if request header matches entry,
541  //! and if so, set route params.
543  bool
545  const http_request_header_t & h,
547  route_params_t & params ) const
548  {
549  return m_matcher( h, target_path, params );
550  }
551 
552  //! Calls a handler of given request with given params.
556  {
557  return m_handler( std::move( rh ), std::move( rp ) );
558  }
559 
560  private:
563 };
564 
565 //
566 // express_route_entry_t
567 //
568 /*!
569  * @brief An alias for a single route entry in the case when the default
570  * extra-data-factory is used in server's traits.
571  *
572  * Since v.0.6.13 this name is just an alias for generic_express_route_entry_t.
573  */
574 template<
575  typename Regex_Engine = std_regex_engine_t >
577  Regex_Engine,
579 
580 //
581 // generic_express_router_t
582 //
583 
584 //! Generic Express.js style router.
585 /*!
586  Express routers acts as a request handler (it means it is a function-object
587  that can be called as a restinio request handler). It aggregates several
588  endpoint-handlers and picks one or none of them to handle the request. The
589  choice of the handler to execute depends on request target and HTTP method.
590 
591  If router finds no handler matching the request then request is considered
592  unmatched.
593 
594  It is possible to set a handler for unmatched requests, otherwise router
595  rejects the request and RESTinio takes care of it.
596 
597  There is a difference between ordinary restinio request handler and the one
598  that is used with experss router: generic_express_request_handler_t. The
599  signature of a handlers that can be put in router has an additional
600  parameter -- a container with parameters extracted from URI (request
601  target).
602 
603  @tparam Regex_Engine Type of regex-engine to be used.
604 
605  @tparam Extra_Data_Factory Type of extra-data-factory specified in
606  server's traits.
607 */
608 template<
609  typename Regex_Engine,
610  typename Extra_Data_Factory >
612 {
613  public:
616  using actual_request_handler_t =
618  Regex_Engine,
619  Extra_Data_Factory
620  >::actual_request_handler_t;
621  using non_matched_handler_t =
623  typename Extra_Data_Factory::data_t
624  >;
625 
626  generic_express_router_t() = default;
628 
629  RESTINIO_NODISCARD
630  request_handling_status_t
632  {
635  for( const auto & entry : m_handlers )
636  {
637  if( entry.match( req->header(), target_path, params ) )
638  {
639  return entry.handle( std::move( req ), std::move( params ) );
640  }
641  }
642 
643  // Here: none of the routes matches this handler.
644 
646  {
647  // If non matched request handler is set
648  // then call it.
650  }
651 
652  return request_not_handled();
653  }
654 
655  //! Add handlers.
656  //! \{
657  template< typename Method_Matcher >
658  void
663  {
664  add_handler(
666  route_path,
668  std::move( handler ) );
669  }
670 
671  template< typename Method_Matcher >
672  void
676  const path2regex::options_t & options,
678  {
681  route_path,
682  options,
683  std::move( handler ) );
684  }
685 
686  void
690  {
691  add_handler(
693  route_path,
694  std::move( handler ) );
695  }
696 
697  void
700  const path2regex::options_t & options,
702  {
703  add_handler(
705  route_path,
706  options,
707  std::move( handler ) );
708  }
709 
710  void
714  {
715  add_handler(
716  http_method_get(),
717  route_path,
718  std::move( handler ) );
719  }
720 
721  void
724  const path2regex::options_t & options,
726  {
727  add_handler(
728  http_method_get(),
729  route_path,
730  options,
731  std::move( handler ) );
732  }
733 
734  void
738  {
739  add_handler(
741  route_path,
742  std::move( handler ) );
743  }
744 
745  void
748  const path2regex::options_t & options,
750  {
751  add_handler(
753  route_path,
754  options,
755  std::move( handler ) );
756  }
757 
758  void
762  {
763  add_handler(
765  route_path,
766  std::move( handler ) );
767  }
768 
769  void
772  const path2regex::options_t & options,
774  {
775  add_handler(
777  route_path,
778  options,
779  std::move( handler ) );
780  }
781 
782  void
786  {
787  add_handler(
788  http_method_put(),
789  route_path,
790  std::move( handler ) );
791  }
792 
793  void
796  const path2regex::options_t & options,
798  {
799  add_handler(
800  http_method_put(),
801  route_path,
802  options,
803  std::move( handler ) );
804  }
805  //! \}
806 
807  //! Set handler for requests that don't match any route.
808  void
810  {
812  }
813 
814  private:
816  Regex_Engine,
818  >;
819 
820  //! A list of existing routes.
822 
823  //! Handler that is called for requests that don't match any route.
825 };
826 
827 //
828 // express_router_t
829 //
830 /*!
831  * @brief A type of express-like router for the case when the default
832  * extra-data-factory is specified in the server's traits.
833  *
834  * Since v.0.6.13 this type is just an alias for generic_express_router_t
835  * with the default extra-data-factory type.
836  *
837  * @tparam Regex_Engine Type of regex-engine to be used.
838  */
839 template<
840  typename Regex_Engine = std_regex_engine_t >
842  Regex_Engine,
844 
845 } /* namespace router */
846 
847 //! Cast named parameter value to a given type.
848 template < typename Value_Type >
849 Value_Type
850 get( const router::route_params_t & params, string_view_t key )
851 {
852  return get< Value_Type >( params[ key ] );
853 }
854 
855 //! Cast indexed parameter value to a given type.
856 template < typename Value_Type >
857 Value_Type
858 get( const router::route_params_t & params, std::size_t index )
859 {
860  return get< Value_Type >( params[ index ] );
861 }
862 
863 } /* namespace restinio */
static const auto & indexed_parameters(const route_params_t &rp) noexcept
Definition: express.hpp:228
generic_express_route_entry_t(generic_express_route_entry_t &&)=default
route_params_appender_t(route_params_t::named_parameters_container_t &named_parameters, route_params_t::indexed_parameters_container_t &indexed_parameters)
Definition: express.hpp:243
string_view_t operator[](std::size_t i) const
Get indexed parameter.
Definition: express.hpp:123
generic_express_route_entry_t & operator=(const generic_express_route_entry_t &)=delete
void non_matched_request_handler(non_matched_handler_t nmrh)
Set handler for requests that don&#39;t match any route.
Definition: express.hpp:809
void add_indexed_param(string_view_t value)
Definition: express.hpp:262
route_matcher_t(route_matcher_t &&)=default
generic_express_route_entry_t()=default
Route params private internals accessor.
Definition: express.hpp:199
route_params_t & operator=(route_params_t &&)=default
RESTINIO_NODISCARD request_handling_status_t handle(actual_request_handle_t rh, route_params_t rp) const
Calls a handler of given request with given params.
Definition: express.hpp:555
void add_named_param(string_view_t key, string_view_t value)
Definition: express.hpp:256
route_matcher_t(http_method_id_t method, regex_t route_regex, std::shared_ptr< std::string > named_params_buffer, param_appender_sequence_t param_appender_sequence)
Creates matcher with a given parameters.
Definition: express.hpp:288
regex_t m_route_regex
Regex of a given route.
Definition: express.hpp:408
auto indexed_parameters_size() const noexcept
Definition: express.hpp:134
actual_request_handler_t m_handler
Definition: express.hpp:562
std::shared_ptr< std::string > m_named_params_buffer
Buffer for named parameters names string views.
Definition: express.hpp:411
auto named_parameters_size() const noexcept
Get number of parameters.
Definition: express.hpp:133
bool match_route(target_path_holder_t &target_path, route_params_t &parameters) const
Try to match a given request target with this route.
Definition: express.hpp:328
std::shared_ptr< std::string > m_key_names_buffer
Shared buffer for string_view of named parameterts names.
Definition: express.hpp:179
std::unique_ptr< char[] > m_request_target
A raw request target.
Definition: express.hpp:176
generic_express_route_entry_t(Method_Matcher &&method_matcher, string_view_t route_path, const path2regex::options_t &options, actual_request_handler_t handler)
Definition: express.hpp:515
static const auto & named_parameters(const route_params_t &rp) noexcept
Get values containers for all parameters (used in unit tests).
Definition: express.hpp:222
named_parameters_container_t::const_reference find_named_parameter_with_check(string_view_t key) const
Definition: express.hpp:151
static void match(route_params_t &rp, std::unique_ptr< char[] > request_target, std::shared_ptr< std::string > key_names_buffer, string_view_t match_, route_params_t::named_parameters_container_t named_parameters, route_params_t::indexed_parameters_container_t indexed_parameters)
Init parameters with a matched route params.
Definition: express.hpp:203
indexed_parameters_container_t m_indexed_parameters
Indexed params.
Definition: express.hpp:188
generic_express_route_entry_t & operator=(generic_express_route_entry_t &&)=default
string_view_t m_match
Matched pattern.
Definition: express.hpp:182
A matcher for a given path.
Definition: express.hpp:281
route_params_t(route_params_t &&)=default
named_parameters_container_t m_named_parameters
Named params.
Definition: express.hpp:185
generic_express_route_entry_t(const generic_express_route_entry_t &)=delete
generic_express_router_t(generic_express_router_t &&)=default
Value_Type get(const router::route_params_t &params, std::size_t index)
Cast indexed parameter value to a given type.
Definition: express.hpp:858
string_view_t match() const noexcept
Matched route.
Definition: express.hpp:93
route_params_t & operator=(const route_params_t &)=delete
route_matcher_t(Method_Matcher &&method_matcher, regex_t route_regex, std::shared_ptr< std::string > named_params_buffer, param_appender_sequence_t param_appender_sequence)
Definition: express.hpp:309
route_params_appender_t & operator=(const route_params_appender_t &)=delete
route_params_t::named_parameters_container_t & m_named_parameters
Definition: express.hpp:268
route_params_appender_t & operator=(route_params_appender_t &&)=delete
route_params_appender_t(const route_params_appender_t &)=delete
param_appender_sequence_t m_param_appender_sequence
Parameters values.
Definition: express.hpp:414
bool operator()(const http_request_header_t &h, target_path_holder_t &target_path, route_params_t &parameters) const
Definition: express.hpp:394
std::vector< route_entry_t > m_handlers
A list of existing routes.
Definition: express.hpp:821
bool has(string_view_t key) const noexcept
Check parameter.
Definition: express.hpp:104
named_parameters_container_t::const_iterator find_named_parameter(string_view_t key) const noexcept
Definition: express.hpp:139
void match(std::unique_ptr< char[] > request_target, std::shared_ptr< std::string > key_names_buffer, string_view_t match, named_parameters_container_t named_parameters, indexed_parameters_container_t indexed_parameters)
Definition: express.hpp:69
Helper class for gthering parameters from route.
Definition: express.hpp:240
optional_t< string_view_t > get_param(string_view_t key) const noexcept
Get the value of a parameter if it exists.
Definition: express.hpp:112
non_matched_handler_t m_non_matched_request_handler
Handler that is called for requests that don&#39;t match any route.
Definition: express.hpp:824
route_params_appender_t(route_params_appender_t &&)=delete
generic_express_route_entry_t(Method_Matcher &&method_matcher, string_view_t route_path, actual_request_handler_t handler)
Definition: express.hpp:529
route_params_t(const route_params_t &)=delete
route_params_t::indexed_parameters_container_t & m_indexed_parameters
Definition: express.hpp:269
buffered_matcher_holder_t m_method_matcher
HTTP method to match.
Definition: express.hpp:405
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