aboutsummaryrefslogtreecommitdiff
path: root/web/apache
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2020-03-18 22:17:49 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2020-03-27 17:28:44 +0300
commit35359f038f571dc46de3d14af72a2bc911fb0a24 (patch)
treede3e89d678e78b9efc4d395274fd7ccc68f4a213 /web/apache
parent8ad672cc7211952716ffe1fbf76c179b4f1149e3 (diff)
Implement brep-monitor
Diffstat (limited to 'web/apache')
-rw-r--r--web/apache/log.hxx80
-rw-r--r--web/apache/request.cxx1005
-rw-r--r--web/apache/request.hxx233
-rw-r--r--web/apache/request.ixx45
-rw-r--r--web/apache/service.cxx268
-rw-r--r--web/apache/service.hxx333
-rw-r--r--web/apache/service.txx213
-rw-r--r--web/apache/stream.hxx148
8 files changed, 0 insertions, 2325 deletions
diff --git a/web/apache/log.hxx b/web/apache/log.hxx
deleted file mode 100644
index 6609190..0000000
--- a/web/apache/log.hxx
+++ /dev/null
@@ -1,80 +0,0 @@
-// file : web/apache/log.hxx -*- C++ -*-
-// license : MIT; see accompanying LICENSE file
-
-#ifndef WEB_APACHE_LOG_HXX
-#define WEB_APACHE_LOG_HXX
-
-#include <httpd.h> // request_rec, server_rec
-#include <http_log.h>
-#include <http_config.h> // module
-
-#include <cstdint> // uint64_t
-#include <algorithm> // min()
-
-#include <web/module.hxx>
-
-namespace web
-{
- namespace apache
- {
- class log: public web::log
- {
- public:
-
- log (server_rec* s, const ::module* m) noexcept
- : server_ (s), module_ (m) {}
-
- virtual void
- write (const char* msg) {write (APLOG_ERR, msg);}
-
- // Apache-specific interface.
- //
- void
- write (int level, const char* msg) const noexcept
- {
- write (nullptr, 0, nullptr, level, msg);
- }
-
- void
- write (const char* file,
- std::uint64_t line,
- const char* func,
- int level,
- const char* msg) const noexcept
- {
- if (file && *file)
- file = nullptr; // Skip file/line placeholder from log line.
-
- level = std::min (level, APLOG_TRACE8);
-
- if (func)
- ap_log_error (file,
- line,
- module_->module_index,
- level,
- 0,
- server_,
- "[%s]: %s",
- func,
- msg);
- else
- // Skip function name placeholder from log line.
- //
- ap_log_error (file,
- line,
- module_->module_index,
- level,
- 0,
- server_,
- ": %s",
- msg);
- }
-
- private:
- server_rec* server_;
- const ::module* module_; // Apache module.
- };
- }
-}
-
-#endif // WEB_APACHE_LOG_HXX
diff --git a/web/apache/request.cxx b/web/apache/request.cxx
deleted file mode 100644
index 4722b7f..0000000
--- a/web/apache/request.cxx
+++ /dev/null
@@ -1,1005 +0,0 @@
-// file : web/apache/request.cxx -*- C++ -*-
-// license : MIT; see accompanying LICENSE file
-
-#include <web/apache/request.hxx>
-
-#include <apr.h> // APR_SIZE_MAX
-#include <apr_errno.h> // apr_status_t, APR_SUCCESS, APR_E*, apr_strerror()
-#include <apr_tables.h> // apr_table_*, apr_table_*(), apr_array_header_t
-#include <apr_strings.h> // apr_pstrdup()
-#include <apr_buckets.h> // apr_bucket*, apr_bucket_*(), apr_brigade_*(),
- // APR_BRIGADE_*()
-
-#include <httpd.h> // request_rec, HTTP_*, OK
-#include <http_protocol.h> // ap_*()
-
-#include <apreq2/apreq.h> // APREQ_*
-#include <apreq2/apreq_util.h> // apreq_brigade_copy()
-#include <apreq2/apreq_param.h> // apreq_param_t, apreq_value_to_param()
-#include <apreq2/apreq_parser.h> // apreq_parser_t, apreq_parser_make()
-
-#include <ctime> // strftime(), time_t
-#include <vector>
-#include <chrono>
-#include <memory> // unique_ptr
-#include <string>
-#include <cassert>
-#include <ostream>
-#include <istream>
-#include <cstring> // str*(), memcpy(), size_t
-#include <utility> // move()
-#include <iterator> // istreambuf_iterator
-#include <stdexcept> // invalid_argument, runtime_error
-#include <exception> // current_exception()
-#include <streambuf>
-#include <algorithm> // min()
-
-#include <libbutl/utility.mxx> // icasecmp()
-#include <libbutl/optional.mxx>
-#include <libbutl/timestamp.mxx>
-
-#include <web/mime-url-encoding.hxx>
-
-using namespace std;
-using namespace butl;
-
-namespace web
-{
- namespace apache
- {
- [[noreturn]] static void
- throw_internal_error (apr_status_t s, const string& what)
- {
- char buf[1024];
- throw runtime_error (what + ": " + apr_strerror (s, buf, sizeof (buf)));
- }
-
- // Extend the Apache stream with checking for the read limit and caching
- // the content if requested. Replay the cached content after rewind.
- //
- class istreambuf_cache: public istreambuf
- {
- enum class mode
- {
- cache, // Read from Apache stream, save the read data into the cache.
- replay, // Read from the cache.
- proxy // Read from Apache stream (don't save into the cache).
- };
-
- public:
- istreambuf_cache (size_t read_limit, size_t cache_limit,
- request_rec* r,
- stream_state& s,
- size_t bufsize = 1024,
- size_t putback = 1)
- : istreambuf (r, s, bufsize, putback),
- read_limit_ (read_limit),
- cache_limit_ (cache_limit)
- {
- }
-
- void
- rewind ()
- {
- // Fail if some content is already missed in the cache.
- //
- if (mode_ == mode::proxy)
- throw sequence_error (
- string ("web::apache::istreambuf_cache::rewind: ") +
- (cache_limit_ > 0
- ? "half-buffered"
- : "unbuffered"));
-
- mode_ = mode::replay;
- replay_pos_ = 0;
- setg (nullptr, nullptr, nullptr);
- }
-
- void
- limits (size_t read_limit, size_t cache_limit)
- {
- if (read_limit > 0)
- read_limit_ = read_limit;
-
- if (cache_limit > 0)
- {
- // We can not increase the cache limit if some content is already
- // missed in the cache.
- //
- if (cache_limit > cache_limit_ && mode_ == mode::proxy)
- throw sequence_error (
- "web::apache::istreambuf_cache::limits: unbuffered");
-
- cache_limit_ = cache_limit;
- }
- }
-
- size_t read_limit () const noexcept {return read_limit_;}
- size_t cache_limit () const noexcept {return cache_limit_;}
-
- private:
- virtual int_type
- underflow ();
-
- private:
- // Limits
- //
- size_t read_limit_;
- size_t cache_limit_;
-
- // State
- //
- mode mode_ = mode::cache;
- size_t read_bytes_ = 0;
- bool eof_ = false; // End of Apache stream is reached.
-
- // Cache
- //
- struct chunk
- {
- vector<char> data;
- size_t offset;
-
- chunk (vector<char>&& d, size_t o): data (move (d)), offset (o) {}
-
- // Make the type move constructible-only to avoid copying of chunks on
- // vector growth.
- //
- chunk (chunk&&) = default;
- };
-
- vector<chunk> cache_;
- size_t cache_size_ = 0;
- size_t replay_pos_ = 0;
- };
-
- istreambuf_cache::int_type istreambuf_cache::
- underflow ()
- {
- if (gptr () < egptr ())
- return traits_type::to_int_type (*gptr ());
-
- if (mode_ == mode::replay)
- {
- if (replay_pos_ < cache_.size ())
- {
- chunk& ch (cache_[replay_pos_++]);
- char* p (ch.data.data ());
- setg (p, p + ch.offset, p + ch.data.size ());
- return traits_type::to_int_type (*gptr ());
- }
-
- // No more data to replay, so switch to the cache mode. That includes
- // resetting eback, gptr and egptr, so they point into the istreambuf's
- // internal buffer. Putback area should also be restored.
- //
- mode_ = mode::cache;
-
- // Bail out if the end of stream is reached.
- //
- if (eof_)
- return traits_type::eof ();
-
- char* p (buf_.data () + putback_);
- size_t pb (0);
-
- // Restore putback area if there is any cached data. Thanks to
- // istreambuf, it's all in a single chunk.
- //
- if (!cache_.empty ())
- {
- chunk& ch (cache_.back ());
- pb = min (putback_, ch.data.size ());
- memcpy (p - pb, ch.data.data () + ch.data.size () - pb, pb);
- }
-
- setg (p - pb, p, p);
- }
-
- // Delegate reading to the base class in the cache or proxy modes, but
- // check for the read limit first.
- //
- if (read_limit_ && read_bytes_ >= read_limit_)
- throw invalid_request (HTTP_REQUEST_ENTITY_TOO_LARGE,
- "payload too large");
-
- // Throws the sequence_error exception if some unbuffered content is
- // already written.
- //
- int_type r (istreambuf::underflow ());
-
- if (r == traits_type::eof ())
- {
- eof_ = true;
- return r;
- }
-
- // Increment the read bytes counter.
- //
- size_t rb (egptr () - gptr ());
- read_bytes_ += rb;
-
- // In the cache mode save the read data if the cache limit is not
- // reached, otherwise switch to the proxy mode.
- //
- if (mode_ == mode::cache)
- {
- // Not to complicate things we will copy the buffer into the cache
- // together with the putback area, which is OK as it usually takes a
- // small fraction of the buffer. By the same reason we will cache the
- // whole data read even though we can exceed the limits by
- // bufsize - putback - 1 bytes.
- //
- if (cache_size_ < cache_limit_)
- {
- chunk ch (vector<char> (eback (), egptr ()),
- static_cast<size_t> (gptr () - eback ()));
-
- cache_.emplace_back (move (ch));
- cache_size_ += rb;
- }
- else
- mode_ = mode::proxy;
- }
-
- return r;
- }
-
- // Stream interface for reading from the Apache's bucket brigade. Put back
- // is not supported.
- //
- // Note that reading from a brigade bucket modifies the brigade in the
- // general case. For example, reading from a file bucket adds a new heap
- // bucket before the file bucket on every read. Traversing/reading through
- // such a bucket brigade effectively loads the whole file into the memory,
- // so the subsequent brigade traversal results in iterating over the
- // loaded heap buckets.
- //
- // To avoid such a behavior we will make a shallow copy of the original
- // bucket brigade, initially and for each rewind. Then, instead of
- // iterating, we will always read from the first bucket removing it after
- // the use.
- //
- class istreambuf_buckets: public streambuf
- {
- public:
- // The bucket brigade must exist during the object's lifetime.
- //
- explicit
- istreambuf_buckets (const apr_bucket_brigade* bs)
- : orig_buckets_ (bs),
- buckets_ (apr_brigade_create (bs->p, bs->bucket_alloc))
-
- {
- if (buckets_ == nullptr)
- throw_internal_error (APR_ENOMEM, "apr_brigade_create");
-
- rewind (); // Copy the original buckets.
- }
-
- void
- rewind ()
- {
- // Note that apreq_brigade_copy() appends buckets to the destination,
- // so we clean it up first.
- //
- apr_status_t r (apr_brigade_cleanup (buckets_.get ()));
- if (r != APR_SUCCESS)
- throw_internal_error (r, "apr_brigade_cleanup");
-
- r = apreq_brigade_copy (
- buckets_.get (),
- const_cast<apr_bucket_brigade*> (orig_buckets_));
-
- if (r != APR_SUCCESS)
- throw_internal_error (r, "apreq_brigade_copy");
-
- setg (nullptr, nullptr, nullptr);
- }
-
- private:
- virtual int_type
- underflow ()
- {
- if (gptr () < egptr ())
- return traits_type::to_int_type (*gptr ());
-
- // If the get-pointer is not NULL then it points to the data referred
- // by the first brigade bucket. As we will bail out or rewrite such a
- // pointer now there is no need for the bucket either, so we can
- // safely delete it.
- //
- if (gptr () != nullptr)
- {
- assert (!APR_BRIGADE_EMPTY (buckets_));
-
- // Note that apr_bucket_delete() is a macro and the following
- // call ends up badly (with SIGSEGV).
- //
- // apr_bucket_delete (APR_BRIGADE_FIRST (buckets_));
- //
- apr_bucket* b (APR_BRIGADE_FIRST (buckets_));
- apr_bucket_delete (b);
- }
-
- if (APR_BRIGADE_EMPTY (buckets_))
- return traits_type::eof ();
-
- apr_size_t n;
- const char* d;
- apr_bucket* b (APR_BRIGADE_FIRST (buckets_));
- apr_status_t r (apr_bucket_read (b, &d, &n, APR_BLOCK_READ));
-
- if (r != APR_SUCCESS)
- throw_internal_error (r, "apr_bucket_read");
-
- char* p (const_cast<char*> (d));
- setg (p, p, p + n);
- return traits_type::to_int_type (*gptr ());
- }
-
- private:
- const apr_bucket_brigade* orig_buckets_;
-
- struct brigade_deleter
- {
- void operator() (apr_bucket_brigade* p) const
- {
- if (p != nullptr)
- {
- apr_status_t r (apr_brigade_destroy (p));
-
- // Shouldn't fail unless something is severely damaged.
- //
- assert (r == APR_SUCCESS);
- }
- }
- };
-
- unique_ptr<apr_bucket_brigade, brigade_deleter> buckets_;
- };
-
- class istream_buckets_base
- {
- public:
- explicit
- istream_buckets_base (const apr_bucket_brigade* bs): buf_ (bs) {}
-
- protected:
- istreambuf_buckets buf_;
- };
-
- class istream_buckets: public istream_buckets_base, public istream
- {
- public:
- explicit
- istream_buckets (const apr_bucket_brigade* bs)
- // Note that calling dtor for istream object before init() is called
- // is undefined behavior. That's the reason for inventing the
- // istream_buckets_base class.
- //
- : istream_buckets_base (bs), istream (&buf_)
- {
- exceptions (failbit | badbit);
- }
-
- void
- rewind ()
- {
- buf_.rewind ();
- clear (); // Clears *bit flags (in particular eofbit).
- }
- };
-
- // request
- //
- request::
- request (request_rec* rec) noexcept
- : rec_ (rec)
- {
- rec_->status = HTTP_OK;
- }
-
- request::
- ~request ()
- {
- }
-
- 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 ()
- {
- // @@ Response cookies buffering is not supported yet. When done will be
- // possible to rewind in broader range of cases.
- //
- if (state_ > request_state::reading)
- throw sequence_error ("web::apache::request::rewind: unbuffered");
-
- out_.reset ();
- out_buf_.reset ();
-
- rec_->status = HTTP_OK;
-
- ap_set_content_type (rec_, nullptr); // Unset the output content type.
-
- // We don't need to rewind the input stream (which well may fail if
- // unbuffered) if the form data is already read.
- //
- if (in_ != nullptr && form_data_ == nullptr)
- {
- assert (in_buf_ != nullptr);
-
- in_buf_->rewind (); // Throws if impossible to rewind.
- in_->clear (); // Clears *bit flags (in particular eofbit).
- }
-
- // Rewind uploaded file streams.
- //
- if (uploads_ != nullptr)
- {
- for (const unique_ptr<istream_buckets>& is: *uploads_)
- {
- if (is != nullptr)
- is->rewind ();
- }
- }
- }
-
- istream& request::
- content (size_t limit, size_t buffer)
- {
- // Create the input stream/streambuf if not present, otherwise adjust the
- // limits.
- //
- if (in_ == nullptr)
- {
- unique_ptr<istreambuf_cache> in_buf (
- new istreambuf_cache (limit, buffer, rec_, *this));
-
- in_.reset (new istream (in_buf.get ()));
- in_buf_ = move (in_buf);
- in_->exceptions (istream::failbit | istream::badbit);
- }
- else
- {
- assert (in_buf_ != nullptr);
- in_buf_->limits (limit, buffer);
- }
-
- return *in_;
- }
-
- const path& request::
- path ()
- {
- if (path_.empty ())
- {
- path_ = path_type (rec_->uri); // Is already URL-decoded.
-
- // Module request handler can not be called if URI is empty.
- //
- assert (!path_.empty ());
- }
-
- return path_;
- }
-
- const name_values& request::
- parameters (size_t limit, bool url_only)
- {
- if (parameters_ == nullptr || url_only < url_only_parameters_)
- {
- try
- {
- if (parameters_ == nullptr)
- {
- parameters_.reset (new name_values ());
- parse_url_parameters (rec_->args);
- }
-
- if (!url_only && form_data (limit))
- {
- // After the form data is parsed we can clean it up for the
- // application/x-www-form-urlencoded encoding but not for the
- // multipart/form-data (see parse_multipart_parameters() for
- // details).
- //
- if (form_multipart_)
- parse_multipart_parameters (*form_data_);
- else
- {
- // Make the character vector a NULL-terminated string.
- //
- form_data_->push_back ('\0');
-
- parse_url_parameters (form_data_->data ());
- *form_data_ = vector<char> (); // Reset the cache.
- }
- }
- }
- catch (const invalid_argument&)
- {
- throw invalid_request ();
- }
-
- url_only_parameters_ = url_only;
- }
-
- return *parameters_;
- }
-
- bool request::
- form_data (size_t limit)
- {
- if (form_data_ == nullptr)
- {
- form_data_.reset (new vector<char> ());
-
- // We will not consider POST body as a form data if the request is in
- // the reading or later state.
- //
- if (rec_->method_number == M_POST && state_ < request_state::reading)
- {
- const char* ct (apr_table_get (rec_->headers_in, "Content-Type"));
-
- if (ct != nullptr)
- {
- form_multipart_ = icasecmp ("multipart/form-data", ct, 19) == 0;
-
- if (form_multipart_ ||
- icasecmp ("application/x-www-form-urlencoded", ct, 33) == 0)
- *form_data_ = vector<char> (
- istreambuf_iterator<char> (content (limit)),
- istreambuf_iterator<char> ());
- }
- }
- }
-
- return !form_data_->empty ();
- }
-
- void request::
- parse_url_parameters (const char* args)
- {
- assert (parameters_ != nullptr);
-
- for (auto n (args); n != nullptr; )
- {
- const char* v (strchr (n, '='));
- const char* e (strchr (n, '&'));
-
- if (e != nullptr && e < v)
- v = nullptr;
-
- string name (v != nullptr
- ? mime_url_decode (n, v) :
- (e
- ? mime_url_decode (n, e)
- : mime_url_decode (n, n + strlen (n))));
-
- optional<string> value;
-
- if (v++)
- value = e
- ? mime_url_decode (v, e)
- : mime_url_decode (v, v + strlen (v));
-
- if (!name.empty () || value)
- parameters_->emplace_back (move (name), move (value));
-
- n = e ? e + 1 : nullptr;
- }
- }
-
- void request::
- parse_multipart_parameters (const vector<char>& body)
- {
- assert (parameters_ != nullptr && uploads_ == nullptr);
-
- auto throw_bad_request = [] (apr_status_t s,
- status_code sc = HTTP_BAD_REQUEST)
- {
- char buf[1024];
- throw invalid_request (sc, apr_strerror (s, buf, sizeof (buf)));
- };
-
- // Create the file upload stream list, filling it with NULLs for the
- // parameters parsed from the URL query part.
- //
- uploads_.reset (
- new vector<unique_ptr<istream_buckets>> (parameters_->size ()));
-
- // All the required objects (parser, input/output buckets, etc.) will be
- // allocated in the request memory pool and so will have the HTTP
- // request duration lifetime.
- //
- apr_pool_t* pool (rec_->pool);
-
- // Create the input bucket brigade containing a single bucket that
- // references the form data.
- //
- apr_bucket_alloc_t* ba (apr_bucket_alloc_create (pool));
- if (ba == nullptr)
- throw_internal_error (APR_ENOMEM, "apr_bucket_alloc_create");
-
- apr_bucket_brigade* bb (apr_brigade_create (pool, ba));
- if (bb == nullptr)
- throw_internal_error (APR_ENOMEM, "apr_brigade_create");
-
- apr_bucket* b (
- apr_bucket_immortal_create (body.data (), body.size (), ba));
-
- if (b == nullptr)
- throw_internal_error (APR_ENOMEM, "apr_bucket_immortal_create");
-
- APR_BRIGADE_INSERT_TAIL (bb, b);
-
- if ((b = apr_bucket_eos_create (ba)) == nullptr)
- throw_internal_error (APR_ENOMEM, "apr_bucket_eos_create");
-
- APR_BRIGADE_INSERT_TAIL (bb, b);
-
- // Make sure that the parser will not swap the parsed data to disk
- // passing the maximum possible value for the brigade limit. This way
- // the resulting buckets will reference the form data directly, making
- // no copies. This why we should not reset the form data cache after
- // the parsing.
- //
- // Note that in future we may possibly setup the parser to read from the
- // Apache internals directly and enable swapping the data to disk to
- // minimize memory consumption.
- //
- apreq_parser_t* parser (
- apreq_parser_make (pool,
- ba,
- apr_table_get (rec_->headers_in, "Content-Type"),
- apreq_parse_multipart,
- APR_SIZE_MAX /* brigade_limit */,
- nullptr /* temp_dir */,
- nullptr /* hook */,
- nullptr /* ctx */));
-
- if (parser == nullptr)
- throw_internal_error (APR_ENOMEM, "apreq_parser_make");
-
- // Create the output table that will be filled with the parsed
- // parameters.
- //
- apr_table_t* params (apr_table_make (pool, APREQ_DEFAULT_NELTS));
- if (params == nullptr)
- throw_internal_error (APR_ENOMEM, "apr_table_make");
-
- // Parse the form data.
- //
- apr_status_t r (apreq_parser_run (parser, params, bb));
- if (r != APR_SUCCESS)
- throw_bad_request (r);
-
- // Fill the parameter and file upload stream lists.
- //
- const apr_array_header_t* ps (apr_table_elts (params));
- size_t n (ps->nelts);
-
- for (auto p (reinterpret_cast<const apr_table_entry_t*> (ps->elts));
- n--; ++p)
- {
- assert (p->key != nullptr && p->val != nullptr);
-
- if (*p->key != '\0')
- {
- parameters_->emplace_back (p->key, optional<string> (p->val));
-
- const apreq_param_t* ap (apreq_value_to_param (p->val));
- assert (ap != nullptr); // Must always be resolvable.
-
- uploads_->emplace_back (ap->upload != nullptr
- ? new istream_buckets (ap->upload)
- : nullptr);
- }
- }
- }
-
- request::uploads_type& request::
- uploads () const
- {
- if (parameters_ == nullptr || url_only_parameters_)
- sequence_error ("web::apache::request::uploads");
-
- if (uploads_ == nullptr)
- throw invalid_argument ("no uploads");
-
- assert (uploads_->size () == parameters_->size ());
- return *uploads_;
- }
-
- istream& request::
- open_upload (size_t index)
- {
- uploads_type& us (uploads ());
- size_t n (us.size ());
-
- if (index >= n)
- throw invalid_argument ("invalid index");
-
- const unique_ptr<istream_buckets>& is (us[index]);
-
- if (is == nullptr)
- throw invalid_argument ("no upload");
-
- return *is;
- }
-
- istream& request::
- open_upload (const string& name)
- {
- uploads_type& us (uploads ());
- size_t n (us.size ());
-
- istream* r (nullptr);
- for (size_t i (0); i < n; ++i)
- {
- if ((*parameters_)[i].name == name)
- {
- istream* is (us[i].get ());
-
- if (is != nullptr)
- {
- if (r != nullptr)
- throw invalid_argument ("multiple uploads for '" + name + "'");
-
- r = is;
- }
- }
- }
-
- if (r == nullptr)
- throw invalid_argument ("no upload");
-
- return *r;
- }
-
- const name_values& request::
- headers ()
- {
- if (headers_ == nullptr)
- {
- headers_.reset (new name_values ());
-
- const apr_array_header_t* ha (apr_table_elts (rec_->headers_in));
- size_t n (ha->nelts);
-
- headers_->reserve (n + 1); // One for the custom :Client-IP header.
-
- auto add = [this] (const char* n, const char* v)
- {
- assert (n != nullptr && v != nullptr);
- headers_->emplace_back (n, optional<string> (v));
- };
-
- for (auto h (reinterpret_cast<const apr_table_entry_t*> (ha->elts));
- n--; ++h)
- add (h->key, h->val);
-
- assert (rec_->connection != nullptr);
-
- add (":Client-IP", rec_->connection->client_ip);
- }
-
- return *headers_;
- }
-
- const name_values& request::
- cookies ()
- {
- if (cookies_ == nullptr)
- {
- 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)
- {
- assert (h->key != nullptr);
-
- if (icasecmp (h->key, "Cookie") == 0)
- {
- for (const char* n (h->val); n != nullptr; )
- {
- const char* v (strchr (n, '='));
- const char* e (strchr (n, ';'));
-
- if (e != nullptr && e < v)
- v = nullptr;
-
- string name (v != nullptr
- ? mime_url_decode (n, v, true)
- : (e
- ? mime_url_decode (n, e, true)
- : mime_url_decode (n, n + strlen (n), true)));
-
- optional<string> value;
-
- if (v++)
- value = e
- ? mime_url_decode (v, e, true)
- : mime_url_decode (v, v + strlen (v), true);
-
- if (!name.empty () || value)
- cookies_->emplace_back (move (name), move (value));
-
- n = e ? e + 1 : nullptr;
- }
- }
- }
- }
-
- return *cookies_;
- }
-
- ostream& request::
- content (status_code status, const string& type, bool buffer)
- {
- if (out_ &&
-
- // Same status code.
- //
- status == rec_->status &&
-
- // Same buffering flag.
- //
- buffer ==
- (dynamic_cast<stringbuf*> (out_buf_.get ()) != nullptr) &&
-
- // Same content type.
- //
- icasecmp (type, rec_->content_type ? rec_->content_type : "") == 0)
- {
- // No change, return the existing stream.
- //
- return *out_;
- }
-
- 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
- // written. Save form data now to make it available for future
- // parameters() call.
- //
- // In the rare cases when the form data is expectedly bigger than 64K
- // the client can always call parameters(limit) explicitly.
- //
- form_data (64 * 1024);
-
- unique_ptr<streambuf> out_buf (
- buffer
- ? static_cast<streambuf*> (new stringbuf ())
- : static_cast<streambuf*> (new ostreambuf (rec_, *this)));
-
- out_.reset (new ostream (out_buf.get ()));
- out_buf_ = move (out_buf);
- out_->exceptions (ostream::eofbit | ostream::failbit | ostream::badbit);
-
- rec_->status = status;
-
- ap_set_content_type (
- rec_,
- type.empty () ? nullptr : apr_pstrdup (rec_->pool, type.c_str ()));
-
- return *out_;
- }
-
- void request::
- status (status_code 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 (state_ >= request_state::writing && !current_exception ())
- throw sequence_error ("web::apache::request::status");
-
- rec_->status = status;
- out_.reset ();
- out_buf_.reset ();
- ap_set_content_type (rec_, nullptr);
- }
- }
-
- void request::
- cookie (const char* name,
- const char* value,
- const chrono::seconds* max_age,
- const char* path,
- const char* domain,
- bool secure,
- bool buffer)
- {
- assert (!buffer); // Cookie buffering is not implemented yet.
-
- string s (mime_url_encode (name));
- s += "=";
- s += mime_url_encode (value);
-
- if (max_age)
- {
- timestamp tp (system_clock::now () + *max_age);
- time_t t (system_clock::to_time_t (tp));
-
- // Assume global locale is not changed and still "C".
- //
- char b[100];
- strftime (b, sizeof (b), "%a, %d-%b-%Y %H:%M:%S GMT", gmtime (&t));
- s += "; Expires=";
- s += b;
- }
-
- if (path)
- {
- s += ";Path=";
- s += path;
- }
-
- if (domain)
- {
- s += ";Domain=";
- s += domain;
- }
-
- if (secure)
- s += ";Secure";
-
- state (request_state::headers);
- apr_table_add (rec_->err_headers_out, "Set-Cookie", s.c_str ());
- }
- }
-}
diff --git a/web/apache/request.hxx b/web/apache/request.hxx
deleted file mode 100644
index 793a09d..0000000
--- a/web/apache/request.hxx
+++ /dev/null
@@ -1,233 +0,0 @@
-// file : web/apache/request.hxx -*- C++ -*-
-// license : MIT; see accompanying LICENSE file
-
-#ifndef WEB_APACHE_REQUEST_HXX
-#define WEB_APACHE_REQUEST_HXX
-
-#include <httpd.h> // request_rec, HTTP_*, OK, M_POST
-
-#include <chrono>
-#include <memory> // unique_ptr
-#include <string>
-#include <vector>
-#include <istream>
-#include <ostream>
-#include <streambuf>
-
-#include <web/module.hxx>
-#include <web/apache/stream.hxx>
-
-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
- };
-
- // Extends istreambuf with read limit checking, caching, etc. (see the
- // implementation for details).
- //
- class istreambuf_cache;
-
- // Stream type for reading from Apache's bucket brigades.
- //
- class istream_buckets;
-
- class request: public web::request,
- public web::response,
- public stream_state
- {
- friend class service;
-
- // Can not be inline/default due to the member of
- // unique_ptr<istreambuf_cache> type. Note that istreambuf_cache type is
- // incomplete.
- //
- request (request_rec* rec) noexcept;
- ~request ();
-
- request_state
- state () const noexcept {return state_;}
-
- // Flush the buffered response content if present. The returned value
- // should be passed to Apache API on request handler exit.
- //
- int
- flush ();
-
- // 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.
- //
- void
- rewind ();
-
- // Get request path.
- //
- virtual const path_type&
- path ();
-
- // Get request body data stream.
- //
- virtual std::istream&
- content (std::size_t limit = 0, std::size_t buffer = 0);
-
- // Get request parameters.
- //
- virtual const name_values&
- parameters (std::size_t limit, bool url_only = false);
-
- // Get upload stream.
- //
- virtual std::istream&
- open_upload (std::size_t index);
-
- virtual std::istream&
- open_upload (const std::string& name);
-
- // Get request headers.
- //
- virtual const name_values&
- headers ();
-
- // Get request cookies.
- //
- virtual const name_values&
- cookies ();
-
- // Get response status code.
- //
- status_code
- status () const noexcept {return rec_->status;}
-
- // Set response status code.
- //
- virtual void
- status (status_code status);
-
- // 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 = nullptr,
- const char* path = nullptr,
- const char* domain = nullptr,
- bool secure = false,
- bool buffer = true);
-
- private:
- // On the first call cache the application/x-www-form-urlencoded or
- // multipart/form-data form data for the subsequent parameters parsing
- // and set the multipart flag accordingly. Don't cache if the request is
- // in the reading or later state. Return true if the cache contains the
- // form data.
- //
- // Note that the function doesn't change the content buffering (see
- // content() function for details) nor rewind the content stream after
- // reading.
- //
- bool
- form_data (std::size_t limit);
-
- // Used to also parse application/x-www-form-urlencoded POST body.
- //
- void
- parse_url_parameters (const char* args);
-
- void
- parse_multipart_parameters (const std::vector<char>& body);
-
- // Return a list of the upload input streams. Throw sequence_error if
- // the parameters() function was not called yet. Throw invalid_argument
- // if the request doesn't contain multipart form data.
- //
- using uploads_type = std::vector<std::unique_ptr<istream_buckets>>;
-
- uploads_type&
- uploads () const;
-
- // 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_read_state () {state (request_state::reading);}
-
- virtual void
- set_write_state () {state (request_state::writing);}
-
- private:
- request_rec* rec_;
- request_state state_ = request_state::initial;
-
- path_type path_;
-
- std::unique_ptr<name_values> parameters_;
- bool url_only_parameters_; // Meaningless if parameters_ is NULL;
-
- // Uploaded file streams. If not NULL, is parallel to the parameters
- // list.
- //
- std::unique_ptr<uploads_type> uploads_;
-
- std::unique_ptr<name_values> headers_;
- std::unique_ptr<name_values> cookies_;
-
- // Form data cache. Is empty if the body doesn't contain the form data.
- //
- std::unique_ptr<std::vector<char>> form_data_;
- bool form_multipart_; // Meaningless if form_data_ is NULL or empty;
-
- std::unique_ptr<istreambuf_cache> in_buf_;
- std::unique_ptr<std::istream> in_;
-
- std::unique_ptr<std::streambuf> out_buf_;
- std::unique_ptr<std::ostream> out_;
- };
- }
-}
-
-#include <web/apache/request.ixx>
-
-#endif // WEB_APACHE_REQUEST_HXX
diff --git a/web/apache/request.ixx b/web/apache/request.ixx
deleted file mode 100644
index 3a1c01a..0000000
--- a/web/apache/request.ixx
+++ /dev/null
@@ -1,45 +0,0 @@
-// file : web/apache/request.ixx -*- C++ -*-
-// license : MIT; see accompanying LICENSE file
-
-#include <http_protocol.h> // ap_*()
-
-#include <sstream> // stringbuf
-
-namespace web
-{
- namespace apache
- {
- inline int request::
- flush ()
- {
- if (std::stringbuf* b = dynamic_cast<std::stringbuf*> (out_buf_.get ()))
- {
- // Response content is buffered.
- //
- std::string s (b->str ());
-
- if (!s.empty ())
- {
- try
- {
- state (request_state::writing);
-
- if (ap_rwrite (s.c_str (), s.length (), rec_) < 0)
- rec_->status = HTTP_REQUEST_TIME_OUT;
- }
- catch (const invalid_request& e)
- {
- rec_->status = e.status;
- }
- }
-
- out_.reset ();
- out_buf_.reset ();
- }
-
- return rec_->status == HTTP_OK || state_ >= request_state::writing
- ? OK
- : rec_->status;
- }
- }
-}
diff --git a/web/apache/service.cxx b/web/apache/service.cxx
deleted file mode 100644
index 1eeb65e..0000000
--- a/web/apache/service.cxx
+++ /dev/null
@@ -1,268 +0,0 @@
-// file : web/apache/service.cxx -*- C++ -*-
-// license : MIT; see accompanying LICENSE file
-
-#include <web/apache/service.hxx>
-
-#include <apr_pools.h> // apr_palloc()
-
-#include <httpd.h> // server_rec
-#include <http_config.h> // command_rec, cmd_*, ap_get_module_config()
-
-#include <memory> // unique_ptr
-#include <string>
-#include <cassert>
-#include <utility> // move()
-#include <cstring> // strlen(), strcmp()
-#include <exception>
-
-#include <libbutl/utility.mxx> // function_cast()
-#include <libbutl/optional.mxx>
-
-#include <web/module.hxx>
-#include <web/apache/log.hxx>
-
-using namespace std;
-using namespace butl;
-
-namespace web
-{
- namespace apache
- {
- void service::
- init_directives ()
- {
- 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.
- //
- const option_descriptions& od (exemplar_.options ());
- unique_ptr<command_rec[]> directives (new command_rec[od.size () + 2]);
- command_rec* d (directives.get ());
-
- for (const auto& o: od)
- {
- auto i (
- option_descriptions_.emplace (name_ + "-" + o.first, o.second));
- assert (i.second);
-
- *d++ =
- {
- i.first->first.c_str (),
- function_cast<cmd_func> (parse_option),
- this,
-
- // Allow directives in both server and directory configuration
- // scopes.
- //
- RSRC_CONF | ACCESS_CONF,
-
- // Move away from TAKE1 to be able to handle empty string and
- // no-value.
- //
- RAW_ARGS,
-
- nullptr
- };
- }
-
- // Track if the handler is allowed to handle a request in the specific
- // configuration scope. The handler exemplar will be created (and
- // initialized) only for configuration contexts that have
- // 'SetHandler <mod_name>' in effect for the corresponding scope.
- //
- *d++ =
- {
- "SetHandler",
- function_cast<cmd_func> (parse_option),
- this,
- RSRC_CONF | ACCESS_CONF,
- RAW_ARGS,
- nullptr
- };
-
- *d = {nullptr, nullptr, nullptr, 0, RAW_ARGS, nullptr};
- cmds = directives.release ();
- }
-
- void* service::
- 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.
- //
- void* p (apr_palloc (pool, sizeof (context)));
- assert (p != nullptr);
- return new (p) context ();
- }
-
- void* service::
- create_dir_context (apr_pool_t* pool, char* dir) 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.
- //
- void* p (apr_palloc (pool, sizeof (context)));
- assert (p != nullptr);
-
- // For the user-defined directory configuration context dir is the path
- // of the corresponding directive. For the special server directory
- // invented by Apache for server scope directives, dir is NULL.
- //
- return new (p) context (dir == nullptr);
- }
-
- const char* service::
- parse_option (cmd_parms* parms, void* conf, const char* args) noexcept
- {
- 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.
- //
- srv.clear_config ();
-
- // 'args' is an optionally double-quoted string. It uses double quotes
- // to distinguish empty string from no-value case.
- //
- assert (args != nullptr);
-
- optional<string> value;
- if (auto l = strlen (args))
- value = l >= 2 && args[0] == '"' && args[l - 1] == '"'
- ? string (args + 1, l - 2)
- : args;
-
- // Determine the directory and server configuration contexts for the
- // option.
- //
- context* dir_context (context_cast (conf));
- assert (dir_context != nullptr);
-
- server_rec* server (parms->server);
- assert (server != nullptr);
- assert (server->module_config != nullptr);
-
- context* srv_context (
- context_cast (ap_get_module_config (server->module_config, &srv)));
-
- assert (srv_context != nullptr);
-
- // Associate the directory configuration context with the enclosing
- // server configuration context.
- //
- context*& s (dir_context->server);
- if (s == nullptr)
- s = srv_context;
- else
- assert (s == srv_context);
-
- // If the option appears in the special directory configuration context,
- // add it to the enclosing server context instead. This way it will be
- // possible to complement all server-enclosed contexts (including this
- // special one) with the server scope options.
- //
- context* c (dir_context->special ? srv_context : dir_context);
-
- if (dir_context->special)
- //
- // Make sure the special directory context is also in the option lists
- // map. Later the context will be populated with an enclosing server
- // context options.
- //
- srv.options_.emplace (dir_context, name_values ());
-
- const char* name (parms->cmd->name);
- if (strcmp (name, "SetHandler") == 0)
- {
- // Keep track of a request handling allowability.
- //
- srv.options_.emplace (c, name_values ()).first->first->handling =
- value && *value == srv.name_
- ? request_handling::allowed
- : request_handling::disallowed;
-
- return 0;
- }
-
- return srv.add_option (c, name, move (value));
- }
-
- const char* service::
- add_option (context* ctx, const char* name, optional<string> value)
- {
- auto i (option_descriptions_.find (name));
- assert (i != option_descriptions_.end ());
-
- // Check that option value presense is expected.
- //
- if (i->second != static_cast<bool> (value))
- return value ? "unexpected value" : "value expected";
-
- options_[ctx].emplace_back (name + name_.length () + 1, move (value));
- return 0;
- }
-
- void service::
- complement (context* enclosed, context* enclosing)
- {
- auto i (options_.find (enclosing));
-
- // The enclosing context may have no options. It can be the context of a
- // server that has no configuration directives in it's immediate scope,
- // but has ones in it's enclosed scope (directory or virtual server).
- //
- if (i != options_.end ())
- {
- const name_values& src (i->second);
- name_values& dest (options_[enclosed]);
- dest.insert (dest.begin (), src.begin (), src.end ());
- }
-
- if (enclosed->handling == request_handling::inherit)
- enclosed->handling = enclosing->handling;
- }
-
- void service::
- finalize_config (server_rec* s)
- {
- if (!version_logged_)
- {
- log l (s, this);
- exemplar_.version (l);
- version_logged_ = true;
- }
-
- // Complement directory configuration contexts with options of the
- // enclosing server configuration context. By this time virtual server
- // contexts are already complemented with the main server configuration
- // context options as a result of the merge_server_context() calls.
- //
- for (const auto& o: options_)
- {
- // Is a directory configuration context.
- //
- if (o.first->server != nullptr)
- complement (o.first, o.first->server);
- }
-
- options_parsed_ = true;
- }
-
- void service::
- clear_config ()
- {
- options_.clear ();
- options_parsed_ = false;
- }
- }
-}
diff --git a/web/apache/service.hxx b/web/apache/service.hxx
deleted file mode 100644
index aaf006e..0000000
--- a/web/apache/service.hxx
+++ /dev/null
@@ -1,333 +0,0 @@
-// file : web/apache/service.hxx -*- C++ -*-
-// license : MIT; see accompanying LICENSE file
-
-#ifndef WEB_APACHE_SERVICE_HXX
-#define WEB_APACHE_SERVICE_HXX
-
-#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
-#include <string>
-#include <cassert>
-
-#include <web/module.hxx>
-#include <web/apache/log.hxx>
-#include <web/apache/request.hxx>
-
-namespace web
-{
- namespace apache
- {
- // Apache has 3 configuration scopes: main server, virtual server, and
- // directory (location). It provides configuration scope-aware modules
- // with the ability to build a hierarchy of configuration contexts. Later,
- // when processing a request, Apache passes the appropriate directory
- // configuration context to the request handler.
- //
- // This Apache service implementation first makes a copy of the provided
- // (in the constructor below) handler exemplar for each directory context.
- // It then initializes each of these "context exemplars" with the (merged)
- // set of configuration options. Finally, when handling a request, it
- // copies the corresponding "context exemplar" to create the "handling
- // instance". Note that the "context exemplars" are created as a copy of
- // the provided exemplar, which is never initialized. As a result, it is
- // possible to detect if the handler's copy constructor is used to create
- // a "context exemplar" or a "handling instance".
- //
- class service: ::module
- {
- public:
- // Note that the module exemplar is stored by-reference.
- //
- template <typename H>
- service (const std::string& name, H& exemplar)
- : ::module
- {
- STANDARD20_MODULE_STUFF,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- &register_hooks<H>
-
-#ifdef AP_MODULE_HAS_FLAGS
- , AP_MODULE_FLAG_NONE
-#endif
- },
- name_ (name),
- exemplar_ (exemplar)
- {
- init_directives ();
-
- // Set configuration context management hooks.
- //
- // The overall process of building the configuration hierarchy for a
- // handler is as follows:
- //
- // 1. Apache creates directory and server configuration contexts for
- // scopes containing handler-defined directives by calling the
- // create_{server,dir}_context() callback functions. For directives
- // at the server scope the special directory context is created as
- // well.
- //
- // 2. Apache calls parse_option() function for each handler-defined
- // directive. The function parses the directives and places the
- // resulting options into the corresponding configuration context.
- // It also establishes the directory-server contexts relations.
- //
- // 3. Apache calls merge_server_context() function for each virtual
- // server. The function complements virtual server context options
- // with the ones from the main server.
- //
- // 4. Apache calls config_finalizer() which complements the directory
- // contexts options with the ones from the enclosing servers.
- //
- // 5. Apache calls worker_initializer() which creates handler exemplar
- // for each directory configuration context that have
- // 'SetHandler <mod_name>' directive in effect for it.
- //
- // References:
- // http://www.apachetutor.org/dev/config
- // http://httpd.apache.org/docs/2.4/developer/modguide.html
- // http://wiki.apache.org/httpd/ModuleLife
- //
- create_server_config = &create_server_context;
- create_dir_config = &create_dir_context;
- merge_server_config = &merge_server_context<H>;
-
- // instance<H> () 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 handler implementation class with just one instance.
- //
- service*& srv (instance<H> ());
- assert (srv == nullptr);
- srv = this;
- }
-
- ~service ()
- {
- delete [] cmds;
- }
-
- private:
- template <typename H>
- static service*&
- instance () noexcept
- {
- static service* instance;
- return instance;
- }
-
- template <typename H>
- static void
- register_hooks (apr_pool_t*) noexcept
- {
- // The config_finalizer() function is called at the end of Apache
- // server configuration parsing.
- //
- ap_hook_post_config (&config_finalizer<H>, NULL, NULL, APR_HOOK_LAST);
-
- // 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<H>, NULL, NULL, APR_HOOK_LAST);
-
- // The request_handler () function is called for each client request.
- //
- ap_hook_handler (&request_handler<H>, NULL, NULL, APR_HOOK_LAST);
- }
-
- template <typename H>
- static int
- config_finalizer (apr_pool_t*, apr_pool_t*, apr_pool_t*, server_rec* s)
- noexcept
- {
- instance<H> ()->finalize_config (s);
- return OK;
- }
-
- template <typename H>
- static void
- worker_initializer (apr_pool_t*, server_rec* s) noexcept
- {
- auto srv (instance<H> ());
- log l (s, srv);
- srv->template init_worker<H> (l);
- }
-
- template <typename H>
- static int
- request_handler (request_rec* r) noexcept;
-
- private:
-
- // Reflects the allowability of the request handling in the specific
- // configuration scope.
- //
- enum class request_handling
- {
- // Configuration scope has 'SetHandler <mod_name>' directive
- // specified. The handler is allowed to handle a request in the scope.
- //
- allowed,
-
- // Configuration scope has 'SetHandler <other_mod_name>|None'
- // directive specified. The handler is disallowed to handle a request
- // in the scope.
- //
- disallowed,
-
- //
- // Note that if there are several SetHandler directives specified
- // in the specific scope, then the latest one takes the precedence.
-
- // Configuration scope has no SetHandler directive specified. The
- // request handling allowability is established by the enclosing
- // scopes.
- //
- inherit
- };
-
- // Our representation of the Apache configuration context.
- //
- // The lifetime of this object is under the control of the Apache API,
- // which treats it as a raw sequence of bytes. In order not to tinker
- // with the C-style structures and APR memory pools, we will keep it a
- // (C++11) POD type with just the members required to maintain the
- // context hierarchy.
- //
- // We will then use the pointers to these context objects as keys in
- // maps to (1) the corresponding application-level option lists during
- // the configuration cycle and to (2) the corresponding handler exemplar
- // during the HTTP request handling phase. We will also use the same
- // type for both directory and server configuration contexts.
- //
- struct context
- {
- // Outer (server) configuration context for the directory
- // configuration context, NULL otherwise.
- //
- context* server = nullptr;
-
- // If module directives appear directly in the server configuration
- // scope, Apache creates a special directory context for them. This
- // context appears at the same hierarchy level as the user-defined
- // directory contexts of the same server scope.
- //
- bool special;
-
- // Request handling allowability for the corresponding configuration
- // scope.
- //
- request_handling handling = request_handling::inherit;
-
- // Create the server configuration context.
- //
- context (): special (false) {}
-
- // Create the directory configuration context. Due to the Apache API
- // implementation details it is not possible to detect the enclosing
- // server configuration context at the time of directory context
- // creation. As a result, the server member is set by the module's
- // parse_option() function.
- //
- context (bool s): special (s) {}
-
- // Ensure the object is only destroyed by Apache.
- //
- ~context () = delete;
- };
-
- static context*
- context_cast (void* config) noexcept
- {return static_cast<context*> (config);}
-
- private:
- void
- init_directives ();
-
- // Create the server configuration context. Called by the Apache API
- // whenever a new object of that type is required.
- //
- static void*
- create_server_context (apr_pool_t*, server_rec*) noexcept;
-
- // Create the server directory configuration context. Called by the
- // Apache API whenever a new object of that type is required.
- //
- static void*
- create_dir_context (apr_pool_t*, char* dir) noexcept;
-
- template <typename H>
- static void*
- merge_server_context (apr_pool_t*, void* enclosing, void* enclosed)
- noexcept
- {
- instance<H> ()->complement (
- context_cast (enclosed), context_cast (enclosing));
-
- return enclosed;
- }
-
- static const char*
- parse_option (cmd_parms* parms, void* conf, const char* args) noexcept;
-
- const char*
- add_option (context*, const char* name, optional<std::string> value);
-
- void
- finalize_config (server_rec*);
-
- void
- clear_config ();
-
- // Complement the enclosed context with options of the enclosing one.
- // If the 'handling' member of the enclosed context is set to
- // request_handling::inherit value, assign it a value from the enclosing
- // context.
- //
- void
- complement (context* enclosed, context* enclosing);
-
- template <typename H>
- void
- init_worker (log&);
-
- template <typename H>
- int
- handle (request&, const context*, log&) const;
-
- private:
- std::string name_;
- handler& exemplar_;
- option_descriptions option_descriptions_;
-
- // The context objects pointed to by the key can change during the
- // configuration phase.
- //
- using options = std::map<context*, name_values>;
- options options_;
-
- // The context objects pointed to by the key can not change during the
- // request handling phase.
- //
- using exemplars = std::map<const context*, std::unique_ptr<handler>>;
- exemplars exemplars_;
-
- bool options_parsed_ = false;
- bool version_logged_ = false;
- };
- }
-}
-
-#include <web/apache/service.txx>
-
-#endif // WEB_APACHE_SERVICE_HXX
diff --git a/web/apache/service.txx b/web/apache/service.txx
deleted file mode 100644
index bda8e10..0000000
--- a/web/apache/service.txx
+++ /dev/null
@@ -1,213 +0,0 @@
-// file : web/apache/service.txx -*- C++ -*-
-// license : MIT; see accompanying LICENSE file
-
-#include <httpd.h> // APEXIT_CHILDSICK
-#include <http_log.h> // APLOG_*
-
-#include <cstdlib> // exit()
-#include <utility> // move()
-#include <exception>
-
-#include <libbutl/utility.mxx> // operator<<(ostream, exception)
-
-namespace web
-{
- namespace apache
- {
- template <typename H>
- void service::
- init_worker (log& l)
- {
- using namespace std;
-
- const string func_name (
- "web::apache::service<" + name_ + ">::init_worker");
-
- try
- {
- const H* exemplar (dynamic_cast<const H*> (&exemplar_));
- assert (exemplar != nullptr);
-
- // For each directory configuration context, for which the handler is
- // allowed to handle a request, create the handler exemplar as a deep
- // copy of the exemplar_ member, and initialize it with the
- // context-specific option list.
- //
- for (const auto& o: options_)
- {
- const context* c (o.first);
-
- if (c->server != nullptr && // Is a directory configuration context.
- c->handling == request_handling::allowed)
- {
- auto r (
- exemplars_.emplace (
- c,
- unique_ptr<handler> (new H (*exemplar))));
-
- r.first->second->init (o.second, l);
- }
- }
-
- // Options are not needed anymore. Free up the space.
- //
- options_.clear ();
- }
- catch (const exception& e)
- {
- l.write (nullptr, 0, func_name.c_str (), APLOG_EMERG, e.what ());
-
- // Terminate the worker apache process. APEXIT_CHILDSICK indicates to
- // the root process that the worker have exited due to a resource
- // shortage. In this case the root process limits the rate of forking
- // until situation is resolved.
- //
- // If the root process fails to create any worker process on startup,
- // the behaviour depends on the Multi-Processing Module enabled. For
- // mpm_worker_module and mpm_event_module the root process terminates.
- // For mpm_prefork_module it keeps trying to create the worker process
- // at one-second intervals.
- //
- // If the root process loses all it's workers while running (for
- // example due to the MaxRequestsPerChild directive), and fails to
- // create any new ones, it keeps trying to create the worker process
- // at one-second intervals.
- //
- exit (APEXIT_CHILDSICK);
- }
- catch (...)
- {
- l.write (nullptr,
- 0,
- func_name.c_str (),
- APLOG_EMERG,
- "unknown error");
-
- // Terminate the worker apache process.
- //
- exit (APEXIT_CHILDSICK);
- }
- }
-
- template <typename H>
- int service::
- request_handler (request_rec* r) noexcept
- {
- auto srv (instance<H> ());
- if (!r->handler || srv->name_ != r->handler) return DECLINED;
-
- assert (r->per_dir_config != nullptr);
-
- // Obtain the request-associated configuration context.
- //
- const context* cx (
- context_cast (ap_get_module_config (r->per_dir_config, srv)));
-
- assert (cx != nullptr);
-
- request rq (r);
- log lg (r->server, srv);
- return srv->template handle<H> (rq, cx, lg);
- }
-
- template <typename H>
- int service::
- handle (request& rq, const context* cx, log& lg) const
- {
- using namespace std;
-
- static const string func_name (
- "web::apache::service<" + name_ + ">::handle");
-
- try
- {
- auto i (exemplars_.find (cx));
- assert (i != exemplars_.end ());
-
- const H* e (dynamic_cast<const H*> (i->second.get ()));
- assert (e != nullptr);
-
- for (H h (*e);;)
- {
- try
- {
- if (static_cast<handler&> (h).handle (rq, rq, lg))
- return rq.flush ();
-
- if (rq.state () == request_state::initial)
- return DECLINED;
-
- lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR,
- "handling declined being partially executed");
- break;
- }
- catch (const handler::retry&)
- {
- // Retry to handle the request.
- //
- rq.rewind ();
- }
- }
- }
- catch (const invalid_request& e)
- {
- if (!e.content.empty () && rq.state () < request_state::writing)
- {
- try
- {
- rq.content (e.status, e.type) << e.content << endl;
- return rq.flush ();
- }
- catch (const exception& e)
- {
- lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ());
- }
- }
-
- return e.status;
- }
- catch (const exception& e)
- {
- lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ());
-
- if (*e.what () && rq.state () < request_state::writing)
- {
- try
- {
- rq.content (
- HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8")
- << e << endl;
-
- return rq.flush ();
- }
- catch (const exception& e)
- {
- lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ());
- }
- }
- }
- catch (...)
- {
- lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, "unknown error");
-
- if (rq.state () < request_state::writing)
- {
- try
- {
- rq.content (
- HTTP_INTERNAL_SERVER_ERROR, "text/plain;charset=utf-8")
- << "unknown error" << endl;
-
- return rq.flush ();
- }
- catch (const exception& e)
- {
- lg.write (nullptr, 0, func_name.c_str (), APLOG_ERR, e.what ());
- }
- }
- }
-
- return HTTP_INTERNAL_SERVER_ERROR;
- }
- }
-}
diff --git a/web/apache/stream.hxx b/web/apache/stream.hxx
deleted file mode 100644
index ed0018e..0000000
--- a/web/apache/stream.hxx
+++ /dev/null
@@ -1,148 +0,0 @@
-// file : web/apache/stream.hxx -*- C++ -*-
-// license : MIT; see accompanying LICENSE file
-
-#ifndef WEB_APACHE_STREAM_HXX
-#define WEB_APACHE_STREAM_HXX
-
-#include <httpd.h> // request_rec, HTTP_*
-#include <http_protocol.h> // ap_*()
-
-#include <ios> // streamsize
-#include <vector>
-#include <cstring> // memmove(), size_t
-#include <streambuf>
-#include <algorithm> // min(), max()
-
-#include <web/module.hxx> // invalid_request
-
-namespace web
-{
- namespace apache
- {
- // Object of a class implementing this interface is intended for keeping
- // the state of communication with the client.
- //
- struct stream_state
- {
- // Called by istreambuf functions when content is about to be read from
- // the client. Can throw invalid_request or sequence_error.
- //
- virtual void
- set_read_state () = 0;
-
- // Called by ostreambuf functions when some content is about to be
- // written to the client. Can throw invalid_request or sequence_error.
- //
- 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 rbuf
- {
- public:
- ostreambuf (request_rec* r, stream_state& s): rbuf (r, s) {}
-
- private:
- virtual int_type
- overflow (int_type c)
- {
- if (c != traits_type::eof ())
- {
- state_.set_write_state ();
-
- 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)
- {
- state_.set_write_state ();
-
- 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;
- }
- };
-
- class istreambuf: public rbuf
- {
- public:
- istreambuf (request_rec* r,
- stream_state& s,
- size_t bufsize = 1024,
- size_t putback = 1)
- : rbuf (r, s),
- bufsize_ (std::max (bufsize, (size_t)1)),
- putback_ (std::min (putback, bufsize_ - 1)),
- buf_ (bufsize_)
- {
- char* p (buf_.data () + putback_);
- setg (p, p, p);
- }
-
- protected:
- virtual int_type
- 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);
-
- char* p (buf_.data () + 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 ());
- }
-
- protected:
- size_t bufsize_;
- size_t putback_;
- std::vector<char> buf_;
- };
- }
-}
-
-#endif // WEB_APACHE_STREAM_HXX