aboutsummaryrefslogtreecommitdiff
path: root/mod/module.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'mod/module.cxx')
-rw-r--r--mod/module.cxx410
1 files changed, 410 insertions, 0 deletions
diff --git a/mod/module.cxx b/mod/module.cxx
new file mode 100644
index 0000000..5e3a4b1
--- /dev/null
+++ b/mod/module.cxx
@@ -0,0 +1,410 @@
+// file : mod/module.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <mod/module>
+
+#include <httpd.h>
+#include <http_log.h>
+
+#include <sstream>
+#include <cstring> // strchr()
+#include <functional> // bind()
+
+#include <web/module>
+#include <web/apache/log>
+
+#include <mod/options>
+
+using namespace std;
+using namespace placeholders; // For std::bind's _1, etc.
+
+namespace brep
+{
+ // module
+ //
+ bool module::
+ handle (request& rq, response& rs, log& l)
+ {
+ log_ = &l;
+
+ try
+ {
+ // Web server should terminate if initialization failed.
+ //
+ assert (initialized_);
+
+ return handle (rq, rs);
+ }
+ catch (const server_error& e)
+ {
+ log_write (e.data);
+
+ try
+ {
+ static const char* sev_str[] = {"error", "warning", "info", "trace"};
+ ostream& o (rs.content (500, "text/plain;charset=utf-8"));
+
+ for (const auto& d: e.data)
+ {
+ string name;
+
+ try
+ {
+ name = func_name (d.name);
+ }
+ catch (const invalid_argument&)
+ {
+ // Log "pretty" function description, see in log file & fix.
+ name = d.name;
+ }
+
+ o << name << ": " << sev_str[static_cast<size_t> (d.sev)] << ": "
+ << d.msg << endl;
+ }
+ }
+ catch (const sequence_error&)
+ {
+ // We tried to return the error status/description but some
+ // content has already been written. Nothing we can do about
+ // it.
+ }
+ }
+
+ return true;
+ }
+
+ option_descriptions module::
+ convert (const cli::options& o)
+ {
+ option_descriptions r;
+ append (r, o);
+ return r;
+ }
+
+ void module::
+ append (option_descriptions& dst, const cli::options& src)
+ {
+ for (const auto& o: src)
+ {
+ bool v (!o.flag ());
+ auto i (dst.emplace (o.name (), v));
+ assert (i.first->second == v); // Consistent option/flag.
+
+ for (const auto& a: o.aliases ())
+ {
+ i = dst.emplace (a, v);
+ assert (i.first->second == v);
+ }
+ }
+ }
+
+ void module::
+ append (option_descriptions& dst, const option_descriptions& src)
+ {
+ for (const auto& o: src)
+ {
+ auto i (dst.emplace (o));
+ assert (i.first->second == o.second); // Consistent option/flag.
+ }
+ }
+
+ name_values module::
+ filter (const name_values& v, const option_descriptions& d)
+ {
+ name_values r;
+ for (const auto& nv: v)
+ {
+ if (d.find (nv.name) != d.end ())
+ r.push_back (nv);
+ }
+
+ return r;
+ }
+
+ // Convert CLI option descriptions to the general interface of option
+ // descriptions, extend with brep::module own option descriptions.
+ //
+ option_descriptions module::
+ options ()
+ {
+ option_descriptions r ({{"conf", true}});
+ append (r, options::module::description ());
+ append (r, cli_options ());
+ return r;
+ }
+
+ // Expand option list parsing configuration files.
+ //
+ name_values module::
+ expand_options (const name_values& v)
+ {
+ using namespace cli;
+
+ vector<const char*> argv;
+ for (const auto& nv: v)
+ {
+ argv.push_back (nv.name.c_str ());
+
+ if (nv.value)
+ argv.push_back (nv.value->c_str ());
+ }
+
+ int argc (argv.size ());
+ argv_file_scanner s (0, argc, const_cast<char**> (argv.data ()), "conf");
+
+ name_values r;
+ const option_descriptions& o (options ());
+
+ while (s.more ())
+ {
+ string n (s.next ());
+ auto i (o.find (n));
+
+ if (i == o.end ())
+ throw unknown_argument (n);
+
+ optional<string> v;
+ if (i->second)
+ v = s.next ();
+
+ r.emplace_back (move (n), move (v));
+ }
+
+ return r;
+ }
+
+ // Parse options with a cli-generated scanner. Options verb and conf are
+ // recognized by brep::module::init while others to be interpreted by the
+ // derived init(). If there is an option which can not be interpreted
+ // neither by brep::module nor by the derived class, then the web server
+ // is terminated with a corresponding error message being logged. Though
+ // this should not happen if the options() function returned the correct
+ // set of options.
+ //
+ void module::
+ init (const name_values& options, log& log)
+ {
+ assert (!initialized_);
+
+ log_ = &log;
+
+ try
+ {
+ name_values opts (expand_options (options));
+
+ // Read module implementation configuration.
+ //
+ init (opts);
+
+ // Read brep::module configuration.
+ //
+ static option_descriptions od (
+ convert (options::module::description ()));
+
+ name_values mo (filter (opts, od));
+ name_value_scanner s (mo);
+ options::module o (s, cli::unknown_mode::fail, cli::unknown_mode::fail);
+
+ verb_ = o.verbosity ();
+ initialized_ = true;
+ }
+ catch (const server_error& e)
+ {
+ log_write (e.data);
+ throw runtime_error ("initialization failed");
+ }
+ catch (const cli::exception& e)
+ {
+ ostringstream o;
+ e.print (o);
+ throw runtime_error (o.str ());
+ }
+ }
+
+ void module::
+ init (const name_values& options)
+ {
+ name_value_scanner s (options);
+ init (s);
+ assert (!s.more ()); // Module didn't handle its options.
+ }
+
+ module::
+ module (): log_writer_ (bind (&module::log_write, this, _1)) {}
+
+ // Custom copy constructor is required to initialize log_writer_ properly.
+ //
+ module::
+ module (const module& m): module ()
+ {
+ verb_ = m.verb_;
+ initialized_ = m.initialized_;
+ }
+
+// For function func declared like this:
+// using B = std::string (*)(int);
+// using A = B (*)(int,int);
+// A func(B (*)(char),B (*)(wchar_t));
+// __PRETTY_FUNCTION__ looks like this:
+// virtual std::string (* (* brep::search::func(std::string (* (*)(char))(int)
+// ,std::string (* (*)(wchar_t))(int)) const)(int, int))(int)
+//
+ string module::
+ func_name (const char* pretty_name)
+ {
+ const char* e (strchr (pretty_name, ')'));
+
+ if (e && e > pretty_name)
+ {
+ // Position e at last matching '(' which is the beginning of the
+ // argument list..
+ //
+ size_t d (1);
+
+ do
+ {
+ switch (*--e)
+ {
+ case ')': ++d; break;
+ case '(': --d; break;
+ }
+ }
+ while (d && e > pretty_name);
+
+ if (!d && e > pretty_name)
+ {
+ // Position e at the character following the function name.
+ //
+ while (e > pretty_name &&
+ (*e != '(' || *(e - 1) == ' ' || *(e - 1) == ')'))
+ --e;
+
+ if (e > pretty_name)
+ {
+ // Position b at the beginning of the qualified function name.
+ //
+ const char* b (e);
+ while (--b > pretty_name && *b != ' ');
+ if (*b == ' ') ++b;
+
+ return string (b, e - b);
+ }
+ }
+ }
+
+ throw invalid_argument ("::brep::module::func_name");
+ }
+
+ void module::
+ log_write (const diag_data& d) const
+ {
+ if (log_ == nullptr)
+ return; // No backend yet.
+
+ //@@ Cast log_ to apache::log and write the records.
+ //
+ auto al (dynamic_cast<web::apache::log*> (log_));
+
+ if (al)
+ {
+ // Considered using lambda for mapping but looks too verbose while can
+ // be a bit safer in runtime.
+ //
+ // Use APLOG_INFO (as opposed to APLOG_TRACE1) as a mapping for
+ // severity::trace. "LogLevel trace1" configuration directive switches
+ // on the avalanche of log messages from various modules. Would be good
+ // to avoid wading through them.
+ //
+ static int s[] = {APLOG_ERR, APLOG_WARNING, APLOG_INFO, APLOG_INFO};
+
+ for (const auto& e: d)
+ {
+ string name;
+
+ try
+ {
+ name = func_name (e.name);
+ }
+ catch (const invalid_argument&)
+ {
+ // Log "pretty" function description, see in log file & fix.
+ name = e.name;
+ }
+
+ al->write (e.loc.file.c_str (),
+ e.loc.line,
+ name.c_str (),
+ s[static_cast<size_t> (e.sev)],
+ e.msg.c_str ());
+ }
+ }
+ }
+
+ void module::
+ version (log& l)
+ {
+ log_ = &l;
+ version ();
+ }
+
+ // module::name_value_scanner
+ //
+ module::name_value_scanner::
+ name_value_scanner (const name_values& nv) noexcept
+ : name_values_ (nv),
+ i_ (nv.begin ()),
+ name_ (true)
+ {
+ }
+
+ bool module::name_value_scanner::
+ more ()
+ {
+ return i_ != name_values_.end ();
+ }
+
+ const char* module::name_value_scanner::
+ peek ()
+ {
+ if (i_ != name_values_.end ())
+ return name_ ? i_->name.c_str () : i_->value->c_str ();
+ else
+ throw cli::eos_reached ();
+ }
+
+ const char* module::name_value_scanner::
+ next ()
+ {
+ if (i_ != name_values_.end ())
+ {
+ const char* r (name_ ? i_->name.c_str () : i_->value->c_str ());
+ skip ();
+ return r;
+ }
+ else
+ throw cli::eos_reached ();
+ }
+
+ void module::name_value_scanner::
+ skip ()
+ {
+ if (i_ != name_values_.end ())
+ {
+ if (name_)
+ {
+ if (i_->value)
+ name_ = false;
+ else
+ ++i_;
+ }
+ else
+ {
+ ++i_;
+ name_ = true;
+ }
+ }
+ else
+ throw cli::eos_reached ();
+ }
+}