// file : mod/module.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #include <mod/module.hxx> #include <httpd.h> #include <http_log.h> #include <sstream> #include <cstring> // strchr() #include <functional> // bind() #include <web/server/module.hxx> #include <web/server/apache/log.hxx> #include <mod/module-options.hxx> using namespace std; using namespace placeholders; // For std::bind's _1, etc. namespace brep { // handler // bool handler:: 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 handler:: convert (const cli::options& o) { option_descriptions r; append (r, o); return r; } void handler:: 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 handler:: 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 handler:: 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::handler own option descriptions. // option_descriptions handler:: options () { option_descriptions r ({{"conf", true}}); append (r, options::handler::description ()); append (r, cli_options ()); return r; } // Expand option list parsing configuration files. // name_values handler:: 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::handler::init while others to be interpreted by the // derived init(). If there is an option which can not be interpreted // neither by brep::handler 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 handler:: init (const name_values& options, log& log) { assert (!initialized_); log_ = &log; try { name_values opts (expand_options (options)); // Read handler implementation configuration. // init (opts); // Read brep::handler configuration. // static option_descriptions od ( convert (options::handler::description ())); name_values mo (filter (opts, od)); name_value_scanner s (mo); options::handler 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 handler:: init (const name_values& options) { name_value_scanner s (options); init (s); assert (!s.more ()); // Handler didn't handle its options. } handler:: handler (): log_writer_ (bind (&handler::log_write, this, _1)) {} // Custom copy constructor is required to initialize log_writer_ properly. // handler:: handler (const handler& m): handler () { 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 handler:: 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::handler::func_name"); } void handler:: 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 handlers. 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 handler:: version (log& l) { log_ = &l; version (); } // handler::name_value_scanner // handler::name_value_scanner:: name_value_scanner (const name_values& nv) noexcept : name_values_ (nv), i_ (nv.begin ()), name_ (true) { } bool handler::name_value_scanner:: more () { return i_ != name_values_.end (); } const char* handler::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* handler::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 handler::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 (); } size_t handler::name_value_scanner:: position () { return (i_ - name_values_.begin ()) * 2 + (name_ ? 0 : 1); } }