aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2016-03-06 13:52:48 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2016-03-07 00:05:29 +0300
commitb72424fca7a6af6ff7921101c450850fef875152 (patch)
treeac295d1e228379b1b31c6af3a84e7057f2ea96ae
parent0f9c65e489a7b59f76ccbf2ca6e76ab0a1012932 (diff)
Support multiple instances of brep in a single Apache instance
-rw-r--r--brep/database.cxx58
-rw-r--r--brep/mod-package-details8
-rw-r--r--brep/mod-package-details.cxx23
-rw-r--r--brep/mod-package-search8
-rw-r--r--brep/mod-package-search.cxx23
-rw-r--r--brep/mod-package-version-details8
-rw-r--r--brep/mod-package-version-details.cxx21
-rw-r--r--brep/mod-repository-details8
-rw-r--r--brep/mod-repository-details.cxx21
-rw-r--r--brep/mod-repository-root6
-rw-r--r--brep/mod-repository-root.cxx33
-rw-r--r--brep/module2
-rw-r--r--brep/module.cxx12
-rw-r--r--web/apache/service189
-rw-r--r--web/apache/service.cxx167
-rw-r--r--web/apache/service.txx156
-rw-r--r--web/module12
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 <brep/database>
+#include <map>
+
#include <odb/pgsql/database.hxx>
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<odb::database>
shared_database (const options::db& o)
{
using odb::pgsql::database;
- static weak_ptr<database> 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<database> 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<options::db, weak_ptr<database>> databases;
+
+ auto i (databases.find (o));
+ if (i != databases.end ())
{
- d = make_shared<database> (o.db_user (),
- o.db_password (),
- o.db_name (),
- o.db_host (),
- o.db_port ());
- db = d;
- return d;
+ if (shared_ptr<database> d = i->second.lock ())
+ return d;
}
+
+ shared_ptr<database> d (
+ make_shared<database> (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<page_menu>& 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<page_menu>& 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<page_menu>& 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<page_menu>& 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<package_search> (*r.package_search_)),
+ package_details_ (
+ r.initialized_
+ ? r.package_details_
+ : make_shared<package_details> (*r.package_details_)),
+ package_version_details_ (
+ r.initialized_
+ ? r.package_version_details_
+ : make_shared<package_version_details> (
+ *r.package_version_details_)),
+ repository_details_ (
+ r.initialized_
+ ? r.repository_details_
+ : make_shared<repository_details> (*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 <httpd.h>
#include <http_config.h> // module, ap_hook_*()
+#include <map>
+#include <memory> // unique_ptr
#include <string>
#include <cassert>
@@ -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<M>;
+
// instance<M> () 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 <typename M>
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<M> ());
- bool& parsed (srv->options_parsed_);
-
- if (!parsed)
- {
- log l (s, srv);
- srv->exemplar_.version (l);
- parsed = true;
- }
-
+ instance<M> ()->finalize_config (s);
return OK;
}
@@ -107,44 +152,140 @@ namespace web
{
auto srv (instance<M> ());
log l (s, srv);
- srv->init_worker (l);
+ srv->template init_worker<M> (l);
}
template <typename M>
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<M> ());
- 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<M> (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<const context*> (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 <typename M>
+ static void*
+ merge_server_context (apr_pool_t*, void* enclosing, void* enclosed)
+ noexcept
+ {
+ // Complement the enclosed context with options of the enclosing one.
+ //
+ instance<M> ()->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<std::string> value);
+ add_option (context_id id, const char* name, optional<std::string> value);
+
+ void
+ finalize_config (server_rec*);
+
+ void
+ clear_config ();
+
+ void
+ complement (context_id enclosed, context_id enclosing);
+
+ template <typename M>
+ void
+ init_worker (log&);
template <typename M>
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<context_id, name_values>;
+ options options_;
+
+ using exemplars = std::map<context_id, std::unique_ptr<module>>;
+ 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 <web/apache/service>
-#include <unistd.h> // getppid()
-#include <signal.h> // kill()
+#include <apr_pools.h>
#include <httpd.h>
#include <http_config.h>
@@ -51,11 +50,17 @@ namespace web
i.first->first.c_str (),
reinterpret_cast<cmd_func> (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<service*> (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<context*> (conf));
+ assert (dir_context != nullptr);
+
+ server_rec* server (parms->server);
+ assert (server != nullptr);
+ assert (server->module_config != nullptr);
+
+ context* srv_context (
+ static_cast<context*> (
+ 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<string> value)
+ add_option (context_id id, const char* name, optional<string> value)
{
auto i (option_descriptions_.find (name));
assert (i != option_descriptions_.end ());
@@ -105,47 +177,54 @@ namespace web
if (i->second != static_cast<bool> (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 <unistd.h> // getppid()
+#include <signal.h> // kill()
+
#include <http_log.h>
+#include <utility> // move()
#include <exception>
namespace web
@@ -11,37 +15,147 @@ namespace web
namespace apache
{
template <typename M>
+ void service::
+ init_worker (log& l)
+ {
+ const std::string func_name (
+ "web::apache::service<" + name_ + ">::init_worker");
+
+ try
+ {
+ const M* exemplar (dynamic_cast<const M*> (&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 <modname>' 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<module> (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 <typename M>
int service::
- handle (request& r, log& l) noexcept
+ request_handler (request_rec* r) noexcept
+ {
+ auto srv (instance<M> ());
+ 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<M> (rq, id, lg);
+ }
+
+ template <typename M>
+ 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<const M&> (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<const M*> (exemplar));
+ assert (e != nullptr);
+
+ M m (*e);
- if (static_cast<module&> (m).handle (r, r, l))
- return r.flush ();
+ if (static_cast<module&> (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.
//