aboutsummaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-04-23 12:43:52 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-04-23 12:43:52 +0200
commita20443c285dabdec8d2ee740500c62e31ad90c7b (patch)
treeb18db4007e45f8db1f97c0d5abf78729138406ac /web
parent370e361db628f60bca5509dcc354014569d56752 (diff)
Implement apache service
Diffstat (limited to 'web')
-rw-r--r--web/apache/log50
-rw-r--r--web/apache/request228
-rw-r--r--web/apache/request.cxx186
-rw-r--r--web/apache/request.ixx218
-rw-r--r--web/apache/service94
-rw-r--r--web/apache/stream161
-rw-r--r--web/module66
7 files changed, 985 insertions, 18 deletions
diff --git a/web/apache/log b/web/apache/log
index 0e39420..151efb4 100644
--- a/web/apache/log
+++ b/web/apache/log
@@ -5,7 +5,11 @@
#ifndef WEB_APACHE_LOG
#define WEB_APACHE_LOG
-#include <cstdint> // uint64_t
+#include <algorithm> // min()
+#include <cstdint> // uint64_t
+
+#include <httpd/httpd.h> // request_rec
+#include <httpd/http_log.h>
#include <web/module>
@@ -16,7 +20,8 @@ namespace web
class log: public web::log
{
public:
- // ...
+
+ log (request_rec* req) noexcept : req_ (req) {}
virtual void
write (const char* msg) {write (APLOG_ERR, msg);}
@@ -24,13 +29,48 @@ namespace web
// Apache-specific interface.
//
void
- write (int level, const char* msg) {write (nullptr, 0, level, msg);}
+ write (int level, const char* msg)
+ {
+ write (nullptr, 0, nullptr, level, msg);
+ }
void
- write (const char* file, std::uint64_t line, int level, const char* msg);
+ write (const char* file,
+ std::uint64_t line,
+ const char* func,
+ int level,
+ const char* msg)
+ {
+ if (file && *file)
+ file = nullptr; // skip file/line placeholder from log line.
+
+ level = std::min (level, APLOG_TRACE8);
+
+ if (func)
+ ap_log_rerror (file,
+ line,
+ APLOG_NO_MODULE,
+ level,
+ 0,
+ req_,
+ "[%s]: %s",
+ func,
+ msg);
+ else
+ // skip function name placeholder from log line
+ //
+ ap_log_rerror (file,
+ line,
+ APLOG_NO_MODULE,
+ level,
+ 0,
+ req_,
+ ": %s",
+ msg);
+ }
private:
- // ...
+ request_rec* req_;
};
}
}
diff --git a/web/apache/request b/web/apache/request
new file mode 100644
index 0000000..ab5c765
--- /dev/null
+++ b/web/apache/request
@@ -0,0 +1,228 @@
+// file : web/apache/request -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#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 <web/module>
+#include <web/apache/stream>
+
+namespace web
+{
+ namespace apache
+ {
+ class request : public web::request, public web::response
+ {
+ friend class service;
+
+ request (request_rec* rec) noexcept: rec_ (rec) {}
+
+ // Flush of buffered content.
+ //
+ int
+ flush ();
+
+ // Get request body data stream.
+ //
+ virtual std::istream&
+ data ()
+ {
+ if (write_flag ())
+ {
+ throw sequence_error ("::web::apache::request::data");
+ }
+
+ if (!in_)
+ {
+ std::unique_ptr<std::streambuf> in_buf (new istreambuf (rec_));
+ in_.reset (new std::istream (in_buf.get ()));
+ in_buf_ = std::move (in_buf);
+ in_->exceptions (std::ios::failbit | std::ios::badbit);
+
+ // Save form data now otherwise will not be available to do later
+ // when data read from stream.
+ //
+ form_data ();
+ }
+
+ return *in_;
+ }
+
+ // Get request parameters.
+ //
+ virtual const name_values&
+ parameters ()
+ {
+ if (!parameters_)
+ {
+ parameters_.reset (new name_values());
+
+ try
+ {
+ parse_parameters(rec_->args);
+ parse_parameters(form_data ()->c_str ());
+ }
+ catch(const std::invalid_argument& )
+ {
+ throw invalid_request();
+ }
+ }
+
+ return *parameters_;
+ }
+
+ // Get request cookies.
+ //
+ virtual const name_values&
+ cookies ();
+
+ // Get response status code.
+ //
+ status_code status () const noexcept {return status_;}
+
+ // Set response status code.
+ //
+ virtual void
+ status (status_code status)
+ {
+ if (status != 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 ())
+ {
+ throw sequence_error ("::web::apache::request::status");
+ }
+
+ status_ = status;
+ type_.clear ();
+ buffer_ = true;
+ out_.reset ();
+ out_buf_.reset ();
+ set_content_type ();
+ }
+ }
+
+ // Set response status code, content type and get body stream.
+ //
+ virtual std::ostream&
+ content (status_code status,
+ const std::string& type,
+ bool buffer = true);
+
+ // Add response cookie.
+ //
+ 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);
+
+ private:
+ using string_ptr = std::unique_ptr<std::string>;
+
+ // Get application/x-www-form-urlencoded form data.
+ //
+ const string_ptr&
+ form_data ();
+
+ void
+ parse_parameters (const char* args);
+
+ static void
+ mime_url_encode (const char* v, std::ostream& o);
+
+ 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
+ {
+ if (type_.empty ())
+ ap_set_content_type (rec_, nullptr);
+ else
+ {
+ if(status_ == HTTP_OK)
+ {
+ ap_set_content_type (rec_,
+ apr_pstrdup (rec_->pool, type_.c_str ()));
+ }
+ else
+ {
+ // 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", "");
+ }
+ }
+ }
+
+ bool
+ write_flag () const noexcept
+ {
+ if (!buffer_)
+ {
+ assert (out_buf_);
+ auto b = dynamic_cast<ostreambuf*> (out_buf_.get ());
+ assert (b);
+ return b->write_flag ();
+ }
+
+ return false;
+ }
+
+ private:
+
+ request_rec* rec_;
+ status_code status_ {HTTP_OK};
+ std::string type_;
+ bool buffer_ {true};
+ 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_;
+ std::unique_ptr<name_values> parameters_;
+ std::unique_ptr<name_values> cookies_;
+ string_ptr form_data_;
+ };
+ }
+}
+
+#include <web/apache/request.ixx>
+
+#endif // WEB_APACHE_REQUEST
diff --git a/web/apache/request.cxx b/web/apache/request.cxx
new file mode 100644
index 0000000..6f043bc
--- /dev/null
+++ b/web/apache/request.cxx
@@ -0,0 +1,186 @@
+// file : web/apache/request.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#include <web/apache/request>
+
+#include <stdexcept>
+#include <ios>
+#include <streambuf>
+#include <sstream>
+#include <ostream>
+#include <memory> // unique_ptr
+#include <algorithm> // move()
+#include <chrono>
+#include <ctime>
+
+#include <strings.h> // strcasecmp()
+
+#include <apr_tables.h>
+
+using namespace std;
+
+namespace web
+{
+ namespace apache
+ {
+ const name_values& request::
+ cookies ()
+ {
+ if (!cookies_)
+ {
+ cookies_.reset (new name_values ());
+
+ const apr_array_header_t* ha = apr_table_elts (rec_->headers_in);
+ size_t n = ha->nelts;
+
+ for (auto h (reinterpret_cast<const apr_table_entry_t *> (ha->elts));
+ n--; ++h)
+ {
+ if (!::strcasecmp (h->key, "Cookie"))
+ {
+ for (const char* n (h->val); n != 0; )
+ {
+ const char* v = strchr (n, '=');
+ const char* e = strchr (n, ';');
+
+ 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 value;
+
+ if (v++)
+ {
+ value = e ? mime_url_decode (v, e, true) :
+ mime_url_decode (v, v + strlen (v), true);
+ }
+
+ if (!name.empty () || !value.empty ())
+ cookies_->emplace_back (move (name), move (value));
+
+ n = e ? e + 1 : 0;
+ }
+ }
+ }
+ }
+
+ return *cookies_;
+ }
+
+ ostream& request::
+ content (status_code status, const std::string& type, bool buffer)
+ {
+ if (type.empty ())
+ {
+ // Getting content stream for writing assumes type to be provided.
+ //
+ throw std::invalid_argument (
+ "::web::apache::request::content invalid type");
+ }
+
+ // 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 ())
+ {
+ 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
+ // parameters () call.
+ //
+ form_data ();
+
+ std::unique_ptr<std::streambuf> out_buf(
+ buffer ? static_cast<std::streambuf*> (new std::stringbuf ()) :
+ static_cast<std::streambuf*> (new ostreambuf (rec_)));
+
+ out_.reset (new std::ostream (out_buf.get ()));
+
+ out_buf_ = std::move (out_buf);
+
+ out_->exceptions (
+ std::ios::eofbit | std::ios::failbit | std::ios::badbit);
+
+ status_ = status;
+ type_ = type;
+ buffer_ = buffer;
+
+ if (!buffer_)
+ set_content_type ();
+
+ return *out_;
+ }
+
+ void request::
+ cookie (const char* name,
+ const char* value,
+ const std::chrono::seconds* max_age,
+ const char* path,
+ const char* domain,
+ bool secure)
+ {
+ if (write_flag ())
+ {
+ throw sequence_error ("::web::apache::request::cookie");
+ }
+
+ std::ostringstream s;
+ mime_url_encode (name, s);
+ s << "=";
+ mime_url_encode (value, s);
+
+ if (max_age)
+ {
+ std::chrono::system_clock::time_point tp =
+ std::chrono::system_clock::now () + *max_age;
+
+ std::time_t t = std::chrono::system_clock::to_time_t (tp);
+
+ // Assume global "C" locale is not changed.
+ //
+ char b[100];
+ std::strftime (b,
+ sizeof (b),
+ "%a, %d-%b-%Y %H:%M:%S GMT",
+ std::gmtime (&t));
+
+ s << "; Expires=" << b;
+ }
+
+ if (path)
+ {
+ s << ";Path=" << path;
+ }
+
+ if (domain)
+ {
+ s << ";Domain=" << domain;
+ }
+
+ if (secure)
+ {
+ s << ";Secure";
+ }
+
+ 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
new file mode 100644
index 0000000..a427fd4
--- /dev/null
+++ b/web/apache/request.ixx
@@ -0,0 +1,218 @@
+// file : web/apache/request.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#include <iomanip>
+#include <sstream>
+#include <cstring>
+#include <cstdlib>
+
+#include <strings.h> // strcasecmp()
+
+namespace web
+{
+ namespace apache
+ {
+ inline int request::
+ flush ()
+ {
+ if (buffer_ && out_buf_)
+ {
+ set_content_type ();
+
+ auto b = dynamic_cast<std::stringbuf*> (out_buf_.get ());
+ assert(b);
+
+ 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)
+ {
+ 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 ());
+ }
+ }
+ else
+ status_ = r;
+ }
+
+ out_.reset ();
+ out_buf_.reset ();
+ }
+
+ return status_ == HTTP_OK ? OK : status_;
+ }
+
+ inline const request::string_ptr& request::
+ form_data ()
+ {
+ if (!form_data_)
+ {
+ form_data_.reset (new std::string ());
+ const char *ct = apr_table_get (rec_->headers_in, "Content-Type");
+
+ if (ct && !strncasecmp ("application/x-www-form-urlencoded", ct, 33))
+ {
+ std::istream& istr (data ());
+ std::getline (istr, *form_data_);
+
+ // Make request data still be available.
+ //
+
+ 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);
+ }
+ }
+
+ return form_data_;
+ }
+
+ inline void request::
+ parse_parameters (const char* args)
+ {
+ for (auto n (args); n != 0; )
+ {
+ const char* v = strchr (n, '=');
+ const char* e = strchr (n, '&');
+
+ if (e && e < v)
+ v = 0;
+
+ std::string name (v
+ ? mime_url_decode (n, v) :
+ (e
+ ? mime_url_decode (n, e)
+ : mime_url_decode (n, n + std::strlen (n))));
+
+ std::string value;
+
+ if (v++)
+ {
+ value = e
+ ? mime_url_decode (v, e)
+ : mime_url_decode (v, v + std::strlen (v));
+ }
+
+ if (!name.empty () || !value.empty ())
+ parameters_->emplace_back (std::move (name), std::move (value));
+
+ n = e ? e + 1 : 0;
+ }
+ }
+
+ inline void request::
+ mime_url_encode (const char* v, std::ostream& o)
+ {
+ 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')
+ {
+ if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9'))
+ {
+ o << c;
+ }
+ else
+ switch (c)
+ {
+ case ' ': o << '+'; break;
+ case '.':
+ case '_':
+ case '-':
+ case '~': o << c; break;
+ default: o << "%" << std::setw (2) << (unsigned short)c;
+ }
+ }
+
+ o.flags (g);
+ o.fill (f);
+ }
+
+ inline std::string request::
+ mime_url_decode (const char* b, const char* e, bool trim)
+ {
+ if (trim)
+ {
+ b += std::strspn (b, " ");
+
+ if (b >= e)
+ return std::string();
+
+ while (*--e == ' ');
+ ++e;
+ }
+
+ std::string value;
+ value.reserve (e - b);
+
+ char bf[3];
+ bf[2] = '\0';
+
+ while (b != e)
+ {
+ char c = *b++;
+
+ switch (c)
+ {
+ case '+':
+ {
+ value.append (" ");
+ break;
+ }
+ case '%':
+ {
+ if (*b == '\0' || b[1] == '\0')
+ {
+ throw std::invalid_argument (
+ "::web::apache::request::mime_url_decode short");
+ }
+
+ *bf = *b;
+ bf[1] = b[1];
+
+ char* ebf = 0;
+ size_t vl = std::strtoul (bf, &ebf, 16);
+
+ if (*ebf != '\0')
+ {
+ throw std::invalid_argument (
+ "::web::apache::request::mime_url_decode wrong");
+ }
+
+ value.append (1, (char)vl);
+ b += 2;
+ break;
+ }
+ default:
+ {
+ value.append (1, c);
+ break;
+ }
+ }
+ }
+
+ return value;
+ }
+
+ }
+}
diff --git a/web/apache/service b/web/apache/service
index 8dd21d8..688f8f1 100644
--- a/web/apache/service
+++ b/web/apache/service
@@ -5,22 +5,106 @@
#ifndef WEB_APACHE_SERVICE
#define WEB_APACHE_SERVICE
+#include <exception>
#include <string>
+#include <cassert>
+
+#include <httpd/httpd.h>
+#include <httpd/http_config.h>
#include <web/module>
+#include <web/apache/log>
+#include <web/apache/request>
+
namespace web
{
namespace apache
{
- class service
+ class service : ::module
{
public:
// Note that the module exemplar is stored by-reference.
//
template <typename M>
service (const std::string& name, const M& exemplar)
- : exemplar_ (exemplar), handle_ (&handle_impl<M>) {}
+ : ::module
+ {
+ STANDARD20_MODULE_STUFF,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ &register_hooks<M>
+ },
+ name_ (name),
+ exemplar_ (exemplar)
+
+// 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.
+ //
+ service*& srv = instance<M> ();
+ assert (srv == nullptr);
+ srv = this;
+ }
+
+ template <typename M>
+ static service*&
+ instance () noexcept
+ {
+ static service* instance;
+ return instance;
+ }
+
+ template <typename M>
+ static void
+ register_hooks (apr_pool_t *pool) noexcept
+ {
+ ap_hook_handler (&request_handler<M>, NULL, NULL, APR_HOOK_LAST);
+ }
+
+ template <typename M>
+ static int
+ request_handler (request_rec* r) noexcept
+ {
+ auto srv = instance<M> ();
+
+ if (!r->handler || srv->name_ != r->handler)
+ return DECLINED;
+
+ request req (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();
+ }
+ catch (const std::exception& e)
+ {
+ l.write (nullptr, 0, __PRETTY_FUNCTION__, APLOG_ERR, e.what ());
+ }
+ catch (...)
+ {
+ l.write (nullptr,
+ 0,
+ __PRETTY_FUNCTION__,
+ APLOG_ERR,
+ "unknown error");
+ }
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
//@@ Implementation calls handle_ function pointer below:
//
@@ -28,6 +112,7 @@ namespace web
//
private:
+/*
template <typename M>
static void
handle_impl (request& rq, response& rs, log& l, const module& exemplar)
@@ -35,9 +120,10 @@ namespace web
M m (static_cast<const M&> (exemplar));
static_cast<module&> (m).handle (rq, rs, l);
}
-
+*/
+ std::string name_;
const module& exemplar_;
- void (*handle_) (request&, response&, log&, const module&);
+// void (*handle_) (request&, response&, log&, const module&);
};
}
}
diff --git a/web/apache/stream b/web/apache/stream
new file mode 100644
index 0000000..eb62b85
--- /dev/null
+++ b/web/apache/stream
@@ -0,0 +1,161 @@
+// file : web/apache/stream -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#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 <web/module>
+
+namespace web
+{
+ namespace apache
+ {
+ class ostreambuf : public std::streambuf
+ {
+ public:
+ ostreambuf (request_rec* rec) : rec_ (rec) {}
+
+ bool
+ write_flag () const noexcept {return write_;}
+
+ private:
+ virtual int_type
+ overflow (int_type c)
+ {
+ if (c != traits_type::eof ())
+ {
+ flag_write ();
+
+ char chr = c;
+
+ // Throwing allows to distinguish comm failure from other IO error
+ // conditions.
+ //
+ if (ap_rwrite (&chr, sizeof (chr), rec_) == -1)
+ throw invalid_request (HTTP_REQUEST_TIME_OUT);
+ }
+
+ return c;
+ }
+
+ virtual std::streamsize
+ xsputn (const char* s, std::streamsize num)
+ {
+ flag_write ();
+
+ if (ap_rwrite (s, num, rec_) < 0)
+ {
+ throw invalid_request (HTTP_REQUEST_TIME_OUT);
+ }
+
+ return num;
+ }
+
+ virtual int
+ sync ()
+ {
+ if(ap_rflush (rec_) < 0)
+ {
+ throw invalid_request (HTTP_REQUEST_TIME_OUT);
+ }
+
+ 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};
+ };
+
+ class istreambuf : public std::streambuf
+ {
+ public:
+ istreambuf (request_rec* rec, size_t bufsize = 1024, size_t putback = 1)
+ : rec_ (rec),
+ bufsize_ (std::max (bufsize, (size_t)1)),
+ putback_ (std::min (putback, bufsize_ - 1)),
+ buf_ (new char[bufsize_])
+ {
+ char* p = buf_.get () + 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 (gptr () < egptr ())
+ return traits_type::to_int_type (*gptr ());
+
+ size_t pb = std::min ((size_t)(gptr () - eback ()), putback_);
+ std::memmove (buf_.get () + putback_ - pb, gptr () - pb, pb);
+
+ char* p = buf_.get () + putback_;
+ 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 ());
+ }
+
+ bool error () const noexcept {return error_;}
+
+ private:
+
+ request_rec* rec_;
+ size_t bufsize_;
+ size_t putback_;
+ std::unique_ptr<char[]> buf_;
+ bool error_ {false};
+ };
+
+ }
+}
+
+#endif // WEB_APACHE_STREAM
diff --git a/web/module b/web/module
index 642b1bd..9f1c778 100644
--- a/web/module
+++ b/web/module
@@ -5,27 +5,49 @@
#ifndef WEB_MODULE
#define WEB_MODULE
+#include <utility> // move()
+#include <stdexcept> // runtime_error
#include <string>
#include <vector>
#include <iosfwd>
+#include <chrono>
#include <cstdint> // uint16_t
-#include <utility> // move()
-#include <stdexcept> // runtime_error
namespace web
{
- // Exception indicating HTTP request/response sequencing error.
- // For example, trying to change the status code after some
- // content has already been written.
- //
- struct sequence_error: std::runtime_error {};
-
// HTTP status code.
//
// @@ Define some commonly used constants?
//
using status_code = std::uint16_t;
+ // This exception is used to signal that the request is invalid
+ // (4XX codes) rather than that it could not be processed (5XX).
+ // By default 400 is returned, which means the request is malformed.
+ // Non empty description of a caught by the module implementation exception
+ // can be sent to client in http response body with
+ // Content-Type:text/html;charset=utf-8 header.
+ //
+ struct invalid_request
+ {
+ status_code status;
+ std::string description;
+
+ //@@ Maybe optional "try again" link?
+ //
+ invalid_request (status_code s = 400, std::string d = "")
+ : status (s), description (std::move (d)) {}
+ };
+
+ // Exception indicating HTTP request/response sequencing error.
+ // For example, trying to change the status code after some
+ // content has already been written.
+ //
+ struct sequence_error: std::runtime_error
+ {
+ sequence_error (std::string d) : std::runtime_error (d) {}
+ };
+
struct name_value
{
// These should eventually become string_view's.
@@ -48,8 +70,21 @@ namespace web
// in name_values.
//@@ Maybe parameter_list() and parameter_map()?
//
+ // Throw invalid_request if mime url decode of name or value fail.
+ //
virtual const name_values&
parameters () = 0;
+
+ // Throw invalid_request if Cookie header is malformed.
+ //
+ virtual const name_values&
+ cookies () = 0;
+
+ // Get stream to read request body data.
+ // Throw sequence_error if some unbuffered content is already written.
+ //
+ virtual std::istream&
+ data () = 0;
};
class response
@@ -75,9 +110,22 @@ namespace web
content (status_code, const std::string& type, bool buffer = true) = 0;
// Set status code without writing any content.
+ // On status change discards buffered output and throw sequence_error
+ // if output were not buffered.
//
virtual void
status (status_code) = 0;
+
+ // Throw sequence_error if some unbuffered content is already written as
+ // will not be able to send Set-Cookie header.
+ //
+ 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;
};
// A web server logging backend. The module can use it to log
@@ -91,7 +139,7 @@ namespace web
{
public:
virtual void
- write (const char* msg);
+ write (const char* msg) = 0;
};
// The web server creates a new module instance for each request