From e28ab8f48c891c03cf4b3a8ed88b98d38a561960 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 8 Dec 2015 13:45:08 +0200 Subject: Separate brep module configuration from Apache server configuration --- web/apache/request | 8 +++--- web/apache/service | 39 ++++++++++++++++++----------- web/apache/service.cxx | 66 ++++++++++++++++++++++++++++++++------------------ web/apache/service.txx | 11 +++++++-- web/module | 47 +++++++++++++++++++++++------------ 5 files changed, 112 insertions(+), 59 deletions(-) (limited to 'web') diff --git a/web/apache/request b/web/apache/request index 88b38a9..8861dac 100644 --- a/web/apache/request +++ b/web/apache/request @@ -43,6 +43,11 @@ namespace web int flush (); + // Return true if content have been sent to the client, false otherwise. + // + bool + get_write_state () const noexcept {return write_state_;} + // Get request path. // virtual const path_type& @@ -98,9 +103,6 @@ namespace web void parse_parameters (const char* args); - bool - get_write_state () const noexcept {return write_state_;} - virtual void set_write_state () { diff --git a/web/apache/service b/web/apache/service index 33d5a0a..7eef81a 100644 --- a/web/apache/service +++ b/web/apache/service @@ -8,9 +8,7 @@ #include #include -#include #include -#include // move() #include #include @@ -23,14 +21,10 @@ namespace web class service: ::module { public: - using option_names = std::vector; - // Note that the module exemplar is stored by-reference. // template - service (const std::string& name, - M& exemplar, - option_names opts = option_names ()) + service (const std::string& name, M& exemplar) : ::module { STANDARD20_MODULE_STUFF, @@ -42,8 +36,7 @@ namespace web ®ister_hooks }, name_ (name), - exemplar_ (exemplar), - option_names_ (std::move (opts)) + exemplar_ (exemplar) { init_directives (); @@ -74,17 +67,31 @@ namespace web static void register_hooks (apr_pool_t*) noexcept { - // The registered function is called right after apache worker - // process is started. Called for every new process spawned. + // The config_finalizer() function is called at the end of Apache + // server configuration parsing. + // + ap_hook_post_config (&config_finalizer, NULL, NULL, APR_HOOK_LAST); + + // The worker_initializer() function is called right after Apache + // worker process is started. Called for every new process spawned. // ap_hook_child_init (&worker_initializer, NULL, NULL, APR_HOOK_LAST); - // The registered function is called for each client request. + // The request_handler () function is called for each client request. // ap_hook_handler (&request_handler, NULL, NULL, APR_HOOK_LAST); } template + static int + config_finalizer (apr_pool_t*, apr_pool_t*, apr_pool_t*, server_rec*) + noexcept + { + instance ()->options_parsed_ = true; + return OK; + } + + template static void worker_initializer (apr_pool_t*, server_rec* server) noexcept { @@ -112,7 +119,10 @@ namespace web init_worker (log& l) noexcept; static const char* - add_option (cmd_parms* parms, void* mconfig, const char* args) noexcept; + parse_option (cmd_parms* parms, void* mconfig, const char* args) noexcept; + + const char* + add_option (const char* name, optional value); template int handle (request& r, log& l) noexcept; @@ -120,8 +130,9 @@ namespace web private: std::string name_; module& exemplar_; - option_names option_names_; + option_descriptions option_descriptions_; name_values options_; + bool options_parsed_ = false; }; } } diff --git a/web/apache/service.cxx b/web/apache/service.cxx index 3bd60f6..1741af3 100644 --- a/web/apache/service.cxx +++ b/web/apache/service.cxx @@ -10,12 +10,15 @@ #include #include -#include // unique_ptr +#include // unique_ptr #include #include -#include // strlen() +#include // move() +#include // strlen() #include +#include + using namespace std; namespace web @@ -34,19 +37,19 @@ namespace web // bar of module foo the corresponding directive will appear in apache // configuration file as foo-bar. // - unique_ptr directives ( - new command_rec[option_names_.size () + 1]); - + const option_descriptions& od (exemplar_.options ()); + unique_ptr directives (new command_rec[od.size () + 1]); command_rec* d (directives.get ()); - for (auto& o: option_names_) + for (const auto& o: od) { - o = name_ + "-" + o; + auto i (option_descriptions_.emplace (name_ + "-" + o.first, o.second)); + assert (i.second); *d++ = { - o.c_str (), - reinterpret_cast (add_option), + i.first->first.c_str (), + reinterpret_cast (parse_option), this, RSRC_CONF, // Move away from TAKE1 to be able to handle empty string and @@ -58,36 +61,51 @@ namespace web } *d = {nullptr, nullptr, nullptr, 0, RAW_ARGS, nullptr}; - cmds = directives.release (); } const char* service:: - add_option (cmd_parms* parms, void*, const char* args) noexcept + parse_option (cmd_parms* parms, void*, const char* args) noexcept { + // @@ Current implementation does not consider configuration context + // (server config, virtual host, directory) for directive parsing, nor + // for request handling. + // service& srv (*reinterpret_cast (parms->cmd->cmd_data)); - string name (parms->cmd->name + srv.name_.length () + 1); - optional value; - // 'args' is an optionally double-quoted string. Use double quotes to - // distinguish empty string from no-value case. + if (srv.options_parsed_) + // Apache is inside the second pass of its messy initialization cycle + // (more details at http://wiki.apache.org/httpd/ModuleLife). Just + // ignore it. + // + return 0; + + // 'args' is an optionally double-quoted string. It uses double quotes + // to distinguish empty string from no-value case. // assert (args != nullptr); + + optional value; if (auto l = strlen (args)) value = l >= 2 && args[0] == '"' && args[l - 1] == '"' ? string (args + 1, l - 2) : args; - for (auto& v: srv.options_) - { - if (v.name == name) - { - v.value = value; - return 0; - } - } + return srv.add_option (parms->cmd->name, move (value)); + } + + const char* service:: + add_option (const char* name, optional value) + { + auto i (option_descriptions_.find (name)); + assert (i != option_descriptions_.end ()); + + // Check that option value presense is expected. + // + if (i->second != static_cast (value)) + return value ? "unexpected value" : "value expected"; - srv.options_.emplace_back (name, value); + options_.emplace_back (name + name_.length () + 1, move (value)); return 0; } diff --git a/web/apache/service.txx b/web/apache/service.txx index 179980c..25a7435 100644 --- a/web/apache/service.txx +++ b/web/apache/service.txx @@ -20,8 +20,15 @@ namespace web try { M m (static_cast (exemplar_)); - static_cast (m).handle (r, r, l); - return r.flush (); + + if (static_cast (m).handle (r, r, l)) + return r.flush (); + + if (!r.get_write_state ()) + return DECLINED; + + l.write (nullptr, 0, func_name.c_str (), APLOG_ERR, + "handling declined while unbuffered content has been written"); } catch (const invalid_request& e) { diff --git a/web/module b/web/module index 1774884..50cc6be 100644 --- a/web/module +++ b/web/module @@ -5,6 +5,7 @@ #ifndef WEB_MODULE #define WEB_MODULE +#include #include #include #include @@ -18,6 +19,8 @@ namespace web { + using butl::optional; + // HTTP status code. // // @@ Define some commonly used constants? @@ -58,7 +61,10 @@ namespace web sequence_error (std::string d): std::runtime_error (std::move (d)) {} }; - using butl::optional; + // Map of module configuration option names to the boolean flag indicating + // whether the value is expected for the option. + // + using option_descriptions = std::map; struct name_value { @@ -182,27 +188,36 @@ namespace web class module { public: - // During startup the web server calls this function on the - // module exemplar passing a list of configuration name-value - // pairs. The place these configuration pairs come from is - // implementation-specific (normally a configuration file). - // Any exception thrown by this function terminates the web + // Description of configuration options supported by this module. Note: + // should be callable during static initialization. + // + virtual option_descriptions + options () = 0; + + // During startup the web server calls this function on the module + // exemplar passing a list of configuration options. The place these + // configuration options come from is implementation-specific (normally + // a configuration file). The web server guarantees that only options + // listed in the map returned by the options() function above can be + // present. Any exception thrown by this function terminates the web // server. // virtual void init (const name_values&, log&) = 0; - // Any exception other than invalid_request described above that - // leaves this function is treated by the web server implementation - // as an internal server error (500). Similar to invalid_request, - // it will try to return the status and description (obtained by - // calling what() on std::exception) to the client, if possible. - // The description is assume to be encoded in UTF-8. The - // implementation may provide a configuration option to omit - // the description from the response, for security/privacy - // reasons. + // Return false if decline to handle the request. If handling have been + // declined after any unbuffered content has been written, then the + // implementation shall terminate the response in a suitable but + // unspecified manner. Any exception other than invalid_request described + // above that leaves this function is treated by the web server + // implementation as an internal server error (500). Similar to + // invalid_request, it will try to return the status and description + // (obtained by calling what() on std::exception) to the client, if + // possible. The description is assume to be encoded in UTF-8. The + // implementation may provide a configuration option to omit the + // description from the response, for security/privacy reasons. // - virtual void + virtual bool handle (request&, response&, log&) = 0; }; } -- cgit v1.1