diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/apache/log | 12 | ||||
-rw-r--r-- | web/apache/request | 119 | ||||
-rw-r--r-- | web/apache/request.cxx | 73 | ||||
-rw-r--r-- | web/apache/request.ixx | 45 | ||||
-rw-r--r-- | web/apache/service | 167 | ||||
-rw-r--r-- | web/apache/stream | 87 | ||||
-rw-r--r-- | web/module | 23 |
7 files changed, 311 insertions, 215 deletions
diff --git a/web/apache/log b/web/apache/log index 151efb4..f8c65e9 100644 --- a/web/apache/log +++ b/web/apache/log @@ -5,12 +5,12 @@ #ifndef WEB_APACHE_LOG #define WEB_APACHE_LOG -#include <algorithm> // min() -#include <cstdint> // uint64_t - #include <httpd/httpd.h> // request_rec #include <httpd/http_log.h> +#include <cstdint> // uint64_t +#include <algorithm> // min() + #include <web/module> namespace web @@ -21,7 +21,7 @@ namespace web { public: - log (request_rec* req) noexcept : req_ (req) {} + log (request_rec* req) noexcept: req_ (req) {} virtual void write (const char* msg) {write (APLOG_ERR, msg);} @@ -29,7 +29,7 @@ namespace web // Apache-specific interface. // void - write (int level, const char* msg) + write (int level, const char* msg) const noexcept { write (nullptr, 0, nullptr, level, msg); } @@ -39,7 +39,7 @@ namespace web std::uint64_t line, const char* func, int level, - const char* msg) + const char* msg) const noexcept { if (file && *file) file = nullptr; // skip file/line placeholder from log line. diff --git a/web/apache/request b/web/apache/request index ab5c765..4071bd1 100644 --- a/web/apache/request +++ b/web/apache/request @@ -5,24 +5,24 @@ #ifndef WEB_APACHE_REQUEST #define WEB_APACHE_REQUEST -#include <stdexcept> -#include <exception> -#include <string> -#include <ios> -#include <istream> -#include <ostream> -#include <streambuf> -#include <memory> // unique_ptr -#include <algorithm> // move -#include <chrono> -#include <cassert> - #include <apr_strings.h> #include <httpd/httpd.h> #include <httpd/http_core.h> #include <httpd/util_script.h> +#include <ios> +#include <chrono> +#include <memory> // unique_ptr +#include <string> +#include <cassert> +#include <istream> +#include <ostream> +#include <streambuf> +#include <stdexcept> +#include <exception> +#include <algorithm> // move + #include <web/module> #include <web/apache/stream> @@ -30,11 +30,13 @@ namespace web { namespace apache { - class request : public web::request, public web::response + class request: public web::request, + public web::response, + public write_state { friend class service; - request (request_rec* rec) noexcept: rec_ (rec) {} + request (request_rec* rec) noexcept: rec_ (rec) {rec_->status = HTTP_OK;} // Flush of buffered content. // @@ -44,16 +46,13 @@ namespace web // Get request body data stream. // virtual std::istream& - data () + content () { - if (write_flag ()) - { - throw sequence_error ("::web::apache::request::data"); - } - if (!in_) { - std::unique_ptr<std::streambuf> in_buf (new istreambuf (rec_)); + std::unique_ptr<std::streambuf> in_buf ( + new istreambuf (rec_, *this)); + in_.reset (new std::istream (in_buf.get ())); in_buf_ = std::move (in_buf); in_->exceptions (std::ios::failbit | std::ios::badbit); @@ -74,16 +73,16 @@ namespace web { if (!parameters_) { - parameters_.reset (new name_values()); + parameters_.reset (new name_values ()); try { - parse_parameters(rec_->args); - parse_parameters(form_data ()->c_str ()); + parse_parameters (rec_->args); + parse_parameters (form_data ()->c_str ()); } - catch(const std::invalid_argument& ) + catch (const std::invalid_argument& ) { - throw invalid_request(); + throw invalid_request (); } } @@ -97,30 +96,29 @@ namespace web // Get response status code. // - status_code status () const noexcept {return status_;} + status_code status () const noexcept {return rec_->status;} // Set response status code. // virtual void status (status_code status) { - if (status != status_) + if (status != rec_->status) { // Setting status code in exception handler is a common usecase // where no sense to throw but still need to signal apache a // proper status code. // - if (write_flag () && !std::current_exception ()) + if (get_write_state () && !std::current_exception ()) { throw sequence_error ("::web::apache::request::status"); } - status_ = status; - type_.clear (); + rec_->status = status; buffer_ = true; out_.reset (); out_buf_.reset (); - set_content_type (); + ap_set_content_type (rec_, nullptr); } } @@ -158,60 +156,33 @@ namespace web static std::string mime_url_decode (const char* b, const char* e, bool trim = false); - // Save content type to apache internals. - // - void - set_content_type () const noexcept + bool + get_write_state () const noexcept {return write_state_;} + + virtual void + set_write_state () { - if (type_.empty ()) - ap_set_content_type (rec_, nullptr); - else + if (!write_state_) { - if(status_ == HTTP_OK) - { - ap_set_content_type (rec_, - apr_pstrdup (rec_->pool, type_.c_str ())); - } - else + // Preparing to write a response read and discard request + // body if any. + // + int r = ap_discard_request_body (rec_); + + if (r != OK) { - // Unfortunatelly there is no way to set a proper content type - // for error custom response. Depending on presense of - // "suppress-error-charset" key in request_rec::subprocess_env - // table content type is set to "text/html" otherwise to - // "text/html; charset=iso-8859-1" (read http_protocol.c for - // details). I have chosen the first one as it is better not to - // specify charset than to set a wrong one. Ensure to put - // a proper encoding to - // <meta http-equiv="Content-Type" content="text/html;charset=..."> - // tag so browser can render the page properly. - // The clean solution would be patching apache but let's leave this - // troublesome option untill really required. - // - apr_table_set (rec_->subprocess_env, "suppress-error-charset", ""); + throw invalid_request (r); } - } - } - bool - write_flag () const noexcept - { - if (!buffer_) - { - assert (out_buf_); - auto b = dynamic_cast<ostreambuf*> (out_buf_.get ()); - assert (b); - return b->write_flag (); + write_state_ = true; } - - return false; } private: request_rec* rec_; - status_code status_ {HTTP_OK}; - std::string type_; bool buffer_ {true}; + bool write_state_ {false}; std::unique_ptr<std::streambuf> out_buf_; std::unique_ptr<std::ostream> out_; std::unique_ptr<std::streambuf> in_buf_; diff --git a/web/apache/request.cxx b/web/apache/request.cxx index 6f043bc..1990fa2 100644 --- a/web/apache/request.cxx +++ b/web/apache/request.cxx @@ -4,19 +4,20 @@ #include <web/apache/request> -#include <stdexcept> +#include <apr_tables.h> + +#include <strings.h> // strcasecmp() + #include <ios> -#include <streambuf> +#include <ctime> +#include <chrono> +#include <memory> // unique_ptr #include <sstream> #include <ostream> -#include <memory> // unique_ptr +#include <cstring> +#include <stdexcept> +#include <streambuf> #include <algorithm> // move() -#include <chrono> -#include <ctime> - -#include <strings.h> // strcasecmp() - -#include <apr_tables.h> using namespace std; @@ -47,17 +48,19 @@ namespace web if (e && e < v) v = 0; - string name ( - v ? mime_url_decode (n, v, true) : - (e ? mime_url_decode (n, e, true) : - mime_url_decode (n, n + strlen (n), true))); + string name (v + ? mime_url_decode (n, v, true) + : (e + ? mime_url_decode (n, e, true) + : mime_url_decode (n, n + strlen (n), true))); string value; if (v++) { - value = e ? mime_url_decode (v, e, true) : - mime_url_decode (v, v + strlen (v), true); + value = e + ? mime_url_decode (v, e, true) + : mime_url_decode (v, v + strlen (v), true); } if (!name.empty () || !value.empty ()) @@ -75,31 +78,18 @@ namespace web ostream& request:: content (status_code status, const std::string& type, bool buffer) { - if (type.empty ()) + if (out_ && status == rec_->status && buffer == buffer_ && + !::strcasecmp (rec_->content_type ? rec_->content_type : "", + type.c_str ())) { - // Getting content stream for writing assumes type to be provided. - // - throw std::invalid_argument ( - "::web::apache::request::content invalid type"); + return *out_; } - // Due to apache implementation of error custom response there is no - // way to make it unbuffered. - // - buffer = buffer || status != HTTP_OK; - - if ((status != status_ || type != type_ || buffer != buffer_) & - write_flag ()) + if (get_write_state ()) { throw sequence_error ("::web::apache::request::content"); } - if (status == status_ && type == type_ && buffer == buffer_) - { - assert (out_); - return *out_; - } - if (!buffer) // Request body will be discarded prior first byte of content is // written. Save form data now to make it available for furture @@ -107,9 +97,10 @@ namespace web // form_data (); - std::unique_ptr<std::streambuf> out_buf( - buffer ? static_cast<std::streambuf*> (new std::stringbuf ()) : - static_cast<std::streambuf*> (new ostreambuf (rec_))); + std::unique_ptr<std::streambuf> out_buf ( + buffer + ? static_cast<std::streambuf*> (new std::stringbuf ()) + : static_cast<std::streambuf*> (new ostreambuf (rec_, *this))); out_.reset (new std::ostream (out_buf.get ())); @@ -118,12 +109,12 @@ namespace web out_->exceptions ( std::ios::eofbit | std::ios::failbit | std::ios::badbit); - status_ = status; - type_ = type; buffer_ = buffer; + rec_->status = status; - if (!buffer_) - set_content_type (); + ap_set_content_type ( + rec_, + type.empty () ? nullptr : apr_pstrdup (rec_->pool, type.c_str ())); return *out_; } @@ -136,7 +127,7 @@ namespace web const char* domain, bool secure) { - if (write_flag ()) + if (get_write_state ()) { throw sequence_error ("::web::apache::request::cookie"); } diff --git a/web/apache/request.ixx b/web/apache/request.ixx index a427fd4..b41de8d 100644 --- a/web/apache/request.ixx +++ b/web/apache/request.ixx @@ -2,13 +2,13 @@ // copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC // license : MIT; see accompanying LICENSE file +#include <strings.h> // strcasecmp() + #include <iomanip> #include <sstream> #include <cstring> #include <cstdlib> -#include <strings.h> // strcasecmp() - namespace web { namespace apache @@ -18,10 +18,8 @@ namespace web { if (buffer_ && out_buf_) { - set_content_type (); - auto b = dynamic_cast<std::stringbuf*> (out_buf_.get ()); - assert(b); + assert (b); std::string s (b->str ()); @@ -33,27 +31,21 @@ namespace web if (r == OK) { - if (status_ == HTTP_OK) - { - if (ap_rwrite (s.c_str (), s.length (), rec_) < 0) - { - status_ = HTTP_REQUEST_TIME_OUT; - } - } - else - { - ap_custom_response (rec_, status_, s.c_str ()); - } + set_write_state (); + + if (ap_rwrite (s.c_str (), s.length (), rec_) < 0) + rec_->status = HTTP_REQUEST_TIME_OUT; } + else - status_ = r; + rec_->status = r; } out_.reset (); out_buf_.reset (); } - return status_ == HTTP_OK ? OK : status_; + return rec_->status == HTTP_OK || get_write_state () ? OK : rec_->status; } inline const request::string_ptr& request:: @@ -66,7 +58,7 @@ namespace web if (ct && !strncasecmp ("application/x-www-form-urlencoded", ct, 33)) { - std::istream& istr (data ()); + std::istream& istr (content ()); std::getline (istr, *form_data_); // Make request data still be available. @@ -89,8 +81,8 @@ namespace web { for (auto n (args); n != 0; ) { - const char* v = strchr (n, '='); - const char* e = strchr (n, '&'); + const char* v = std::strchr (n, '='); + const char* e = ::strchr (n, '&'); if (e && e < v) v = 0; @@ -123,6 +115,7 @@ namespace web char f = o.fill (); std::ios_base::fmtflags g = o.flags (); o << std::hex << std::uppercase << std::right << std::setfill ('0'); + char c; while ((c = *v++) != '\0') @@ -140,7 +133,11 @@ namespace web case '_': case '-': case '~': o << c; break; - default: o << "%" << std::setw (2) << (unsigned short)c; + default: + { + o << "%" << std::setw (2) << static_cast<unsigned short> (c); + break; + } } } @@ -156,7 +153,7 @@ namespace web b += std::strspn (b, " "); if (b >= e) - return std::string(); + return std::string (); while (*--e == ' '); ++e; @@ -199,7 +196,7 @@ namespace web "::web::apache::request::mime_url_decode wrong"); } - value.append (1, (char)vl); + value.append (1, static_cast<char> (vl)); b += 2; break; } diff --git a/web/apache/service b/web/apache/service index 688f8f1..d003767 100644 --- a/web/apache/service +++ b/web/apache/service @@ -5,15 +5,18 @@ #ifndef WEB_APACHE_SERVICE #define WEB_APACHE_SERVICE -#include <exception> -#include <string> -#include <cassert> +#include <string.h> // memset() +#include <unistd.h> // getppid() +#include <signal.h> // kill() #include <httpd/httpd.h> #include <httpd/http_config.h> -#include <web/module> +#include <string> +#include <cassert> +#include <exception> +#include <web/module> #include <web/apache/log> #include <web/apache/request> @@ -21,13 +24,13 @@ namespace web { namespace apache { - class service : ::module + class service: ::module { public: // Note that the module exemplar is stored by-reference. // template <typename M> - service (const std::string& name, const M& exemplar) + service (const std::string& name, M& exemplar) : ::module { STANDARD20_MODULE_STUFF, @@ -35,25 +38,53 @@ namespace web nullptr, nullptr, nullptr, - nullptr, + directives_, ®ister_hooks<M> }, name_ (name), - exemplar_ (exemplar) - + exemplar_ (exemplar), + conf_ (name + "_conf"), + conf_err_ ("A file containing configuration options for module " + + name_), + + // Defines service configuration directives. At the moment the + // only configuration directive is + // "<module_name>_conf <conf_file_path>". Configuration file path + // specified will be passed as a parameter to + // web::module::init call on exemplar_ object when apache worker + // process is started but prior to accepting client requests. + // + directives_ + { + { + conf_.c_str (), + reinterpret_cast<cmd_func> (config_file), + this, + RSRC_CONF, + TAKE1, + conf_err_.c_str () + } + } // Doesn't look like handle_ member is required at all. // handle_ (&handle_impl<M>) { - // instance<M> () invented to delegate processing from apache request - // handler C function to service non static member function. - // This appoach resticts number of service objects per module - // implementation class with just one instance. + // 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 + // specific module implementation class with just one instance. // service*& srv = instance<M> (); assert (srv == nullptr); srv = this; } + static const char* + config_file (cmd_parms *parms, void *mconfig, const char *w) + { + reinterpret_cast<service*> (parms->cmd->cmd_data)->conf_file_ = w; + return 0; + } + template <typename M> static service*& instance () noexcept @@ -66,10 +97,45 @@ namespace web static void register_hooks (apr_pool_t *pool) noexcept { + // The registered function is called right after apache worker + // process is started. Called for every new process spawned. + // + ap_hook_child_init (&child_initializer<M>, NULL, NULL, APR_HOOK_LAST); + + // The registered function is called for each client request. + // ap_hook_handler (&request_handler<M>, NULL, NULL, APR_HOOK_LAST); } template <typename M> + static void + child_initializer (apr_pool_t *pchild, server_rec *s) noexcept + { + auto srv = instance<M> (); + + try + { + srv->exemplar_.init (srv->conf_file_.c_str ()); + } + catch (const std::exception& e) + { + ap_log_error (0, + 0, + APLOG_NO_MODULE, + APLOG_EMERG, + 0, + s, + "[::web::apache::service<%s>::child_initializer]: %s", + srv->name_.c_str (), + e.what ()); + + // Terminate the root apache process. + // + ::kill (::getppid (), SIGTERM); + } + } + + template <typename M> static int request_handler (request_rec* r) noexcept { @@ -78,29 +144,75 @@ namespace web if (!r->handler || srv->name_ != r->handler) return DECLINED; + static const std::string func_name ( + "web::apache::service<" + srv->name_ + ">::request_handler"); + request req (r); - log l(r); + log l (r); - // As soons as M (), handle () and flush () can throw need to handle - // exceptions here. - // try { M m (static_cast<const M&> (srv->exemplar_)); static_cast<module&> (m).handle (req, req, l); - return req.flush(); + return req.flush (); + } + catch (const invalid_request& e) + { + if (!e.content.empty () && !req.get_write_state ()) + { + try + { + req.content (e.status, e.type) << e.content; + return req.flush (); + } + catch (const std::exception& e) + { + l.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); + } + } + + return e.status; } catch (const std::exception& e) { - l.write (nullptr, 0, __PRETTY_FUNCTION__, APLOG_ERR, e.what ()); + l.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); + + if (*e.what () && !req.get_write_state ()) + { + try + { + req.content (HTTP_INTERNAL_SERVER_ERROR, + "text/plain;charset=utf-8") + << e.what (); + + return req.flush (); + } + catch (const std::exception& e) + { + l.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); + } + } } catch (...) { - l.write (nullptr, - 0, - __PRETTY_FUNCTION__, - APLOG_ERR, - "unknown error"); + l.write (nullptr, 0, func_name.c_str (), APLOG_ERR, "unknown error"); + + if (!req.get_write_state ()) + { + try + { + req.content (HTTP_INTERNAL_SERVER_ERROR, + "text/plain;charset=utf-8") + << "unknown error"; + + return req.flush (); + } + catch (const std::exception& e) + { + l.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ()); + } + } + } return HTTP_INTERNAL_SERVER_ERROR; @@ -122,7 +234,12 @@ namespace web } */ std::string name_; - const module& exemplar_; + module& exemplar_; + std::string conf_; + std::string conf_err_; + command_rec directives_[2]; + std::string conf_file_; + // void (*handle_) (request&, response&, log&, const module&); }; } diff --git a/web/apache/stream b/web/apache/stream index eb62b85..5f76347 100644 --- a/web/apache/stream +++ b/web/apache/stream @@ -5,28 +5,44 @@ #ifndef WEB_APACHE_STREAM #define WEB_APACHE_STREAM -#include <streambuf> -#include <ios> // streamsize -#include <algorithm> // min(), max() -#include <cstring> // memmove() -#include <memory> // unique_ptr - #include <httpd/httpd.h> #include <httpd/http_protocol.h> +#include <ios> // streamsize +#include <memory> // unique_ptr +#include <cstring> // memmove() +#include <streambuf> +#include <algorithm> // min(), max() + #include <web/module> namespace web { namespace apache { - class ostreambuf : public std::streambuf + // Object of a class implementing this interface is intended for + // keeping the state of writing response to the client. + // + struct write_state { - public: - ostreambuf (request_rec* rec) : rec_ (rec) {} + // Called by ostreambuf methods when some content to be written to the + // client. + // + virtual void + set_write_state () = 0; + + // Called to check if any data have already been written to the client. + // Such checks required for some operations which are impossible to + // execute after response is partially written. + // + virtual bool + get_write_state () const noexcept = 0; + }; - bool - write_flag () const noexcept {return write_;} + class ostreambuf: public std::streambuf + { + public: + ostreambuf (request_rec* rec, write_state& ws): rec_ (rec), ws_ (ws) {} private: virtual int_type @@ -34,7 +50,7 @@ namespace web { if (c != traits_type::eof ()) { - flag_write (); + ws_.set_write_state (); char chr = c; @@ -51,7 +67,7 @@ namespace web virtual std::streamsize xsputn (const char* s, std::streamsize num) { - flag_write (); + ws_.set_write_state (); if (ap_rwrite (s, num, rec_) < 0) { @@ -64,7 +80,7 @@ namespace web virtual int sync () { - if(ap_rflush (rec_) < 0) + if (ap_rflush (rec_) < 0) { throw invalid_request (HTTP_REQUEST_TIME_OUT); } @@ -72,40 +88,30 @@ namespace web return 0; } - void - flag_write () noexcept - { - if (!write_) - { - // Preparing to write a response read and discard request - // body if any. - // - int r = ap_discard_request_body (rec_); - - if (r != OK) - { - throw invalid_request (r); - } - - write_ = true; - } - } - private: request_rec* rec_; - bool write_ {false}; + write_state& ws_; }; - class istreambuf : public std::streambuf + class istreambuf: public std::streambuf { public: - istreambuf (request_rec* rec, size_t bufsize = 1024, size_t putback = 1) + istreambuf (request_rec* rec, + write_state& ws, + size_t bufsize = 1024, + size_t putback = 1) : rec_ (rec), + ws_ (ws), bufsize_ (std::max (bufsize, (size_t)1)), putback_ (std::min (putback, bufsize_ - 1)), buf_ (new char[bufsize_]) { + if (ws_.get_write_state ()) + { + throw sequence_error ("::web::apache::istreambuf::istreambuf"); + } + char* p = buf_.get () + putback_; setg (p, p, p); @@ -121,6 +127,11 @@ namespace web virtual int_type underflow () { + if (ws_.get_write_state ()) + { + throw sequence_error ("::web::apache::istreambuf::underflow"); + } + if (gptr () < egptr ()) return traits_type::to_int_type (*gptr ()); @@ -144,15 +155,13 @@ namespace web return traits_type::to_int_type (*gptr ()); } - bool error () const noexcept {return error_;} - private: request_rec* rec_; + write_state& ws_; size_t bufsize_; size_t putback_; std::unique_ptr<char[]> buf_; - bool error_ {false}; }; } @@ -26,22 +26,24 @@ namespace web // By default 400 is returned, which means the request is malformed. // // If caught by the web server implementation, it will try to return - // the specified status and description to the client, if possible. + // the specified status and content to the client, if possible. // It is, however, may not be possible if some unbuffered content has // already been written. The behavior in this case is implementation- // specific and may result in no indication of an error being sent to - // the client. If description is not empty, then it is assumed to be - // encoded in UTF-8. + // the client. // struct invalid_request { status_code status; - std::string description; + std::string content; + std::string type; //@@ Maybe optional "try again" link? // - invalid_request (status_code s = 400, std::string d = "") - : status (s), description (std::move (d)) {} + invalid_request (status_code s = 400, + std::string c = "", + std::string t = "text/plain;charset=utf-8") + : status (s), content (std::move (c)), type (std::move (t)) {} }; // Exception indicating HTTP request/response sequencing error. @@ -163,6 +165,15 @@ namespace web class module { public: + // The web server calls this method on the module exemplar prior + // accepting client requests. Configuration file path is passed + // as a parameter. The way configuration file content interpreted is + // module implementation specific. Any exception thrown terminates web + // server. + // + virtual void + init (const char* path) = 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, |