diff --git a/cppcms/http_context.h b/cppcms/http_context.h index 9977d9ea..ae1d754d 100644 --- a/cppcms/http_context.h +++ b/cppcms/http_context.h @@ -250,6 +250,12 @@ namespace cppcms { set_holder(0); return r; } + + protected: + ///\ the method can be used by a the dummy http context to prepare the request -> parse the content type + ///\ note: this is needed in order to be able to test response and request media type url dispatching. + bool prepare_request(); + private: void set_holder(holder *p); diff --git a/cppcms/url_dispatcher.h b/cppcms/url_dispatcher.h index c5446ab3..e71d7ed0 100644 --- a/cppcms/url_dispatcher.h +++ b/cppcms/url_dispatcher.h @@ -64,6 +64,10 @@ namespace cppcms { /// Parameters are parsed using `bool parse_url_parameter(util::const_char_istream ¶meter,Type ¶m)` /// that by default uses std::istream to perform casting. /// + /// Newer `map` interface allows matching an URL, an HTTP request method, an HTTP response content media type, an HTTP accept header, an HTTP request content media type,. + /// Parameters are parsed using `bool parse_url_parameter(util::const_char_istream ¶meter,Type ¶m)` + /// that by default uses std::istream to perform casting. + /// /// Additionally every matched parameter is checked to contain valid text encoding /// /// Newer API uses map member functions family that was introduced in CppCMS 1.1. @@ -90,8 +94,11 @@ namespace cppcms { /// /* New API - CppCMS 1.1 and above */ /// /// dispatcher().map("GET","/resource/(\\d+)",&my_web_project::get_resource,this,1); + /// dispatcher().map("GET","text/html","/resource/(\\d+)",&my_web_project::get_resource,this,1); + /// dispatcher().map("GET",{"application/json", "application/json"},"/resource/(\\d+)",&my_web_project::get_resource,this,1); /// dispatcher().map("PUT","/resource/(\\d+)",&my_web_project::update_resource,this,1); /// dispatcher().map("POST","/resources",&my_web_project::new_resource,this); + /// dispatcher().map("POST","text/html","application/x-www-form-urlencoded","/resources",&my_web_project::new_resource,this); /// dispatcher().map("GET"."/id_by_name/(.*)",&my_web_project::id_by_name,this,1); /// dispatcher().map("/page/(\\d+)",&my_web_project::display_page_by_id,this,1); /* any method */ /// ... @@ -128,6 +135,29 @@ namespace cppcms { /// \ver{v1_2} void map_generic(std::string const &method,booster::regex const &re,generic_handler const &h); /// + /// Map a callback \a h to a URL matching regular expression \a re and an HTTP \a method \a media_type + /// + /// \param method - HTTP method to match like GET, POST, note regular expression can be used as well, for example "(POST|PUT)" + /// \param response_media_type - HTTP response media type of content type and also to match against the Accept header like text/html, text/javascript, + /// note since the http accept header may contain a list of media types the given string must be within list. + /// \param re - regular expression to match the URL + /// \param h - handler to execute + /// + /// \ver{vnext} + void map_generic(std::string const &method,std::string const &response_media_type,booster::regex const &re,generic_handler const &h); + /// + /// Map a callback \a h to a URL matching regular expression \a re and an HTTP \a method \a media_type + /// + /// \param method - HTTP method to match like GET, POST, note regular expression can be used as well, for example "(POST|PUT)" + /// \param response_media_type - HTTP response media type of content type and also to match against the Accept header like text/html, text/javascript, + /// note since the http accept header may contain a list of media types the given string must be within list. + /// \param request_media_type - HTTP request media type of content type is matched against the media type of the http request. + /// \param re - regular expression to match the URL + /// \param h - handler to execute + /// + /// \ver{vnext} + void map_generic(std::string const &method,std::string const &response_media_type, std::string const &request_media_type,booster::regex const &re,generic_handler const &h); + /// /// Map a callback \a h to a URL matching regular expression \a re /// /// \param re - regular expression to match the URL @@ -139,6 +169,92 @@ namespace cppcms { #ifdef CPPCMS_DOXYGEN_DOCS + + /// + /// \brief Map \a member of \a app as a URL handler that matches the \a method, \a response_media_type, \a request_media_type and regular expression \a re, HTTP method \a method and HTTP media_type \a media_type + /// + /// \param method - HTTP method to match like GET, POST, note regular expression can be used as well, for example "(POST|PUT)" + /// \param response_media_type - HTTP response media type of content type and also to match against the Accept header like text/html, text/javascript, + /// note since the http accept header may contain a list of media types the given string must be within list. + /// \param request_media_type - HTTP request media type of content type is matched against the media type of the http request. + /// \param re - regular expression to match the URL + /// \param member - member function of application \a app + /// \param app - application that its \a member is called + /// \param groups - matched groups converted to ApplicationMemberArgs + /// + /// Note: + /// + /// - number of integers in \a groups should match the number of arguments of \a member + /// - \a member can have up to 8 arguments MemberArgs and the function should receive same number of integer parameters representing matched groups + /// + /// For exaample + /// \code + /// class foo : public cppcms::application { + /// ... + /// void page(int x,std::string const &name); + /// void update(int x); + /// foo(...) + /// { + /// dispatcher().map("GET","application/json","application/json","/page/(\\d+)(/(.*))?",&foo::page,this,1,3); + /// dispatcher().map("POST","text/html","application/x-www-form-urlencoded","/update/(\\d+)",&foo::update,this,1); + /// } + /// \endcode + /// + /// When the request matches the \a method, \a response_media_type, \a request_media_type and regular expression \a re, \a member of \a app is called, For case of `page` that has two + /// parameters the first matched group is converted to integer and passed to as first parameter and 3rd group is passed as string to 2nd parameter + /// + /// In case of `update` - that has only 1 parameter, a single integer should be passed + /// + /// In addition to calling \a member function it calls app->init() before call + /// and app->clean() after the call if Application is derived from cppcms::application + /// + /// + /// \ver{vnext} + template + void map(std::string const &method,std::string const &response_media_type, std::string const &request_media_type,std::string const &re,void (Application::*member)(ApplicationMemberArgs...),Application *app, int ... groups ); + + /// + /// \brief Map \a member of \a app as a URL handler that matches the \a method, \a response_media_type and regular expression \a re, HTTP method \a method and HTTP media_type \a media_type + /// + /// \param method - HTTP method to match like GET, POST, note regular expression can be used as well, for example "(POST|PUT)" + /// \param response_media_type - HTTP response media type of content type and also to match against the Accept header like text/html, text/javascript, + /// note since the http accept header may contain a list of media types the given string must be within list. + /// \param re - regular expression to match the URL + /// \param member - member function of application \a app + /// \param app - application that its \a member is called + /// \param groups - matched groups converted to ApplicationMemberArgs + /// + /// Note: + /// + /// - number of integers in \a groups should match the number of arguments of \a member + /// - \a member can have up to 8 arguments MemberArgs and the function should receive same number of integer parameters representing matched groups + /// + /// For exaample + /// \code + /// class foo : public cppcms::application { + /// ... + /// void page(int x,std::string const &name); + /// void update(int x); + /// foo(...) + /// { + /// dispatcher().map("GET","text/html","/page/(\\d+)(/(.*))?",&foo::page,this,1,3); + /// dispatcher().map("POST","text/html","/update/(\\d+)",&foo::update,this,1); + /// } + /// \endcode + /// + /// When the reuqest matches the \a method, \a response_media_type and regular expression \a re, \a member of \a app is called, For case of `page` that has two + /// parameters the first matched group is converted to integer and passed to as first parameter and 3rd group is passed as string to 2nd parameter + /// + /// In case of `update` - that has only 1 parameter, a single integer should be passed + /// + /// In addition to calling \a member function it calls app->init() before call + /// and app->clean() after the call if Application is derived from cppcms::application + /// + /// + /// \ver{vnext} + template + void map(std::string const &method,std::string const &response_media_type,std::string const &re,void (Application::*member)(ApplicationMemberArgs...),Application *app, int ... groups ); + /// /// \brief Map \a member of \a app as a URL handler that matches regualr expression \a re and HTTP method \a method /// @@ -219,6 +335,93 @@ namespace cppcms { void map(std::string const &re,void (Application::*member)(ApplicationMemberArgs...),Application *app, int ... groups ); + /// + /// \brief Map \a member of \a app as a URL handler that matches regualr expression \a re, HTTP method \a method and HTTP media_type \a media_type + /// + /// \param method - HTTP method to match like GET, POST, note regular expression can be used as well, for example "(POST|PUT)" + /// \param response_media_type - HTTP response media type of content type and also to match against the Accept header like text/html, text/javascript, + /// note since the http accept header may contain a list of media types the given string must be within list. + /// \param request_media_type - HTTP request media type of content type is matched against the media type of the http request. + /// \param re - regular expression to match the URL + /// \param member - member function of application \a app + /// \param app - application that its \a member is called + /// \param groups - matched groups converted to ApplicationMemberArgs + /// + /// Note: + /// + /// - number of integers in \a groups should match the number of arguments of \a member + /// - \a member can have up to 8 arguments MemberArgs and the function should receive same number of integer parameters representing matched groups + /// + /// For exaample + /// \code + /// class foo : public cppcms::application { + /// ... + /// void page(int x,std::string const &name); + /// void update(int x); + /// foo(...) + /// { + /// using booster::regex; + /// dispatcher().map("POST","application/json","application/json",regex("/page/(\\d+)(/(.*))?",regex::icase),&foo::page,this,1,3); + /// dispatcher().map("POST","text/html","application/x-www-form-urlencoded","/update/(\\d+)",&foo::update,this,1); + /// } + /// \endcode + /// + /// When the reuqest matches the \a method, \a media_type and regualr expression \a re, \a member of \a app is called, For case of `page` that has two + /// parameters the first matched group is converted to integer and passed to as first parameter and 3rd group is passed as string to 2nd parameter + /// + /// In case of `update` - that has only 1 parameter, a single integer should be passed + /// + /// In addition to calling \a member function it calls app->init() before call + /// and app->clean() after the call if Application is derived from cppcms::application + /// + /// + /// \ver{v1_2} + template + void map(std::string const &method,std::string const &response_media_type, std::string const &request_media_type,booster::regex const &re,void (Application::*member)(ApplicationMemberArgs...),Application *app, int ... groups ); + + /// + /// \brief Map \a member of \a app as a URL handler that matches regualr expression \a re, HTTP method \a method and HTTP media_type \a media_type + /// + /// \param method - HTTP method to match like GET, POST, note regular expression can be used as well, for example "(POST|PUT)" + /// \param response_media_type - HTTP response media type of content type and also to match against the Accept header like text/html, text/javascript, + /// note since the http accept header may contain a list of media types the given string must be within list. + /// \param re - regular expression to match the URL + /// \param member - member function of application \a app + /// \param app - application that its \a member is called + /// \param groups - matched groups converted to ApplicationMemberArgs + /// + /// Note: + /// + /// - number of integers in \a groups should match the number of arguments of \a member + /// - \a member can have up to 8 arguments MemberArgs and the function should receive same number of integer parameters representing matched groups + /// + /// For exaample + /// \code + /// class foo : public cppcms::application { + /// ... + /// void page(int x,std::string const &name); + /// void update(int x); + /// foo(...) + /// { + /// using booster::regex; + /// dispatcher().map("POST","text/hml",regex("/page/(\\d+)(/(.*))?",regex::icase),&foo::page,this,1,3); + /// dispatcher().map("POST","text/hml","/update/(\\d+)",&foo::update,this,1); + /// } + /// \endcode + /// + /// When the reuqest matches the \a method, \a media_type and regualr expression \a re, \a member of \a app is called, For case of `page` that has two + /// parameters the first matched group is converted to integer and passed to as first parameter and 3rd group is passed as string to 2nd parameter + /// + /// In case of `update` - that has only 1 parameter, a single integer should be passed + /// + /// In addition to calling \a member function it calls app->init() before call + /// and app->clean() after the call if Application is derived from cppcms::application + /// + /// + /// \ver{v1_2} + template + void map(std::string const &method,std::string const &response_media_type,booster::regex const &re,void (Application::*member)(ApplicationMemberArgs...),Application *app, int ... groups ); + /// /// \brief Map \a member of \a app as a URL handler that matches regualr expression \a re and HTTP method \a method /// @@ -709,6 +912,22 @@ namespace cppcms { }; \ public: \ template \ + void map(std::string const &me,std::string const &rpmt,std::string const &rqmt,std::string const &re, \ + void (C::*mb)(CPPCMS_URLBINDER_MPAR),C *app CPPCMS_URLBINDER_PRD \ + CPPCMS_URLBINDER_IPAR) \ + { \ + typedef url_binder btype; \ + map_generic(me,rpmt,rqmt,re,btype(mb,app CPPCMS_URLBINDER_PRD CPPCMS_URLBINDER_PPAR)); \ + } \ + template \ + void map(std::string const &me,std::string const &rpmt,std::string const &re, \ + void (C::*mb)(CPPCMS_URLBINDER_MPAR),C *app CPPCMS_URLBINDER_PRD \ + CPPCMS_URLBINDER_IPAR) \ + { \ + typedef url_binder btype; \ + map_generic(me,rpmt,re,btype(mb,app CPPCMS_URLBINDER_PRD CPPCMS_URLBINDER_PPAR)); \ + } \ + template \ void map(std::string const &me,std::string const &re, \ void (C::*mb)(CPPCMS_URLBINDER_MPAR),C *app CPPCMS_URLBINDER_PRD \ CPPCMS_URLBINDER_IPAR) \ @@ -725,6 +944,22 @@ namespace cppcms { map_generic(re,btype(mb,app CPPCMS_URLBINDER_PRD CPPCMS_URLBINDER_PPAR)); \ } \ template \ + void map(std::string const &me,std::string const &rpmt,std::string const &rqmt,booster::regex const &re, \ + void (C::*mb)(CPPCMS_URLBINDER_MPAR),C *app CPPCMS_URLBINDER_PRD \ + CPPCMS_URLBINDER_IPAR) \ + { \ + typedef url_binder btype; \ + map_generic(me,rpmt,rqmt,re,btype(mb,app CPPCMS_URLBINDER_PRD CPPCMS_URLBINDER_PPAR)); \ + } \ + template \ + void map(std::string const &me,std::string const &rpmt,booster::regex const &re, \ + void (C::*mb)(CPPCMS_URLBINDER_MPAR),C *app CPPCMS_URLBINDER_PRD \ + CPPCMS_URLBINDER_IPAR) \ + { \ + typedef url_binder btype; \ + map_generic(me,rpmt,re,btype(mb,app CPPCMS_URLBINDER_PRD CPPCMS_URLBINDER_PPAR)); \ + } \ + template \ void map(std::string const &me,booster::regex const &re, \ void (C::*mb)(CPPCMS_URLBINDER_MPAR),C *app CPPCMS_URLBINDER_PRD \ CPPCMS_URLBINDER_IPAR) \ diff --git a/src/http_context.cpp b/src/http_context.cpp index f745e5d5..9a9415b1 100644 --- a/src/http_context.cpp +++ b/src/http_context.cpp @@ -58,6 +58,11 @@ context::context(booster::shared_ptr conn) : skin(service().views_pool().default_skin()); } +bool context::prepare_request() +{ + return request().prepare(); +} + void context::set_holder(holder *p) { d->specific.reset(p); @@ -236,7 +241,7 @@ int context::on_headers_ready() if(!pool) return 404; - request().prepare(); + prepare_request(); int flags; diff --git a/src/url_dispatcher.cpp b/src/url_dispatcher.cpp index c85fcf30..f7caed17 100644 --- a/src/url_dispatcher.cpp +++ b/src/url_dispatcher.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -21,14 +22,18 @@ namespace cppcms { struct option : public booster::noncopyable { option(booster::regex const &expr) : expr_(expr), - match_method_(0) + match_method_(0), + match_accept_(false), + match_media_type_(false) { } option(booster::regex const &expr,std::string const &method) : expr_(expr), match_method_(1), mexpr_(method), - method_(method) + method_(method), + match_accept_(false), + match_media_type_(false) { for(size_t i=0;imain(match_[select_]); + if(match_accept_) // set response content media type if matching accept header is enabled + app_->response().set_content_header(response_media_type_); return true; } return false; @@ -99,9 +168,10 @@ namespace cppcms { select_[4]=e; select_[5]=f; } - virtual bool dispatch(std::string const &url,char const *method,application *) + + virtual bool dispatch(std::string const &url,char const *method,std::string const &accept,std::string const &media_type,application *) { - if(matches(url,method)) { + if(matches(url,method,accept,media_type)) { execute_handler(handle_); return true; } @@ -154,20 +224,32 @@ namespace cppcms { handle_(h) { } - generic_option(std::string method,booster::regex const &r,url_dispatcher::generic_handler const &h) : + generic_option(std::string const &method,booster::regex const &r,url_dispatcher::generic_handler const &h) : option(r,method), handle_(h) { } - virtual bool dispatch(std::string const &url,char const *method,application *app) + generic_option(std::string const &method,std::string const &response_media_type,booster::regex const &r,url_dispatcher::generic_handler const &h) : + option(r,method,response_media_type), + handle_(h) + { + } + generic_option(std::string const &method,std::string const &response_media_type,std::string const &request_media_type,booster::regex const &r,url_dispatcher::generic_handler const &h) : + option(r,method,response_media_type,request_media_type), + handle_(h) + { + } + + virtual bool dispatch(std::string const &url,char const *method,std::string const &accept,std::string const &media_type,application *app) { if(!app) return false; - if(matches(url,method)) { + if(matches(url,method,accept,media_type)) { return handle_(*app,match_); } return false; } + url_dispatcher::generic_handler handle_; }; @@ -203,16 +285,20 @@ namespace cppcms { unsigned i; std::string method; char const *cmethod = 0; + std::string accept; + std::string media_type; application *app = d->app; if(app && app->has_context()) { method = app->request().request_method(); cmethod = method.c_str(); + accept = app->request().http_accept(); + media_type = app->request().content_type_parsed().media_type(); } else { app = 0; } for(i=0;ioptions.size();i++) { - if(d->options[i]->dispatch(url,cmethod,app)) + if(d->options[i]->dispatch(url,cmethod,accept,media_type,app)) return true; } return false; @@ -235,6 +321,16 @@ namespace cppcms { d->options.push_back(make_handler(expr,h)); } + void url_dispatcher::map_generic(std::string const &method,std::string const &response_media_type,std::string const &request_media_type,booster::regex const &re,generic_handler const &h) + { + booster::shared_ptr