From 0b6b57f9acaa2ec648bf582ff67851331f8e6eef Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Mon, 14 Mar 2016 14:38:45 +0300 Subject: Use serializable transaction isolation level --- web/apache/log | 6 +- web/apache/request | 121 +++++++++++++++++++++++++--------------- web/apache/request.cxx | 147 ++++++++++++++++++++++++++++++++++++++----------- web/apache/request.ixx | 43 ++++++++------- web/apache/service | 13 +++-- web/apache/service.cxx | 42 ++++++++------ web/apache/service.txx | 44 +++++++++------ web/apache/stream | 92 +++++++++++++------------------ web/module | 46 ++++++++++------ 9 files changed, 345 insertions(+), 209 deletions(-) (limited to 'web') diff --git a/web/apache/log b/web/apache/log index 7318e32..291f8de 100644 --- a/web/apache/log +++ b/web/apache/log @@ -5,7 +5,7 @@ #ifndef WEB_APACHE_LOG #define WEB_APACHE_LOG -#include // request_rec +#include // request_rec, server_rec #include #include // module @@ -44,7 +44,7 @@ namespace web const char* msg) const noexcept { if (file && *file) - file = nullptr; // skip file/line placeholder from log line. + file = nullptr; // Skip file/line placeholder from log line. level = std::min (level, APLOG_TRACE8); @@ -59,7 +59,7 @@ namespace web func, msg); else - // skip function name placeholder from log line + // Skip function name placeholder from log line. // ap_log_error (file, line, diff --git a/web/apache/request b/web/apache/request index ab69dff..9540561 100644 --- a/web/apache/request +++ b/web/apache/request @@ -5,23 +5,14 @@ #ifndef WEB_APACHE_REQUEST #define WEB_APACHE_REQUEST -#include +#include // request_rec, HTTP_*, OK, M_POST -#include -#include -#include - -#include #include #include // unique_ptr #include -#include #include #include -#include // move() #include -#include -#include #include #include @@ -30,23 +21,63 @@ namespace web { namespace apache { + // The state of the request processing, reflecting an interaction with + // Apache API (like reading/writing content function calls), with no + // buffering taken into account. Any state different from the initial + // suppose that some irrevocable interaction with Apache API have + // happened, so request processing should be either completed, or + // reported as failed. State values are ordered in a sense that the + // higher value reflects the more advanced stage of processing, so the + // request current state value may not decrease. + // + enum class request_state + { + // Denotes the initial stage of the request handling. At this stage + // the request line and headers are already parsed by Apache. + // + initial, + + // Reading the request content. + // + reading, + + // Adding the response headers (cookies in particular). + // + headers, + + // Writing the response content. + // + writing + }; + class request: public web::request, public web::response, - public write_state + public stream_state { friend class service; - request (request_rec* rec) noexcept: rec_ (rec) {rec_->status = HTTP_OK;} + request (request_rec* rec) noexcept + : rec_ (rec) + { + rec_->status = HTTP_OK; + } + + request_state + state () const noexcept {return state_;} - // Flush of buffered content. + // Flush the buffered response content if present. The returned value + // should be passed to Apache API on request handler exit. // int flush (); - // Return true if content have been sent to the client, false otherwise. + // Prepare for the request re-processing if possible (no unbuffered + // read/write operations have been done). Throw sequence_error + // otherwise. In particular, the preparation can include the response + // content buffer cleanup, the request content buffer rewind. // - bool - get_write_state () const noexcept {return write_state_;} + void + rewind (); // Get request path. // @@ -56,7 +87,7 @@ namespace web // Get request body data stream. // virtual std::istream& - content (); + content (bool buffer = false); // Get request parameters. // @@ -70,7 +101,8 @@ namespace web // Get response status code. // - status_code status () const noexcept {return rec_->status;} + status_code + status () const noexcept {return rec_->status;} // Set response status code. // @@ -89,10 +121,11 @@ namespace web virtual void cookie (const char* name, const char* value, - const std::chrono::seconds* max_age = 0, - const char* path = 0, - const char* domain = 0, - bool secure = false); + const std::chrono::seconds* max_age = nullptr, + const char* path = nullptr, + const char* domain = nullptr, + bool secure = false, + bool buffer = true); private: // Get application/x-www-form-urlencoded form data. @@ -103,37 +136,35 @@ namespace web void parse_parameters (const char* args); + // Advance the request processing state. Noop if new state is equal to + // the current one. Throw sequence_error if the new state is less then + // the current one. Can throw invalid_request if HTTP request is + // malformed. + // + void + state (request_state); + + // stream_state members implementation. + // virtual void - set_write_state () - { - if (!write_state_) - { - // 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_state_ = true; - } - } + set_read_state () {state (request_state::reading);} + + virtual void + set_write_state () {state (request_state::writing);} private: request_rec* rec_; - bool buffer_ {true}; - bool write_state_ {false}; - std::unique_ptr out_buf_; - std::unique_ptr out_; - std::unique_ptr in_buf_; - std::unique_ptr in_; + request_state state_ = request_state::initial; + path_type path_; std::unique_ptr parameters_; std::unique_ptr cookies_; std::unique_ptr form_data_; + std::unique_ptr in_buf_; + std::unique_ptr in_; + + std::unique_ptr out_buf_; + std::unique_ptr out_; }; } } diff --git a/web/apache/request.cxx b/web/apache/request.cxx index 2e03190..b0b4d61 100644 --- a/web/apache/request.cxx +++ b/web/apache/request.cxx @@ -4,22 +4,30 @@ #include -#include +#include // apr_table_*, apr_array_header_t +#include // apr_pstrdup() + +#include // request_rec, HTTP_*, OK +#include // ap_*() #include // strcasecmp() -#include -#include +#include // strftime(), time_t #include #include // unique_ptr +#include +#include #include #include #include -#include +#include // str*(), size_t #include // move() -#include +#include // invalid_argument +#include // current_exception() #include +#include + #include using namespace std; @@ -28,16 +36,92 @@ namespace web { namespace apache { + void request:: + state (request_state s) + { + assert (s != request_state::initial); + + if (s == state_) + return; // Noop. + + if (s < state_) + { + // Can't "unwind" irrevocable interaction with Apache API. + // + static const char* names[] = { + "initial", "reading", "headers", "writing"}; + + string str ("web::apache::request::set_state: "); + str += names[static_cast (state_)]; + str += " to "; + str += names[static_cast (s)]; + + throw sequence_error (move (str)); + } + + if (s == request_state::reading) + { + // Prepare request content for reading. + // + int r (ap_setup_client_block (rec_, REQUEST_CHUNKED_DECHUNK)); + + if (r != OK) + throw invalid_request (r); + } + else if (s > request_state::reading && state_ <= request_state::reading) + { + // Read request content if any, discard whatever is received. + // + int r (ap_discard_request_body (rec_)); + + if (r != OK) + throw invalid_request (r); + } + + state_ = s; + } + + void request:: + rewind () + { + // @@ Request content buffering, and response cookies buffering are not + // supported yet. When done will be possible to rewind in broader + // range of cases. + // + + if (state_ == request_state::initial || + + // Form data have been read. Lucky case, can rewind. + // + (state_ == request_state::reading && + dynamic_cast (in_buf_.get ()) != nullptr)) + { + out_.reset (); + out_buf_.reset (); + + rec_->status = HTTP_OK; + + ap_set_content_type (rec_, nullptr); + + if (in_) + in_->seekg (0); + } + else + throw sequence_error ("web::apache::request::rewind"); + } + istream& request:: - content () + content (bool buffer) { + assert (!buffer); // Request content buffering is not implemented yet. + if (!in_) { unique_ptr in_buf (new istreambuf (rec_, *this)); in_.reset (new istream (in_buf.get ())); in_buf_ = move (in_buf); - in_->exceptions (ios::failbit | ios::badbit); + in_->exceptions (istream::failbit | istream::badbit); // Save form data now otherwise will not be available to do later // when data already read from stream. @@ -135,17 +219,29 @@ namespace web ostream& request:: content (status_code status, const string& type, bool buffer) { - if (out_ && status == rec_->status && buffer == buffer_ && + if (out_ && + + // Same status code. + // + status == rec_->status && + + // Same buffering flag. + // + buffer == + (dynamic_cast (out_buf_.get ()) != nullptr) && + + // Same content type. + // strcasecmp (rec_->content_type ? rec_->content_type : "", type.c_str ()) == 0) { + // No change, return the existing stream. + // return *out_; } - if (get_write_state ()) - { - throw sequence_error ("::web::apache::request::content"); - } + if (state_ >= request_state::writing) + throw sequence_error ("web::apache::request::content"); if (!buffer) // Request body will be discarded prior first byte of content is @@ -161,9 +257,8 @@ namespace web out_.reset (new ostream (out_buf.get ())); out_buf_ = move (out_buf); - out_->exceptions (ios::eofbit | ios::failbit | ios::badbit); + out_->exceptions (ostream::eofbit | ostream::failbit | ostream::badbit); - buffer_ = buffer; rec_->status = status; ap_set_content_type ( @@ -182,13 +277,10 @@ namespace web // where no sense to throw but still need to signal apache a // proper status code. // - if (get_write_state () && !current_exception ()) - { - throw sequence_error ("::web::apache::request::status"); - } + if (state_ >= request_state::writing && !current_exception ()) + throw sequence_error ("web::apache::request::status"); rec_->status = status; - buffer_ = true; out_.reset (); out_buf_.reset (); ap_set_content_type (rec_, nullptr); @@ -201,14 +293,10 @@ namespace web const chrono::seconds* max_age, const char* path, const char* domain, - bool secure) + bool secure, + bool buffer) { - if (get_write_state ()) - { - // Too late to send cookie if content is already written. - // - throw sequence_error ("::web::apache::request::cookie"); - } + assert (!buffer); // Cookie buffering is not implemented yet. ostringstream s; mime_url_encode (name, s); @@ -230,20 +318,15 @@ namespace web } if (path) - { s << ";Path=" << path; - } if (domain) - { s << ";Domain=" << domain; - } if (secure) - { s << ";Secure"; - } + state (request_state::headers); apr_table_add (rec_->err_headers_out, "Set-Cookie", s.str ().c_str ()); } diff --git a/web/apache/request.ixx b/web/apache/request.ixx index 8a3b32b..821aaba 100644 --- a/web/apache/request.ixx +++ b/web/apache/request.ixx @@ -4,10 +4,12 @@ #include // strncasecmp() -#include +#include // apr_table_* + +#include // ap_*() + #include -#include -#include +#include // move() namespace web { @@ -16,35 +18,34 @@ namespace web inline int request:: flush () { - if (buffer_ && out_buf_) + if (std::stringbuf* b = dynamic_cast (out_buf_.get ())) { - auto b (dynamic_cast (out_buf_.get ())); - assert (b); - + // Response content is buffered. + // std::string s (b->str ()); if (!s.empty ()) { - // Before writing response read and discard request body if any. - // - int r (ap_discard_request_body (rec_)); - - if (r == OK) + try { - set_write_state (); + state (request_state::writing); if (ap_rwrite (s.c_str (), s.length (), rec_) < 0) rec_->status = HTTP_REQUEST_TIME_OUT; } - else - rec_->status = r; + catch (const invalid_request& e) + { + rec_->status = e.status; + } } out_.reset (); out_buf_.reset (); } - return rec_->status == HTTP_OK || get_write_state () ? OK : rec_->status; + return rec_->status == HTTP_OK || state_ >= request_state::writing + ? OK + : rec_->status; } inline const std::string& request:: @@ -64,19 +65,21 @@ namespace web std::istream& istr (content ()); // Do not throw when eofbit is set (end of stream reached), and - // when failbit is set (getline() failed to extract any character). + // when failbit is set (getline() failed to extract any + // character). // - istr.exceptions (std::ios::badbit); + istr.exceptions (std::istream::badbit); std::getline (istr, *form_data_); - // Make this data the content of the input stream. + // Make this data the content of the input stream, so it's + // available for the application as well. // std::unique_ptr in_buf ( new std::stringbuf (*form_data_)); in_.reset (new std::istream (in_buf.get ())); in_buf_ = std::move (in_buf); - in_->exceptions (std::ios::failbit | std::ios::badbit); + in_->exceptions (std::istream::failbit | std::istream::badbit); } } } diff --git a/web/apache/service b/web/apache/service index 4c0d395..165ff90 100644 --- a/web/apache/service +++ b/web/apache/service @@ -5,8 +5,11 @@ #ifndef WEB_APACHE_SERVICE #define WEB_APACHE_SERVICE -#include -#include // module, ap_hook_*() +#include // apr_pool_t +#include // APR_HOOK_* + +#include // request_rec, server_rec, HTTP_*, DECLINED +#include // module, cmd_parms, ap_hook_*() #include #include // unique_ptr @@ -130,7 +133,8 @@ namespace web // The worker_initializer() function is called right after Apache // worker process is started. Called for every new process spawned. // - ap_hook_child_init (&worker_initializer, NULL, NULL, APR_HOOK_LAST); + ap_hook_child_init ( + &worker_initializer, NULL, NULL, APR_HOOK_LAST); // The request_handler () function is called for each client request. // @@ -254,7 +258,8 @@ namespace web parse_option (cmd_parms* parms, void* conf, const char* args) noexcept; const char* - add_option (context_id id, const char* name, optional value); + add_option ( + context_id id, const char* name, optional value); void finalize_config (server_rec*); diff --git a/web/apache/service.cxx b/web/apache/service.cxx index 2ca8cf0..da44530 100644 --- a/web/apache/service.cxx +++ b/web/apache/service.cxx @@ -4,10 +4,10 @@ #include -#include +#include // apr_palloc() -#include -#include +#include // server_rec +#include // command_rec, cmd_*, ap_get_module_config() #include // unique_ptr #include @@ -16,7 +16,10 @@ #include // strlen() #include +#include + #include +#include using namespace std; @@ -29,12 +32,12 @@ namespace web { assert (cmds == nullptr); - // Fill apache module directive definitions. Directives share - // common name space in apache configuration file, so to prevent name - // clash have to form directive name as a combination of module and - // option names: -