aboutsummaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2016-03-14 14:38:45 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2016-03-17 12:59:35 +0300
commit0b6b57f9acaa2ec648bf582ff67851331f8e6eef (patch)
tree7ce5da6a1c37f3674762d5514b0a34bf05e38df7 /web
parent637d5650b91cb1da2605e5f7049ccc8bab5591f3 (diff)
Use serializable transaction isolation level
Diffstat (limited to 'web')
-rw-r--r--web/apache/log6
-rw-r--r--web/apache/request121
-rw-r--r--web/apache/request.cxx147
-rw-r--r--web/apache/request.ixx43
-rw-r--r--web/apache/service13
-rw-r--r--web/apache/service.cxx42
-rw-r--r--web/apache/service.txx44
-rw-r--r--web/apache/stream92
-rw-r--r--web/module46
9 files changed, 345 insertions, 209 deletions
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 <httpd.h> // request_rec
+#include <httpd.h> // request_rec, server_rec
#include <http_log.h>
#include <http_config.h> // 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 <apr_strings.h>
+#include <httpd.h> // request_rec, HTTP_*, OK, M_POST
-#include <httpd.h>
-#include <http_core.h>
-#include <util_script.h>
-
-#include <ios>
#include <chrono>
#include <memory> // unique_ptr
#include <string>
-#include <cassert>
#include <istream>
#include <ostream>
-#include <utility> // move()
#include <streambuf>
-#include <stdexcept>
-#include <exception>
#include <web/module>
#include <web/apache/stream>
@@ -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<std::streambuf> out_buf_;
- std::unique_ptr<std::ostream> out_;
- std::unique_ptr<std::streambuf> in_buf_;
- std::unique_ptr<std::istream> in_;
+ request_state state_ = request_state::initial;
+
path_type path_;
std::unique_ptr<name_values> parameters_;
std::unique_ptr<name_values> cookies_;
std::unique_ptr<std::string> form_data_;
+ std::unique_ptr<std::streambuf> in_buf_;
+ std::unique_ptr<std::istream> in_;
+
+ std::unique_ptr<std::streambuf> out_buf_;
+ std::unique_ptr<std::ostream> 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 <web/apache/request>
-#include <apr_tables.h>
+#include <apr_tables.h> // apr_table_*, apr_array_header_t
+#include <apr_strings.h> // apr_pstrdup()
+
+#include <httpd.h> // request_rec, HTTP_*, OK
+#include <http_protocol.h> // ap_*()
#include <strings.h> // strcasecmp()
-#include <ios>
-#include <ctime>
+#include <ctime> // strftime(), time_t
#include <chrono>
#include <memory> // unique_ptr
+#include <string>
+#include <cassert>
#include <sstream>
#include <ostream>
#include <istream>
-#include <cstring>
+#include <cstring> // str*(), size_t
#include <utility> // move()
-#include <stdexcept>
+#include <stdexcept> // invalid_argument
+#include <exception> // current_exception()
#include <streambuf>
+#include <butl/optional>
+
#include <web/mime-url-encoding>
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<size_t> (state_)];
+ str += " to ";
+ str += names[static_cast<size_t> (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<stringbuf*> (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<streambuf> 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<stringbuf*> (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 <strings.h> // strncasecmp()
-#include <iomanip>
+#include <apr_tables.h> // apr_table_*
+
+#include <http_protocol.h> // ap_*()
+
#include <sstream>
-#include <cstring>
-#include <cstdlib>
+#include <utility> // move()
namespace web
{
@@ -16,35 +18,34 @@ namespace web
inline int request::
flush ()
{
- if (buffer_ && out_buf_)
+ if (std::stringbuf* b = dynamic_cast<std::stringbuf*> (out_buf_.get ()))
{
- auto b (dynamic_cast<std::stringbuf*> (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<std::streambuf> 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 <httpd.h>
-#include <http_config.h> // module, ap_hook_*()
+#include <apr_pools.h> // apr_pool_t
+#include <apr_hooks.h> // APR_HOOK_*
+
+#include <httpd.h> // request_rec, server_rec, HTTP_*, DECLINED
+#include <http_config.h> // module, cmd_parms, ap_hook_*()
#include <map>
#include <memory> // 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<M>, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_child_init (
+ &worker_initializer<M>, 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<std::string> value);
+ add_option (
+ context_id id, const char* name, optional<std::string> 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 <web/apache/service>
-#include <apr_pools.h>
+#include <apr_pools.h> // apr_palloc()
-#include <httpd.h>
-#include <http_config.h>
+#include <httpd.h> // server_rec
+#include <http_config.h> // command_rec, cmd_*, ap_get_module_config()
#include <memory> // unique_ptr
#include <string>
@@ -16,7 +16,10 @@
#include <cstring> // strlen()
#include <exception>
+#include <butl/optional>
+
#include <web/module>
+#include <web/apache/log>
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: <module name>-<option name>. This why for option
- // bar of module foo the corresponding directive will appear in apache
- // configuration file as foo-bar.
+ // 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: <module name>-<option name>. This why for option bar of module
+ // foo the corresponding directive will appear in apache configuration
+ // file as foo-bar.
//
const option_descriptions& od (exemplar_.options ());
unique_ptr<command_rec[]> directives (new command_rec[od.size () + 1]);
@@ -42,7 +45,8 @@ namespace web
for (const auto& o: od)
{
- auto i (option_descriptions_.emplace (name_ + "-" + o.first, o.second));
+ auto i (
+ option_descriptions_.emplace (name_ + "-" + o.first, o.second));
assert (i.second);
*d++ =
@@ -73,8 +77,8 @@ namespace web
create_server_context (apr_pool_t* pool, server_rec*) 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.
+ // 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);
@@ -104,10 +108,10 @@ namespace web
service& srv (*reinterpret_cast<service*> (parms->cmd->cmd_data));
if (srv.options_parsed_)
- // 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.
+ // 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.
//
srv.clear_config ();
@@ -214,8 +218,12 @@ namespace web
// 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.
+ {
+ // Is a directory configuration context.
+ //
+ if (o.first->server != nullptr)
complement (o.first, o.first->server);
+ }
options_parsed_ = true;
}
diff --git a/web/apache/service.txx b/web/apache/service.txx
index b57befc..961c19b 100644
--- a/web/apache/service.txx
+++ b/web/apache/service.txx
@@ -3,9 +3,9 @@
// license : MIT; see accompanying LICENSE file
#include <unistd.h> // getppid()
-#include <signal.h> // kill()
+#include <signal.h> // kill(), SIGTERM
-#include <http_log.h>
+#include <http_log.h> // APLOG_*
#include <utility> // move()
#include <exception>
@@ -132,21 +132,31 @@ namespace web
const M* e (dynamic_cast<const M*> (exemplar));
assert (e != nullptr);
- M m (*e);
-
- if (static_cast<module&> (m).handle (rq, rq, lg))
- return rq.flush ();
+ for (M m (*e);;)
+ {
+ try
+ {
+ if (static_cast<module&> (m).handle (rq, rq, lg))
+ return rq.flush ();
- if (!rq.get_write_state ())
- return DECLINED;
+ if (rq.state () == request_state::initial)
+ return DECLINED;
- lg.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 being partially executed");
+ break;
+ }
+ catch (const module::retry&)
+ {
+ // Retry to handle the request.
+ //
+ rq.rewind ();
+ }
+ }
}
catch (const invalid_request& e)
{
- if (!e.content.empty () && !rq.get_write_state ())
+ if (!e.content.empty () && rq.state () < request_state::writing)
{
try
{
@@ -165,11 +175,12 @@ namespace web
{
lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ());
- if (*e.what () && !rq.get_write_state ())
+ if (*e.what () && rq.state () < request_state::writing)
{
try
{
- rq.content (HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8")
+ rq.content (
+ HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8")
<< e.what ();
return rq.flush ();
@@ -184,11 +195,12 @@ namespace web
{
lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, "unknown error");
- if (!rq.get_write_state ())
+ if (rq.state () < request_state::writing)
{
try
{
- rq.content (HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8")
+ rq.content (
+ HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8")
<< "unknown error";
return rq.flush ();
diff --git a/web/apache/stream b/web/apache/stream
index 2ea9b7e..f05ea08 100644
--- a/web/apache/stream
+++ b/web/apache/stream
@@ -5,44 +5,56 @@
#ifndef WEB_APACHE_STREAM
#define WEB_APACHE_STREAM
-#include <httpd.h>
-#include <http_protocol.h>
+#include <httpd.h> // request_rec, HTTP_*
+#include <http_protocol.h> // ap_*()
#include <ios> // streamsize
#include <vector>
-#include <cstring> // memmove()
+#include <cstring> // memmove(), size_t
#include <streambuf>
#include <algorithm> // min(), max()
-#include <web/module>
+#include <web/module> // invalid_request
namespace web
{
namespace apache
{
- // Object of a class implementing this interface is intended for
- // keeping the state of writing response to the client.
+ // Object of a class implementing this interface is intended for keeping
+ // the state of communication with the client.
//
- struct write_state
+ struct stream_state
{
- // Called by ostreambuf methods when some content to be written to the
- // client.
+ // Called by istreambuf functions when content is about to be read from
+ // the client. Can throw invalid_request or sequence_error.
//
virtual void
- set_write_state () = 0;
+ set_read_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.
+ // Called by ostreambuf functions when some content is about to be
+ // written to the client. Can throw invalid_request or sequence_error.
//
- virtual bool
- get_write_state () const noexcept = 0;
+ virtual void
+ set_write_state () = 0;
+ };
+
+ // Base class for ostreambuf and istreambuf. References request and
+ // communication state structures.
+ //
+ class rbuf: public std::streambuf
+ {
+ protected:
+ rbuf (request_rec* r, stream_state& s): rec_ (r), state_ (s) {}
+
+ protected:
+ request_rec* rec_;
+ stream_state& state_;
};
- class ostreambuf: public std::streambuf
+ class ostreambuf: public rbuf
{
public:
- ostreambuf (request_rec* rec, write_state& ws): rec_ (rec), ws_ (ws) {}
+ ostreambuf (request_rec* r, stream_state& s): rbuf (r, s) {}
private:
virtual int_type
@@ -50,7 +62,7 @@ namespace web
{
if (c != traits_type::eof ())
{
- ws_.set_write_state ();
+ state_.set_write_state ();
char chr (c);
@@ -67,12 +79,10 @@ namespace web
virtual std::streamsize
xsputn (const char* s, std::streamsize num)
{
- ws_.set_write_state ();
+ state_.set_write_state ();
if (ap_rwrite (s, num, rec_) < 0)
- {
throw invalid_request (HTTP_REQUEST_TIME_OUT);
- }
return num;
}
@@ -81,59 +91,37 @@ namespace web
sync ()
{
if (ap_rflush (rec_) < 0)
- {
throw invalid_request (HTTP_REQUEST_TIME_OUT);
- }
return 0;
}
-
- private:
- request_rec* rec_;
- write_state& ws_;
};
- class istreambuf: public std::streambuf
+ class istreambuf: public rbuf
{
public:
- istreambuf (request_rec* rec,
- write_state& ws,
+ istreambuf (request_rec* r,
+ stream_state& s,
size_t bufsize = 1024,
size_t putback = 1)
- : rec_ (rec),
- ws_ (ws),
+ : rbuf (r, s),
bufsize_ (std::max (bufsize, (size_t)1)),
putback_ (std::min (putback, bufsize_ - 1)),
buf_ (bufsize_)
{
- if (ws_.get_write_state ())
- {
- throw sequence_error ("::web::apache::istreambuf::istreambuf");
- }
-
char* p (buf_.data () + putback_);
setg (p, p, p);
-
- int status (ap_setup_client_block (rec_, REQUEST_CHUNKED_DECHUNK));
-
- if (status != OK)
- {
- throw invalid_request (status);
- }
}
private:
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 ());
+ state_.set_read_state ();
+
size_t pb (std::min ((size_t)(gptr () - eback ()), putback_));
std::memmove (buf_.data () + putback_ - pb, gptr () - pb, pb);
@@ -141,22 +129,16 @@ namespace web
int rb (ap_get_client_block (rec_, p, bufsize_ - putback_));
if (rb == 0)
- {
return traits_type::eof ();
- }
if (rb < 0)
- {
throw invalid_request (HTTP_REQUEST_TIME_OUT);
- }
setg (p - pb, p, p + rb);
return traits_type::to_int_type (*gptr ());
}
private:
- request_rec* rec_;
- write_state& ws_;
size_t bufsize_;
size_t putback_;
std::vector<char> buf_;
diff --git a/web/module b/web/module
index 85896c3..fcd7012 100644
--- a/web/module
+++ b/web/module
@@ -111,13 +111,13 @@ namespace web
virtual const name_values&
cookies () = 0;
- // Get the stream to read the request content from. Note that
- // reading content after any unbuffered content has been written
- // is undefined behavior. The implementation may detect it and
- // throw sequence_error but is not required to do so.
+ // Get the stream to read the request content from. If the buffer argument
+ // is false, then reading content after any unbuffered content has been
+ // written or after a retry is undefined behavior. The implementation may
+ // detect this and throw sequence_error but is not required to do so.
//
virtual std::istream&
- content () = 0;
+ content (bool buffer = false) = 0;
};
class response
@@ -160,10 +160,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) = 0;
+ const std::chrono::seconds* max_age = nullptr,
+ const char* path = nullptr,
+ const char* domain = nullptr,
+ bool secure = false,
+ bool buffer = true) = 0;
};
// A web server logging backend. The module can use it to log
@@ -229,15 +230,26 @@ namespace web
// Return false if decline to handle the request. If handling have been
// declined after any unbuffered content has been written, then the
// implementation shall terminate the response in a suitable but
- // unspecified manner. 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, it will try to return the status and description
- // (obtained by calling what() on std::exception) to the client, if
- // possible. The description is assume to be encoded in UTF-8. The
- // implementation may provide a configuration option to omit the
- // description from the response, for security/privacy reasons.
+ // unspecified manner.
//
+ // Throw retry if need to retry handling the request. The retry will
+ // happen on the same instance of the module and the implementation is
+ // expected to "rewind" the request and response objects to their initial
+ // state. This is only guaranteed to be possible if the relevant functions
+ // in the request and response objects were called in buffered mode (the
+ // buffer argument was true).
+ //
+ // Any exception other than retry and 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, it will try to
+ // return the status and description (obtained by calling what() on
+ // std::exception) to the client, if possible. The description is assume
+ // to be encoded in UTF-8. The implementation may provide a configuration
+ // option to omit the description from the response, for security/privacy
+ // reasons.
+ //
+ struct retry {};
+
virtual bool
handle (request&, response&, log&) = 0;
};