aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--brep/module27
-rw-r--r--brep/module.cxx144
-rw-r--r--brep/search.cxx31
-rw-r--r--brep/view20
-rw-r--r--brep/view.cxx15
-rwxr-xr-xetc/apachectl170
-rw-r--r--etc/httpd.conf61
-rw-r--r--services.cxx8
-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
-rw-r--r--www/htdocs/index.html5
16 files changed, 1428 insertions, 56 deletions
diff --git a/brep/module b/brep/module
index 3f7b409..d976559 100644
--- a/brep/module
+++ b/brep/module
@@ -5,7 +5,9 @@
#ifndef BREP_MODULE
#define BREP_MODULE
+#include <string>
#include <utility> // move()
+#include <cstdint>
#include <web/module>
@@ -20,28 +22,14 @@ namespace brep
// web::module and our module. Or maybe not, need to try.
//
using web::status_code;
+ using web::invalid_request;
+ using web::sequence_error;
using web::name_value;
using web::name_values;
using web::request;
using web::response;
using web::log;
- // This exception is used to signal that the request is invalid
- // (4XX codes) rather than that it could not be processed (5XX).
- // By default 422 is returned, which means the request was
- // semantically invalid.
- //
- struct invalid_request
- {
- status_code status {422};
- std::string description;
-
- //@@ Maybe optional "try again" link?
- //
- invalid_request (std::string d, status_code s = 422)
- : status (s), description (std::move (d)) {}
- };
-
// And this exception indicated a server error (5XX). In particular,
// it is thrown by the fail diagnostics stream and is caught by the
// module implementation where it is both logged as an error and
@@ -105,6 +93,7 @@ namespace brep
//
protected:
module ();
+ module (const module& );
virtual void
handle (request&, response&, log&);
@@ -114,6 +103,12 @@ namespace brep
private:
log* log_ {nullptr}; // Diagnostics backend provided by the web server.
+ // Extract function name from a __PRETTY_FUNCTION__.
+ // Throw std::invalid_argument if fail to parse.
+ //
+ static std::string
+ func_name (const std::string& pretty_name);
+
void
log_write (diag_data&&) const;
diff --git a/brep/module.cxx b/brep/module.cxx
index 15e996f..17790de 100644
--- a/brep/module.cxx
+++ b/brep/module.cxx
@@ -4,7 +4,16 @@
#include <brep/module>
+#include <stdexcept>
+#include <string>
#include <functional> // bind()
+#include <cstring> // strncmp()
+
+#include <httpd/httpd.h>
+
+#include <web/module>
+
+#include <web/apache/log>
using namespace std;
using namespace placeholders; // For std::bind's _1, etc.
@@ -15,6 +24,7 @@ namespace brep
handle (request& rq, response& rs, log& l)
{
log_ = &l;
+ const basic_mark error (severity::error, log_writer_, __PRETTY_FUNCTION__);
try
{
@@ -22,39 +32,151 @@ namespace brep
}
catch (const invalid_request& e)
{
- // @@ Both log and format as HTML in proper style, etc.
- //
- rs.content (e.status, "text/html;charset=utf-8") << e.description;
+ if (e.description.empty ())
+ {
+ rs.status (e.status);
+ }
+ else
+ {
+ try
+ {
+ rs.content (e.status, "text/html;charset=utf-8") << e.description;
+ }
+ catch (const sequence_error& se)
+ {
+ error << se.what ();
+ rs.status (e.status);
+ }
+ }
}
catch (server_error& e) // Non-const because of move() below.
{
- // @@ Both log and return as 505.
- //
log_write (move (e.data));
+ rs.status (HTTP_INTERNAL_SERVER_ERROR);
}
catch (const exception& e)
{
- // @@ Exception: log e.what () & 505.
- //
- rs.status (505);
+ error << e.what ();
+ rs.status (HTTP_INTERNAL_SERVER_ERROR);
}
catch (...)
{
- // @@ Unknown exception: log & 505.
- //
- rs.status (505);
+ error << "unknown error";
+ rs.status (HTTP_INTERNAL_SERVER_ERROR);
}
}
module::
module (): log_writer_ (bind (&module::log_write, this, _1)) {}
+ // Custom copy constructor is required to initialize log_writer_ properly.
+ //
+ module::
+ module (const module& m): module () {verb_ = m.verb_;}
+
+// For function func declared like this:
+// using B = std::string (*)(int);
+// using A = B (*)(int,int);
+// A func(B (*)(char),B (*)(wchar_t));
+// __PRETTY_FUNCTION__ looks like this:
+// virtual std::string (* (* brep::search::func(std::string (* (*)(char))(int)\
+// ,std::string (* (*)(wchar_t))(int)) const)(int, int))(int)
+//
+ string module::
+ func_name (const string& pretty_name)
+ {
+ string::size_type b (0);
+ string::size_type e (pretty_name.find (' '));
+
+ // Position b at beginning of supposed function name,
+ //
+ if (e != string::npos && !strncmp (pretty_name.c_str (), "virtual ", 8))
+ {
+ // Skip keyword virtual.
+ //
+ b = pretty_name.find_first_not_of (' ', e);
+ e = pretty_name.find (' ', b);
+ }
+
+ if (pretty_name.find ('(', b) > e)
+ {
+ // Not a constructor nor destructor. Skip type or *.
+ //
+ b = pretty_name.find_first_not_of (' ', e);
+ }
+
+ if (b != string::npos)
+ {
+ // Position e at the last character of supposed function name.
+ //
+ e = pretty_name.find_last_of (')');
+
+ if (e != string::npos && e > b)
+ {
+ size_t d (1);
+
+ while (--e > b && d)
+ {
+ switch (pretty_name[e])
+ {
+ case ')': ++d; break;
+ case '(': --d; break;
+ }
+ }
+
+ if (!d)
+ {
+ return pretty_name[b] == '(' && pretty_name[e] == ')' ?
+ // Not a name yet, go deeper.
+ //
+ func_name (string(pretty_name, b + 1, e - b - 1)) :
+ // Got the name.
+ //
+ string (pretty_name, b, e - b + 1);
+ }
+ }
+ }
+
+ throw invalid_argument ("");
+ }
+
void module::
log_write (diag_data&& d) const
{
if (log_ == nullptr)
return; // No backend yet.
+ auto al = dynamic_cast<::web::apache::log*> (log_);
+
+ if (al)
+ {
+ // Considered using lambda for mapping but looks too verbose while can
+ // be a bit safer in runtime.
+ //
+ static int s[] = { APLOG_ERR, APLOG_WARNING, APLOG_INFO, APLOG_TRACE1 };
+
+ for (const auto& e : d)
+ {
+ string name;
+
+ try
+ {
+ name = func_name (e.name);
+ }
+ catch (const invalid_argument&)
+ {
+ // Log "pretty" function description, see in log file & fix.
+ name = e.name;
+ }
+
+ al->write (e.loc.file.c_str(),
+ e.loc.line,
+ name.c_str(),
+ s[static_cast<int> (e.sev)],
+ e.msg.c_str());
+ }
+ }
+
//@@ Cast log_ to apache::log and write the records.
//
diff --git a/brep/search.cxx b/brep/search.cxx
index 683016c..93690e4 100644
--- a/brep/search.cxx
+++ b/brep/search.cxx
@@ -5,6 +5,10 @@
#include <brep/search>
#include <ostream>
+#include <iostream>
+#include <chrono>
+
+#include <web/module>
using namespace std;
@@ -15,26 +19,39 @@ namespace brep
{
MODULE_DIAG;
+ chrono::seconds ma (60);
+ rs.cookie ("Oh", " Ah\n\n", &ma, "/");
+ rs.cookie ("Hm", ";Yes", &ma);
+
+ info << "handling search request from "; // << rq.client_ip ();
+
+ ostream& o (rs.content (200, "text/html;charset=utf-8", false));
+
+ o << "<html><head></head><body><b>Params:</b>";
+
const name_values& ps (rq.parameters ());
if (ps.empty ())
- throw invalid_request ("search parameters expected");
+ throw invalid_request (422, "search parameters expected");
if (ps.size () > 100)
fail << "too many parameters: " << ps.size () <<
info << "are you crazy to specify so many?";
- info << "handling search request from "; // << rq.client_ip ();
-
level2 ([&]{trace << "search request with " << ps.size () << " params";});
- ostream& o (rs.content (202, "text/html;charset=utf-8"));
+ for (const auto& p: ps)
+ {
+ o << "<br>\n" << p.name << "=" << p.value;
+ }
- o << "Search page:" << endl;
+ o << "<br>\n<b>Cookies:</b>";
- for (const name_value& p: ps)
+ for (const auto& c : rq.cookies ())
{
- o << p.name << "=" << p.value << endl;
+ o << "<br>\n" << c.name << "=" << c.value << " ";
}
+
+ o << "</body></html>";
}
}
diff --git a/brep/view b/brep/view
new file mode 100644
index 0000000..819eff3
--- /dev/null
+++ b/brep/view
@@ -0,0 +1,20 @@
+// file : brep/view -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BREP_VIEW
+#define BREP_VIEW
+
+#include <brep/module>
+
+namespace brep
+{
+ class view: public module
+ {
+ public:
+ virtual void
+ handle (request&, response&);
+ };
+}
+
+#endif // BREP_VIEW
diff --git a/brep/view.cxx b/brep/view.cxx
new file mode 100644
index 0000000..6dafa1b
--- /dev/null
+++ b/brep/view.cxx
@@ -0,0 +1,15 @@
+// file : brep/view.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#include <brep/view>
+
+using namespace std;
+
+namespace brep
+{
+ void view::
+ handle (request& rq, response& rs)
+ {
+ }
+}
diff --git a/etc/apachectl b/etc/apachectl
new file mode 100755
index 0000000..d9c1ee9
--- /dev/null
+++ b/etc/apachectl
@@ -0,0 +1,170 @@
+#!/bin/sh
+#
+# Copyright (c) 2000-2002 The Apache Software Foundation.
+# See license at the end of this file.
+#
+# Apache control script designed to allow an easy command line interface
+# to controlling Apache. Written by Marc Slemko, 1997/08/23
+#
+# The exit codes returned are:
+# XXX this doc is no longer correct now that the interesting
+# XXX functions are handled by httpd
+# 0 - operation completed successfully
+# 1 -
+# 2 - usage error
+# 3 - httpd could not be started
+# 4 - httpd could not be stopped
+# 5 - httpd could not be started during a restart
+# 6 - httpd could not be restarted during a restart
+# 7 - httpd could not be restarted during a graceful restart
+# 8 - configuration syntax error
+#
+# When multiple arguments are given, only the error from the _last_
+# one is reported. Run "apachectl help" for usage info
+
+ARGV="$@"
+#
+# |||||||||||||||||||| START CONFIGURATION SECTION ||||||||||||||||||||
+# -------------------- --------------------
+
+PORT=7180
+LOG_LEVEL=trace1
+ADMIN_EMAIL=admin@cppget.org
+
+# |||||||||||||||||||| END CONFIGURATION SECTION |||||||||||||||||||||||
+
+PROJECT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/..
+
+site_config="$PROJECT_DIR/etc"
+workspace="$PROJECT_DIR/var"
+www="$PROJECT_DIR/www"
+
+mkdir -p "$workspace"
+
+# the path to your httpd binary, including options if necessary
+
+HTTPD="/usr/sbin/httpd -d $workspace -f $site_config/httpd.conf"
+
+# a command that outputs a formatted text version of the HTML at the
+# url given on the command line. Designed for lynx, however other
+# programs may work.
+LYNX="lynx -dump"
+
+# the URL to your server's mod_status status page. If you do not
+# have one, then status and fullstatus will not work.
+STATUSURL="http://localhost:$PORT/server-status"
+
+# Set this variable to a command that increases the maximum
+# number of file descriptors allowed per child process. This is
+# critical for configurations that use many file descriptors,
+# such as mass vhosting, or a multithreaded server.
+ULIMIT_MAX_FILES="ulimit -S -n `ulimit -H -n`"
+# -------------------- --------------------
+# |||||||||||||||||||| END CONFIGURATION SECTION ||||||||||||||||||||
+
+# Set the maximum number of file descriptors allowed per child process.
+if [ "x$ULIMIT_MAX_FILES" != "x" ] ; then
+ $ULIMIT_MAX_FILES
+fi
+
+ERROR=0
+if [ "x$ARGV" = "x" ] ; then
+ ARGV="-h"
+fi
+
+case $ARGV in
+start)
+ $HTTPD -C "Listen $PORT" -C "ServerName cppget.org:$PORT" \
+ -C "DocumentRoot $www/htdocs" -C "CoreDumpDirectory $workspace" \
+ -C "PidFile $workspace/httpd.pid" \
+ -C "LogLevel $LOG_LEVEL" \
+ -C "LoadModule search_srv $PROJECT_DIR/libbrep.so" \
+ -C "LoadModule view_srv $PROJECT_DIR/libbrep.so" \
+ -C "ServerAdmin $ADMIN_EMAIL" \
+ -k $ARGV
+
+ ERROR=$?
+ ;;
+stop|restart|graceful)
+ $HTTPD -C "ServerName cppget.org:$PORT" \
+ -C "PidFile $workspace/httpd.pid" -k $ARGV
+ ERROR=$?
+ ;;
+startssl|sslstart|start-SSL)
+ $HTTPD -k start -DSSL
+ ERROR=$?
+ ;;
+configtest)
+ $HTTPD -t
+ ERROR=$?
+ ;;
+status)
+ $LYNX $STATUSURL | awk ' /process$/ { print; exit } { print } '
+ ;;
+fullstatus)
+ $LYNX $STATUSURL
+ ;;
+*)
+ $HTTPD $ARGV
+ ERROR=$?
+esac
+
+exit $ERROR
+
+# ====================================================================
+# The Apache Software License, Version 1.1
+#
+# Copyright (c) 2000-2003 The Apache Software Foundation. All rights
+# reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# 3. The end-user documentation included with the redistribution,
+# if any, must include the following acknowledgment:
+# "This product includes software developed by the
+# Apache Software Foundation (http://www.apache.org/)."
+# Alternately, this acknowledgment may appear in the software itself,
+# if and wherever such third-party acknowledgments normally appear.
+#
+# 4. The names "Apache" and "Apache Software Foundation" must
+# not be used to endorse or promote products derived from this
+# software without prior written permission. For written
+# permission, please contact apache@apache.org.
+#
+# 5. Products derived from this software may not be called "Apache",
+# nor may "Apache" appear in their name, without prior written
+# permission of the Apache Software Foundation.
+#
+# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+# ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+# ====================================================================
+#
+# This software consists of voluntary contributions made by many
+# individuals on behalf of the Apache Software Foundation. For more
+# information on the Apache Software Foundation, please see
+# <http://www.apache.org/>.
+#
+# Portions of this software are based upon public domain software
+# originally written at the National Center for Supercomputing Applications,
+# University of Illinois, Urbana-Champaign.
+#
diff --git a/etc/httpd.conf b/etc/httpd.conf
new file mode 100644
index 0000000..95b887d
--- /dev/null
+++ b/etc/httpd.conf
@@ -0,0 +1,61 @@
+User apache
+Group apache
+
+ErrorLog error_log
+#ErrorLog "|/usr/sbin/rotatelogs /Users/karen/projects/brep/var/error_log.%Y%m%d 86400"
+
+ErrorLogFormat "[%t] [%l] [%m] %M"
+
+Timeout 60
+
+KeepAlive On
+KeepAliveTimeout 3
+
+ThreadLimit 1000
+ServerLimit 2
+StartServers 1
+MaxClients 1000
+MinSpareThreads 400
+MaxSpareThreads 600
+ThreadsPerChild 500
+MaxRequestsPerChild 0
+
+LoadModule mpm_worker_module /usr/lib64/httpd/modules/mod_mpm_worker.so
+LoadModule unixd_module /usr/lib64/httpd/modules/mod_unixd.so
+LoadModule filter_module /usr/lib64/httpd/modules/mod_filter.so
+LoadModule access_compat_module /usr/lib64/httpd/modules/mod_access_compat.so
+LoadModule authn_core_module /usr/lib64/httpd/modules/mod_authn_core.so
+LoadModule authz_core_module /usr/lib64/httpd/modules/mod_authz_core.so
+LoadModule status_module /usr/lib64/httpd/modules/mod_status.so
+LoadModule mime_module /usr/lib64/httpd/modules/mod_mime.so
+LoadModule deflate_module /usr/lib64/httpd/modules/mod_deflate.so
+LoadModule authz_host_module /usr/lib64/httpd/modules/mod_authz_host.so
+LoadModule expires_module /usr/lib64/httpd/modules/mod_expires.so
+LoadModule dir_module /usr/lib64/httpd/modules/mod_dir.so
+
+TypesConfig /etc/mime.types
+
+<LocationMatch ^/search$>
+ SetHandler search
+</LocationMatch>
+
+<LocationMatch ^/view$>
+ SetHandler view
+</LocationMatch>
+
+DirectoryIndex index.html
+
+ExtendedStatus On
+
+<Location /server-status>
+ SetHandler server-status
+ Order deny,allow
+ Deny from all
+ Allow from 127.0.0
+ Allow from localhost
+</Location>
+
+<Directory />
+ Options FollowSymLinks
+ AllowOverride None
+</Directory>
diff --git a/services.cxx b/services.cxx
index 2345114..2da5bca 100644
--- a/services.cxx
+++ b/services.cxx
@@ -5,13 +5,13 @@
#include <web/apache/service>
#include <brep/search>
-//#include <brep/view>
+#include <brep/view>
using namespace brep;
using web::apache::service;
static const search search_mod;
-service /*AP_MODULE_DECLARE_DATA*/ search_srv ("search", search_mod);
+service AP_MODULE_DECLARE_DATA search_srv ("search", search_mod);
-//static const view view_mod;
-//service AP_MODULE_DECLARE_DATA view_srv ("view", view_mod);
+static const view view_mod;
+service AP_MODULE_DECLARE_DATA view_srv ("view", view_mod);
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
diff --git a/www/htdocs/index.html b/www/htdocs/index.html
new file mode 100644
index 0000000..3d6d51e
--- /dev/null
+++ b/www/htdocs/index.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <b>Home Page</b>
+ </body>
+</html>