From b72424fca7a6af6ff7921101c450850fef875152 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sun, 6 Mar 2016 13:52:48 +0300 Subject: Support multiple instances of brep in a single Apache instance --- brep/database.cxx | 58 ++++++----- brep/mod-package-details | 8 ++ brep/mod-package-details.cxx | 23 +++-- brep/mod-package-search | 8 ++ brep/mod-package-search.cxx | 23 +++-- brep/mod-package-version-details | 8 ++ brep/mod-package-version-details.cxx | 21 ++-- brep/mod-repository-details | 8 ++ brep/mod-repository-details.cxx | 21 ++-- brep/mod-repository-root | 6 ++ brep/mod-repository-root.cxx | 33 +++++- brep/module | 2 +- brep/module.cxx | 12 ++- web/apache/service | 189 ++++++++++++++++++++++++++++++----- web/apache/service.cxx | 167 +++++++++++++++++++++++-------- web/apache/service.txx | 156 +++++++++++++++++++++++++---- web/module | 12 +++ 17 files changed, 606 insertions(+), 149 deletions(-) diff --git a/brep/database.cxx b/brep/database.cxx index 9177b55..17605c5 100644 --- a/brep/database.cxx +++ b/brep/database.cxx @@ -4,40 +4,50 @@ #include +#include + #include namespace brep { + namespace options + { + bool + operator< (const db& x, const db& y) + { + int r; + if ((r = x.db_user ().compare (y.db_user ())) != 0 || + (r = x.db_password ().compare (y.db_password ())) != 0 || + (r = x.db_name ().compare (y.db_name ())) != 0 || + (r = x.db_host ().compare (y.db_host ()))) + return r < 0; + + return x.db_port () < y.db_port (); + } + } + shared_ptr shared_database (const options::db& o) { using odb::pgsql::database; - static weak_ptr db; - // In C++11, function-static variable initialization is guaranteed to be - // thread-safe, thought this doesn't seem to be enough in our case - // (because we are re-initializing the weak pointer). - // - if (shared_ptr d = db.lock ()) - { - if (o.db_user () != d->user () || - o.db_password () != d->password () || - o.db_name () != d->db () || - o.db_host () != d->host () || - o.db_port () != d->port ()) - throw runtime_error ("shared database options mismatch"); - - return d; - } - else + static std::map> databases; + + auto i (databases.find (o)); + if (i != databases.end ()) { - d = make_shared (o.db_user (), - o.db_password (), - o.db_name (), - o.db_host (), - o.db_port ()); - db = d; - return d; + if (shared_ptr d = i->second.lock ()) + return d; } + + shared_ptr d ( + make_shared (o.db_user (), + o.db_password (), + o.db_name (), + o.db_host (), + o.db_port ())); + + databases[o] = d; + return d; } } diff --git a/brep/mod-package-details b/brep/mod-package-details index a99d271..0a86fc2 100644 --- a/brep/mod-package-details +++ b/brep/mod-package-details @@ -18,6 +18,14 @@ namespace brep class package_details: public module { public: + package_details () = default; + + // Create a shallow copy (handling instance) if initialized and a deep + // copy (context exemplar) otherwise. + // + explicit + package_details (const package_details&); + virtual bool handle (request&, response&); diff --git a/brep/mod-package-details.cxx b/brep/mod-package-details.cxx index fa07dd1..a5c2e3e 100644 --- a/brep/mod-package-details.cxx +++ b/brep/mod-package-details.cxx @@ -24,6 +24,18 @@ using namespace odb::core; using namespace brep::cli; +// While currently the user-defined copy constructor is not required (we don't +// need to deep copy nullptr's), it is a good idea to keep the placeholder +// ready for less trivial cases. +// +brep::package_details:: +package_details (const package_details& r) + : module (r), + options_ (r.initialized_ ? r.options_ : nullptr), + db_ (r.initialized_ ? r.db_ : nullptr) +{ +} + void brep::package_details:: init (scanner& s) { @@ -61,13 +73,8 @@ handle (request& rq, response& rs) MODULE_DIAG; - // The module options object is not changed after being created once per - // server process. - // - static const size_t res_page (options_->search_results ()); - static const dir_path& root (options_->root ()); - static const fragment& logo (options_->logo ()); - static const vector& menu (options_->menu ()); + const size_t res_page (options_->search_results ()); + const dir_path& root (options_->root ()); const string& name (*rq.path ().rbegin ()); const string ename (mime_url_encode (name)); @@ -130,7 +137,7 @@ handle (request& rq, response& rs) << SCRIPT << " " << ~SCRIPT << ~HEAD << BODY - << DIV_HEADER (root, logo, menu) + << DIV_HEADER (root, options_->logo (), options_->menu ()) << DIV(ID="content"); if (full) diff --git a/brep/mod-package-search b/brep/mod-package-search index 7db976b..1730be4 100644 --- a/brep/mod-package-search +++ b/brep/mod-package-search @@ -18,6 +18,14 @@ namespace brep class package_search: public module { public: + package_search () = default; + + // Create a shallow copy (handling instance) if initialized and a deep + // copy (context exemplar) otherwise. + // + explicit + package_search (const package_search&); + virtual bool handle (request&, response&); diff --git a/brep/mod-package-search.cxx b/brep/mod-package-search.cxx index 7505e02..77a06b6 100644 --- a/brep/mod-package-search.cxx +++ b/brep/mod-package-search.cxx @@ -27,6 +27,18 @@ using namespace odb::core; using namespace brep::cli; +// While currently the user-defined copy constructor is not required (we don't +// need to deep copy nullptr's), it is a good idea to keep the placeholder +// ready for less trivial cases. +// +brep::package_search:: +package_search (const package_search& r) + : module (r), + options_ (r.initialized_ ? r.options_ : nullptr), + db_ (r.initialized_ ? r.db_ : nullptr) +{ +} + void brep::package_search:: init (scanner& s) { @@ -74,13 +86,8 @@ handle (request& rq, response& rs) MODULE_DIAG; - // The module options object is not changed after being created once per - // server process. - // - static const size_t res_page (options_->search_results ()); - static const dir_path& root (options_->root ()); - static const fragment& logo (options_->logo ()); - static const vector& menu (options_->menu ()); + const size_t res_page (options_->search_results ()); + const dir_path& root (options_->root ()); params::package_search params; @@ -124,7 +131,7 @@ handle (request& rq, response& rs) << SCRIPT << " " << ~SCRIPT << ~HEAD << BODY - << DIV_HEADER (root, logo, menu) + << DIV_HEADER (root, options_->logo (), options_->menu ()) << DIV(ID="content"); session sn; diff --git a/brep/mod-package-version-details b/brep/mod-package-version-details index d102fdf..cfbcf94 100644 --- a/brep/mod-package-version-details +++ b/brep/mod-package-version-details @@ -18,6 +18,14 @@ namespace brep class package_version_details: public module { public: + package_version_details () = default; + + // Create a shallow copy (handling instance) if initialized and a deep + // copy (context exemplar) otherwise. + // + explicit + package_version_details (const package_version_details&); + virtual bool handle (request&, response&); diff --git a/brep/mod-package-version-details.cxx b/brep/mod-package-version-details.cxx index c9055a5..cbf9c86 100644 --- a/brep/mod-package-version-details.cxx +++ b/brep/mod-package-version-details.cxx @@ -25,6 +25,18 @@ using namespace std; using namespace odb::core; using namespace brep::cli; +// While currently the user-defined copy constructor is not required (we don't +// need to deep copy nullptr's), it is a good idea to keep the placeholder +// ready for less trivial cases. +// +brep::package_version_details:: +package_version_details (const package_version_details& r) + : module (r), + options_ (r.initialized_ ? r.options_ : nullptr), + db_ (r.initialized_ ? r.db_ : nullptr) +{ +} + void brep::package_version_details:: init (scanner& s) { @@ -48,12 +60,7 @@ handle (request& rq, response& rs) MODULE_DIAG; - // The module options object is not changed after being created once per - // server process. - // - static const dir_path& root (options_->root ()); - static const fragment& logo (options_->logo ()); - static const vector& menu (options_->menu ()); + const dir_path& root (options_->root ()); auto i (rq.path ().rbegin ()); version ver; @@ -107,7 +114,7 @@ handle (request& rq, response& rs) << CSS_LINKS (path ("package-version-details.css"), root) << ~HEAD << BODY - << DIV_HEADER (root, logo, menu) + << DIV_HEADER (root, options_->logo (), options_->menu ()) << DIV(ID="content"); if (full) diff --git a/brep/mod-repository-details b/brep/mod-repository-details index 5f1e93f..411d9e6 100644 --- a/brep/mod-repository-details +++ b/brep/mod-repository-details @@ -18,6 +18,14 @@ namespace brep class repository_details: public module { public: + repository_details () = default; + + // Create a shallow copy (handling instance) if initialized and a deep + // copy (context exemplar) otherwise. + // + explicit + repository_details (const repository_details&); + virtual bool handle (request&, response&); diff --git a/brep/mod-repository-details.cxx b/brep/mod-repository-details.cxx index 0a88a16..f040be6 100644 --- a/brep/mod-repository-details.cxx +++ b/brep/mod-repository-details.cxx @@ -31,6 +31,18 @@ using namespace std; using namespace odb::core; using namespace brep::cli; +// While currently the user-defined copy constructor is not required (we don't +// need to deep copy nullptr's), it is a good idea to keep the placeholder +// ready for less trivial cases. +// +brep::repository_details:: +repository_details (const repository_details& r) + : module (r), + options_ (r.initialized_ ? r.options_ : nullptr), + db_ (r.initialized_ ? r.db_ : nullptr) +{ +} + void brep::repository_details:: init (scanner& s) { @@ -54,12 +66,7 @@ handle (request& rq, response& rs) MODULE_DIAG; - // The module options object is not changed after being created once per - // server process. - // - static const dir_path& root (options_->root ()); - static const fragment& logo (options_->logo ()); - static const vector& menu (options_->menu ()); + const dir_path& root (options_->root ()); // Make sure no parameters passed. // @@ -82,7 +89,7 @@ handle (request& rq, response& rs) << CSS_LINKS (path ("repository-details.css"), root) << ~HEAD << BODY - << DIV_HEADER (root, logo, menu) + << DIV_HEADER (root, options_->logo (), options_->menu ()) << DIV(ID="content"); transaction t (db_->begin ()); diff --git a/brep/mod-repository-root b/brep/mod-repository-root index 254dfa3..f2c2ba0 100644 --- a/brep/mod-repository-root +++ b/brep/mod-repository-root @@ -23,6 +23,12 @@ namespace brep public: repository_root (); + // Create a shallow copy (handling instance) if initialized and a deep + // copy (context exemplar) otherwise. + // + explicit + repository_root (const repository_root&); + private: virtual bool handle (request&, response&); diff --git a/brep/mod-repository-root.cxx b/brep/mod-repository-root.cxx index 61aa71b..7c27a60 100644 --- a/brep/mod-repository-root.cxx +++ b/brep/mod-repository-root.cxx @@ -58,6 +58,37 @@ namespace brep { } + repository_root:: + repository_root (const repository_root& r) + : module (r), + // + // Deep/shallow-copy sub-modules depending on whether this is an + // exemplar/handler. + // + package_search_ ( + r.initialized_ + ? r.package_search_ + : make_shared (*r.package_search_)), + package_details_ ( + r.initialized_ + ? r.package_details_ + : make_shared (*r.package_details_)), + package_version_details_ ( + r.initialized_ + ? r.package_version_details_ + : make_shared ( + *r.package_version_details_)), + repository_details_ ( + r.initialized_ + ? r.repository_details_ + : make_shared (*r.repository_details_)), + options_ ( + r.initialized_ + ? r.options_ + : nullptr) + { + } + // Return amalgamation of repository_root and all its sub-modules option // descriptions. // @@ -112,7 +143,7 @@ namespace brep { MODULE_DIAG; - static const dir_path& root (options_->root ()); + const dir_path& root (options_->root ()); const path& rpath (rq.path ()); if (!rpath.sub (root)) diff --git a/brep/module b/brep/module index 4ea5164..52106cd 100644 --- a/brep/module +++ b/brep/module @@ -88,7 +88,7 @@ namespace brep // Set to true when the module is successfully initialized. // - bool loaded_ {false}; + bool initialized_ {false}; // Implementation details. // diff --git a/brep/module.cxx b/brep/module.cxx index e8a824d..1257d82 100644 --- a/brep/module.cxx +++ b/brep/module.cxx @@ -30,7 +30,7 @@ namespace brep try { - if (!loaded_) + if (!initialized_) { MODULE_DIAG; fail << "not initialized, presumably due to misconfiguration"; @@ -187,7 +187,7 @@ namespace brep void module:: init (const name_values& options, log& log) { - assert (!loaded_); + assert (!initialized_); log_ = &log; @@ -207,7 +207,7 @@ namespace brep options::module o (s, cli::unknown_mode::fail, cli::unknown_mode::fail); verb_ = o.verbosity (); - loaded_ = true; + initialized_ = true; } catch (const server_error& e) { @@ -236,7 +236,11 @@ namespace brep // Custom copy constructor is required to initialize log_writer_ properly. // module:: - module (const module& m): module () {verb_ = m.verb_; loaded_ = m.loaded_;} + module (const module& m): module () + { + verb_ = m.verb_; + initialized_ = m.initialized_; + } // For function func declared like this: // using B = std::string (*)(int); diff --git a/web/apache/service b/web/apache/service index 75c096c..4c0d395 100644 --- a/web/apache/service +++ b/web/apache/service @@ -8,6 +8,8 @@ #include #include // module, ap_hook_*() +#include +#include // unique_ptr #include #include @@ -19,6 +21,22 @@ namespace web { namespace apache { + // Apache has 3 configuration scopes: main server, virtual server, and + // directory (location). It provides configuration scope-aware modules + // with the ability to build a hierarchy of configuration contexts. Later, + // when processing a request, Apache passes the appropriate directory + // configuration context to the request handler. + // + // This Apache service implementation first makes a copy of the provided + // (in the constructor below) module exemplar for each directory context. + // It then initializes each of these "context exemplars" with the (merged) + // set of configuration options. Finally, when handling a request, it + // copies the corresponding "context exemplar" to create the "handling + // instance". Note that the "context exemplars" are create before the + // provided exemplar is initialized. As a result, it is possible to detect + // if the module's copy constructor is used to create a "context exemplar" + // or a "handling instance". + // class service: ::module { public: @@ -41,6 +59,41 @@ namespace web { init_directives (); + // Set configuration context management hooks. + // + // The overall process of building the configuration hierarchy for a + // module is as follows: + // + // 1. Apache creates directory and server configuration contexts for + // scopes containing module-defined directives by calling the + // create_{server,dir}_context() callback functions. For directives + // at the server scope the special directory context is created as + // well. + // + // 2. Apache calls parse_option() function for each module-defined + // directive. The function parses the directives and places the + // resulting options into the corresponding configuration context. + // It also establishes the directory-server contexts relations. + // + // 3. Apache calls merge_server_context() function for each virtual + // server. The function complements virtual server context options + // with the ones from the main server. + // + // 4. Apache calls config_finalizer() which complements the directory + // contexts options with the ones from the enclosing servers. + // + // 5. Apache calls worker_initializer() which creates module exemplar + // for each directory configuration context. + // + // References: + // http://www.apachetutor.org/dev/config + // http://httpd.apache.org/docs/2.4/developer/modguide.html + // http://wiki.apache.org/httpd/ModuleLife + // + create_server_config = &create_server_context; + create_dir_config = &create_dir_context; + merge_server_config = &merge_server_context; + // instance () is invented to delegate processing from apache // request handler C function to the service non static member // function. This appoach resticts number of service objects per @@ -56,6 +109,7 @@ namespace web delete [] cmds; } + private: template static service*& instance () noexcept @@ -88,16 +142,7 @@ namespace web config_finalizer (apr_pool_t*, apr_pool_t*, apr_pool_t*, server_rec* s) noexcept { - auto srv (instance ()); - bool& parsed (srv->options_parsed_); - - if (!parsed) - { - log l (s, srv); - srv->exemplar_.version (l); - parsed = true; - } - + instance ()->finalize_config (s); return OK; } @@ -107,44 +152,140 @@ namespace web { auto srv (instance ()); log l (s, srv); - srv->init_worker (l); + srv->template init_worker (l); } template static int - request_handler (request_rec* r) noexcept + request_handler (request_rec* r) noexcept; + + private: + // Our representation of the Apache configuration context. + // + // The lifetime of this object is under the control of the Apache API, + // which treats it as a raw sequence of bytes. In order not to tinker + // with the C-style structures and APR memory pools, we will keep it a + // (C++11) POD type with just the members required to maintain the + // context hierarchy. + // + // We will then use the pointers to these context objects as keys in + // maps to (1) the corresponding application-level option lists during + // the configuration cycle and to (2) the corresponding module exemplar + // during the HTTP request handling phase. We will also use the same + // type for both directory and server configuration contexts. + // + struct context { - auto srv (instance ()); - if (!r->handler || srv->name_ != r->handler) return DECLINED; + // Outer (server) configuration context for the directory + // configuration context, NULL otherwise. + // + context* server; - request req (r); - log l (r->server, srv); - return srv->template handle (req, l); - } + // If module directives appear directly in the server configuration + // scope, Apache creates a special directory context for them. This + // context appears at the same hierarchy level as the user-defined + // directory contexts of the same server scope. + // + bool special; + + // Create the server configuration context. + // + context (): server (nullptr), special (false) {} + + // Create the directory configuration context. Due to the Apache API + // implementation details it is not possible to detect the enclosing + // server configuration context at the time of directory context + // creation. As a result, the server member is set by the module's + // parse_option() function. + // + context (bool s): server (nullptr), special (s) {} + + // Ensure the object is only destroyed by Apache. + // + ~context () = delete; + }; + + // Type of the key for configuration options and module exemplar maps. + // + using context_id = const context*; + + static bool + is_null (context_id id) noexcept {return id == nullptr;} + + static context_id + make_context_id (const context* c) noexcept {return c;} + + // Convert Apache-provided configuration pointer to the context id. + // + static context_id + make_context_id (void* config) noexcept + {return make_context_id (static_cast (config));} private: void init_directives (); - void - init_worker (log& l) noexcept; + // Create the server configuration context. Called by the Apache API + // whenever a new object of that type is required. + // + static void* + create_server_context (apr_pool_t*, server_rec*) noexcept; + + // Create the server directory configuration context. Called by the + // Apache API whenever a new object of that type is required. + // + static void* + create_dir_context (apr_pool_t*, char* dir) noexcept; + + template + static void* + merge_server_context (apr_pool_t*, void* enclosing, void* enclosed) + noexcept + { + // Complement the enclosed context with options of the enclosing one. + // + instance ()->complement ( + make_context_id (enclosed), make_context_id (enclosing)); + + return enclosed; + } static const char* - parse_option (cmd_parms* parms, void* mconfig, const char* args) noexcept; + parse_option (cmd_parms* parms, void* conf, const char* args) noexcept; const char* - add_option (const char* name, optional value); + add_option (context_id id, const char* name, optional value); + + void + finalize_config (server_rec*); + + void + clear_config (); + + void + complement (context_id enclosed, context_id enclosing); + + template + void + init_worker (log&); template int - handle (request& r, log& l) noexcept; + handle (request&, context_id, log&) const; private: std::string name_; module& exemplar_; option_descriptions option_descriptions_; - name_values options_; + + using options = std::map; + options options_; + + using exemplars = std::map>; + exemplars exemplars_; + bool options_parsed_ = false; + bool version_logged_ = false; }; } } diff --git a/web/apache/service.cxx b/web/apache/service.cxx index 96ff855..2ca8cf0 100644 --- a/web/apache/service.cxx +++ b/web/apache/service.cxx @@ -4,8 +4,7 @@ #include -#include // getppid() -#include // kill() +#include #include #include @@ -51,11 +50,17 @@ namespace web i.first->first.c_str (), reinterpret_cast (parse_option), this, - RSRC_CONF, + + // Allow directives in both server and directory configuration + // scopes. + // + RSRC_CONF | ACCESS_CONF, + // Move away from TAKE1 to be able to handle empty string and // no-value. // RAW_ARGS, + nullptr }; } @@ -64,21 +69,47 @@ namespace web cmds = directives.release (); } - const char* service:: - parse_option (cmd_parms* parms, void*, const char* args) noexcept + void* service:: + create_server_context (apr_pool_t* pool, server_rec*) noexcept { - // @@ Current implementation does not consider configuration context - // (server config, virtual host, directory) for directive parsing, nor - // for request handling. + // Create the object using the configuration memory pool provided by the + // Apache API. The lifetime of the object is equal to the lifetime of the + // pool. + // + void* p (apr_palloc (pool, sizeof (context))); + assert (p != nullptr); + return new (p) context (); + } + + void* service:: + create_dir_context (apr_pool_t* pool, char* dir) noexcept + { + // Create the object using the configuration memory pool provided by the + // Apache API. The lifetime of the object is equal to the lifetime of + // the pool. + // + void* p (apr_palloc (pool, sizeof (context))); + assert (p != nullptr); + + // For the user-defined directory configuration context dir is the path + // of the corresponding directive. For the special server directory + // invented by Apache for server scope directives, dir is NULL. // + return new (p) context (dir == nullptr); + } + + const char* service:: + parse_option (cmd_parms* parms, void* conf, const char* args) noexcept + { service& srv (*reinterpret_cast (parms->cmd->cmd_data)); 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. + // Apache have started the second pass of its messy initialization cycle + // (more details at http://wiki.apache.org/httpd/ModuleLife). This time + // we are parsing for real. Cleanup the existing config, and start + // building the new one. // - return 0; + srv.clear_config (); // 'args' is an optionally double-quoted string. It uses double quotes // to distinguish empty string from no-value case. @@ -91,11 +122,52 @@ namespace web ? string (args + 1, l - 2) : args; - return srv.add_option (parms->cmd->name, move (value)); + // Determine the directory and server configuration contexts for the + // option. + // + context* dir_context (static_cast (conf)); + assert (dir_context != nullptr); + + server_rec* server (parms->server); + assert (server != nullptr); + assert (server->module_config != nullptr); + + context* srv_context ( + static_cast ( + ap_get_module_config (server->module_config, &srv))); + + assert (srv_context != nullptr); + + // Associate the directory configuration context with the enclosing + // server configuration context. + // + context*& s (dir_context->server); + if (s == nullptr) + s = srv_context; + else + assert (s == srv_context); + + // If the option appears in the special directory configuration context, + // add it to the enclosing server context instead. This way it will be + // possible to complement all server-enclosed contexts (including this + // special one) with the server scope options. + // + context* c (dir_context->special ? srv_context : dir_context); + + if (dir_context->special) + // + // Make sure the special directory context is also in the option lists + // map. Later the context will be populated with an enclosing server + // context options. + // + srv.options_.emplace (make_context_id (dir_context), name_values ()); + + return srv.add_option ( + make_context_id (c), parms->cmd->name, move (value)); } const char* service:: - add_option (const char* name, optional value) + add_option (context_id id, const char* name, optional value) { auto i (option_descriptions_.find (name)); assert (i != option_descriptions_.end ()); @@ -105,47 +177,54 @@ namespace web if (i->second != static_cast (value)) return value ? "unexpected value" : "value expected"; - options_.emplace_back (name + name_.length () + 1, move (value)); + options_[id].emplace_back (name + name_.length () + 1, move (value)); return 0; } void service:: - init_worker (log& l) noexcept + complement (context_id enclosed, context_id enclosing) { - const string func_name ( - "web::apache::service<" + name_ + ">::init_worker"); + auto i (options_.find (enclosing)); - try + // The enclosing context may have no options. It can be the context of a + // server having no configuration directives in it's immediate scope, + // but having ones in it's enclosed scope (directory or virtual server). + // + if (i != options_.end ()) { - exemplar_.init (options_, l); + const name_values& src (i->second); + name_values& dest (options_[enclosed]); + dest.insert (dest.begin (), src.begin (), src.end ()); } - catch (const exception& e) + } + + void service:: + finalize_config (server_rec* s) + { + if (!version_logged_) { - l.write (nullptr, 0, func_name.c_str (), APLOG_EMERG, e.what ()); - - // Terminate the root apache process. Indeed we can only try to - // terminate the process, and most likely will fail in a production - // environment where the apache root process usually runs under root, - // and worker processes run under some other user. This is why the - // implementation should consider the possibility of not being - // initialized at the time of HTTP request processing. In such a case - // it should respond with an internal server error (500 HTTP status), - // reporting misconfiguration. - // - ::kill (::getppid (), SIGTERM); + log l (s, this); + exemplar_.version (l); + version_logged_ = true; } - catch (...) - { - l.write (nullptr, - 0, - func_name.c_str (), - APLOG_EMERG, - "unknown error"); - // Terminate the root apache process. - // - ::kill (::getppid (), SIGTERM); - } + // Complement directory configuration contexts with options of the + // enclosing server configuration context. By this time virtual server + // contexts are already complemented with the main server configuration + // context options as a result of the merge_server_context() calls. + // + for (const auto& o: options_) + if (o.first->server != nullptr) // Is a directory configuration context. + complement (o.first, o.first->server); + + options_parsed_ = true; + } + + void service:: + clear_config () + { + options_.clear (); + options_parsed_ = false; } } } diff --git a/web/apache/service.txx b/web/apache/service.txx index 300a2a8..b57befc 100644 --- a/web/apache/service.txx +++ b/web/apache/service.txx @@ -2,8 +2,12 @@ // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file +#include // getppid() +#include // kill() + #include +#include // move() #include namespace web @@ -11,37 +15,147 @@ namespace web namespace apache { template + void service:: + init_worker (log& l) + { + const std::string func_name ( + "web::apache::service<" + name_ + ">::init_worker"); + + try + { + const M* exemplar (dynamic_cast (&exemplar_)); + assert (exemplar != nullptr); + + // For each directory configuration context create the module exemplar + // as a deep copy of the exemplar_ member and initialize it with the + // context-specific option list. Note that there can be contexts + // having no module options specified for them and no options + // inherited from enclosing contexts. Such contexts will not appear in + // the options_ map. Meanwhile 'SetHandler ' directive can be + // in effect for such contexts, and we should be ready to handle + // requests for them (by using the "root exemplar"). + // + for (const auto& o: options_) + { + const context* c (o.first); + + if (c->server != nullptr) // Is a directory configuration context. + { + auto r ( + exemplars_.emplace ( + make_context_id (c), + std::unique_ptr (new M (*exemplar)))); + + r.first->second->init (o.second, l); + } + } + + // Options are not needed anymore. Free up the space. + // + options_.clear (); + + // Initialize the "root exemplar" by default (with no options). It + // will be used to handle requests for configuration contexts having + // no options specified, and no options inherited from enclosing + // contexts. + // + exemplar_.init (name_values (), l); + } + catch (const std::exception& e) + { + l.write (nullptr, 0, func_name.c_str (), APLOG_EMERG, e.what ()); + + // Terminate the root apache process. Indeed we can only try to + // terminate the process, and most likely will fail in a production + // environment, where the apache root process usually runs under root, + // and worker processes run under some other user. This is why the + // implementation should consider the possibility of not being + // initialized at the time of HTTP request processing. In such a case + // it should respond with an internal server error (500 HTTP status), + // reporting misconfiguration. + // + kill (getppid (), SIGTERM); + } + catch (...) + { + l.write (nullptr, + 0, + func_name.c_str (), + APLOG_EMERG, + "unknown error"); + + // Terminate the root apache process. + // + kill (getppid (), SIGTERM); + } + } + + template int service:: - handle (request& r, log& l) noexcept + request_handler (request_rec* r) noexcept + { + auto srv (instance ()); + if (!r->handler || srv->name_ != r->handler) return DECLINED; + + assert (r->per_dir_config != nullptr); + + // Obtain the request-associated configuration context id. + // + context_id id ( + make_context_id (ap_get_module_config (r->per_dir_config, srv))); + + assert (!is_null (id)); + + request rq (r); + log lg (r->server, srv); + return srv->template handle (rq, id, lg); + } + + template + int service:: + handle (request& rq, context_id id, log& lg) const { static const std::string func_name ( "web::apache::service<" + name_ + ">::handle"); try { - M m (static_cast (exemplar_)); + auto i (exemplars_.find (id)); + + // Use the context-specific exemplar if found, otherwise use the + // default one. + // + const module* exemplar (i != exemplars_.end () + ? i->second.get () + : &exemplar_); + + const M* e (dynamic_cast (exemplar)); + assert (e != nullptr); + + M m (*e); - if (static_cast (m).handle (r, r, l)) - return r.flush (); + if (static_cast (m).handle (rq, rq, lg)) + return rq.flush (); - if (!r.get_write_state ()) + if (!rq.get_write_state ()) return DECLINED; - l.write (nullptr, 0, func_name.c_str (), APLOG_ERR, - "handling declined while unbuffered content has been written"); + lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, + "handling declined while unbuffered content " + "has been written"); } catch (const invalid_request& e) { - if (!e.content.empty () && !r.get_write_state ()) + if (!e.content.empty () && !rq.get_write_state ()) { try { - r.content (e.status, e.type) << e.content; - return r.flush (); + rq.content (e.status, e.type) << e.content; + return rq.flush (); } catch (const std::exception& e) { - l.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); + lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); } } @@ -49,39 +163,39 @@ namespace web } catch (const std::exception& e) { - l.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); + lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); - if (*e.what () && !r.get_write_state ()) + if (*e.what () && !rq.get_write_state ()) { try { - r.content (HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8") + rq.content (HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8") << e.what (); - return r.flush (); + return rq.flush (); } catch (const std::exception& e) { - l.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); + lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); } } } catch (...) { - l.write (nullptr, 0, func_name.c_str (), APLOG_ERR, "unknown error"); + lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, "unknown error"); - if (!r.get_write_state ()) + if (!rq.get_write_state ()) { try { - r.content (HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8") + rq.content (HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8") << "unknown error"; - return r.flush (); + return rq.flush (); } catch (const std::exception& e) { - l.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); + lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); } } } diff --git a/web/module b/web/module index 824551f..85896c3 100644 --- a/web/module +++ b/web/module @@ -86,6 +86,9 @@ namespace web public: using path_type = web::path; + virtual + ~request () = default; + // Corresponds to abs_path portion of HTTP URL as described in // "3.2.2 HTTP URL" of http://tools.ietf.org/html/rfc2616. // Returns '/' if no abs_path is present in URL. @@ -120,6 +123,9 @@ namespace web class response { public: + virtual + ~response () = default; + // Set status code, content type, and get the stream to write // the content to. If the buffer argument is true (default), // then buffer the entire content before sending it as a @@ -170,6 +176,9 @@ namespace web class log { public: + virtual + ~log () = default; + virtual void write (const char* msg) = 0; }; @@ -188,6 +197,9 @@ namespace web class module { public: + virtual + ~module () = default; + // Description of configuration options supported by this module. Note: // should be callable during static initialization. // -- cgit v1.1