Hello libcode-seafire-server

This commit is contained in:
G.H.O.S.T 2024-12-24 22:26:40 +01:00
commit ef1e544ff1
Signed by: G.H.O.S.T
GPG Key ID: 3BD93EABD1407B82
50 changed files with 3080 additions and 0 deletions

17
.editorconfig Normal file
View File

@ -0,0 +1,17 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
indent_size = 4
max_line_length = off
trim_trailing_whitespace = false
[*.yaml]
indent_size = 2

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto

View File

@ -0,0 +1,24 @@
name: on-push
on: [push]
jobs:
build-and-test:
runs-on: linux
container: code.helloryan.se/infra/buildenv/cxx-amd64-fedora-40:latest
volumes:
- /build
steps:
- name: Clone repository
uses: actions/checkout@v3
- name: Authenticate
run: |
git config unset http.https://code.helloryan.se/.extraheader
echo "${{ secrets.NETRC }}" >> ~/.netrc
- name: Initialize
run: |
bpkg create -d /build cc config.cc.coptions="-Wall -Werror"
bdep init -A /build
- name: Build
run: b
- name: Test
run: b test

32
.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
.bdep/
Doxyfile
# Local default options files.
#
.build2/local/
# Compiler/linker output.
#
*.d
*.t
*.i
*.i.*
*.ii
*.ii.*
*.o
*.obj
*.gcm
*.pcm
*.ifc
*.so
*.dylib
*.dll
*.a
*.lib
*.exp
*.pdb
*.ilk
*.exe
*.exe.dlls/
*.exe.manifest
*.pc

31
LICENSE Normal file
View File

@ -0,0 +1,31 @@
Copyright © 2024 Ryan. 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. All advertising materials mentioning features or use of this software must
display the following acknowledgement:
This product includes software developed by Ryan, http://helloryan.se/.
4. Neither the name(s) of the copyright holder(s) nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS 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 COPYRIGHT HOLDER 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.

21
README.md Normal file
View File

@ -0,0 +1,21 @@
# libcode-seafire-server
![Build status](https://code.helloryan.se/code/libcode-seafire-server/actions/workflows/on-push.yaml/badge.svg)
## Requirements
None, other than a modern C++-compiler.
## Building
See the wiki, https://code.helloryan.se/code/wiki/wiki/Build-Instructions, for
build instructions.
## Contact
Please report bugs and issues by sending an e-mail to: ryan@helloryan.se.
## Contributing
Please send an e-mail to ryan@helloryan.se to request an account and
write-access to the libcode-seafire-server repository.

4
build/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

7
build/bootstrap.build Normal file
View File

@ -0,0 +1,7 @@
project = libcode-seafire-server
using version
using config
using test
using install
using dist

6
build/export.build Normal file
View File

@ -0,0 +1,6 @@
$out_root/
{
include code/seafire/server/
}
export $out_root/code/seafire/server/$import.target

16
build/root.build Normal file
View File

@ -0,0 +1,16 @@
# Uncomment to suppress warnings coming from external libraries.
#
#cxx.internal.scope = current
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

5
buildfile Normal file
View File

@ -0,0 +1,5 @@
./: {code/ tests/} doc{README.md} legal{LICENSE} manifest
# Don't install tests.
#
tests/: install = false

9
code/seafire/server/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
# Generated version header.
#
version.hxx
# Unit test executables and Testscript output directories
# (can be symlinks).
#
*.test
test-*.test

View File

@ -0,0 +1,68 @@
intf_libs = # Interface dependencies.
impl_libs = # Implementation dependencies.
import intf_libs =+ libasio%lib{asio}
import intf_libs =+ libcode-uri%lib{code-uri}
import intf_libs =+ libcode-seafire-common%lib{code-seafire-common}
import intf_libs =+ libcode-seafire-protocol%lib{code-seafire-protocol}
./: lib{code-seafire-server}: libul{code-seafire-server}
libul{code-seafire-server}: {hxx ixx txx cxx}{** -**.test... -version} \
{hxx }{ version}
libul{code-seafire-server}: $impl_libs $intf_libs
# Unit tests.
#
exe{*.test}:
{
test = true
install = false
}
for t: cxx{**.test...}
{
d = $directory($t)
n = $name($t)...
./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n}
$d/exe{$n}: libul{code-seafire-server}: bin.whole = false
}
hxx{version}: in{version} $src_root/manifest
{
dist = true
clean = ($src_root != $out_root)
}
# Build options.
#
cxx.poptions =+ "-I$out_root" "-I$src_root"
cxx.coptions =+ -fvisibility=hidden
# Export options.
#
lib{code-seafire-server}:
{
cxx.export.poptions = "-I$out_root" "-I$src_root"
cxx.export.libs = $intf_libs
}
# For pre-releases use the complete version to make sure they cannot
# be used in place of another pre-release or the final version. See
# the version module for details on the version.* variable values.
#
if $version.pre_release
lib{code-seafire-server}: bin.lib.version = "-$version.project_id"
else
lib{code-seafire-server}: bin.lib.version = "-$version.major.$version.minor"
# Install into the code/seafire/server/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
install = include/code/seafire/server/
install.subdirs = true
}

View File

@ -0,0 +1,52 @@
#ifndef code__seafire__server__common_error_hxx_
#define code__seafire__server__common_error_hxx_
namespace code::seafire::server
{
enum class common_error_t
{
// RCF 7231.
//
bad_request,
payment_required,
forbidden,
not_found,
method_not_allowed,
not_acceptable,
request_timeout,
conflict,
gone,
length_required,
payload_too_large,
uri_too_long,
unsupported_media_type,
expectation_failed,
upgrade_required,
internal_server_error,
not_implemented,
bad_gateway,
service_unavailable,
gateway_timeout,
http_version_not_supported,
// RFC 7232.
//
not_modified,
precondition_failed,
// RFC 7235.
//
unauthorized,
proxy_auth_required,
// Unofficial.
//
enhance_your_calm
};
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,21 @@
#ifndef code__seafire__server__configuration_hxx_
#define code__seafire__server__configuration_hxx_
#include <chrono>
namespace code::seafire::server
{
/// Holds server configuration parameters.
///
struct configuration_t
{
/// The timeout of a transaction.
///
std::chrono::seconds request_timeout;
};
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,33 @@
#ifndef code__seafire__server__counter_hxx_
#define code__seafire__server__counter_hxx_
#include <atomic>
#include <cstdint>
namespace code::seafire::server
{
template<typename T>
class atomic_t
: public std::atomic<T>
{
public:
atomic_t()
: std::atomic<T>{0}
{}
atomic_t(atomic_t<T> const& other)
: std::atomic<T>{other.load()}
{}
atomic_t(atomic_t<T>&& other)
: std::atomic<T>{other.load()}
{}
};
using counter_t = atomic_t<std::uint64_t>;
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,41 @@
#include <code/seafire/server/diagnostics.hxx>
namespace code::seafire::server
{
common::diagnostics_t::category_t const&
server_category()
{
static common::diagnostics_t::category_t category{"server"};
return category;
}
common::diagnostics_t::category_t const&
supervisor_category()
{
static common::diagnostics_t::category_t category{"supervisor"};
return category;
}
common::diagnostics_t::category_t const&
session_category()
{
static common::diagnostics_t::category_t category{"session"};
return category;
}
common::diagnostics_t::category_t const&
transaction_category()
{
static common::diagnostics_t::category_t category{"transaction"};
return category;
}
common::diagnostics_t::category_t const&
request_category()
{
static common::diagnostics_t::category_t category{"request"};
return category;
}
} // namespace code::seafire::server

View File

@ -0,0 +1,26 @@
#ifndef code__seafire__server__diagnostics_hxx_
#define code__seafire__server__diagnostics_hxx_
#include <code/seafire/common/diagnostics.hxx>
namespace code::seafire::server
{
common::diagnostics_t::category_t const&
server_category();
common::diagnostics_t::category_t const&
supervisor_category();
common::diagnostics_t::category_t const&
session_category();
common::diagnostics_t::category_t const&
transaction_category();
common::diagnostics_t::category_t const&
request_category();
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,12 @@
#include <code/seafire/server/error-handler.hxx>
namespace code::seafire::server
{
error_handler_t::
error_handler_t() = default;
error_handler_t::
~error_handler_t() noexcept = default;
} // namespace code::seafire::serverk

View File

@ -0,0 +1,38 @@
#ifndef code__seafire__server__error_handler_hxx_
#define code__seafire__server__error_handler_hxx_
#include <code/seafire/server/common-error.hxx>
namespace code::seafire::server
{
class request_t;
class response_t;
class error_handler_t
{
public:
virtual
void
on_error(request_t&, response_t&, common_error_t) = 0;
virtual
void
on_exception(request_t&, response_t&) noexcept = 0;
protected:
error_handler_t();
error_handler_t(error_handler_t const&) = delete;
error_handler_t(error_handler_t&&) = delete;
~error_handler_t() noexcept;
error_handler_t& operator=(error_handler_t const&) = delete;
error_handler_t& operator=(error_handler_t&&) = delete;
};
}
#endif

View File

@ -0,0 +1,44 @@
#include <code/seafire/server/middleware.hxx>
namespace code::seafire::server
{
void
middleware_t::
invoke(request_t& req, response_t& res, request_handler_t const& next) const
{
handler_->invoke(req, res, next);
}
middleware_t::
middleware_t(std::shared_ptr<concept_t const> handler)
: handler_{handler}
{}
request_handler_t
make_middleware(std::vector<middleware_t> const& chain,
request_handler_t handler)
{
struct handler_t
{
middleware_t middleware;
request_handler_t next;
void
operator()(request_t& req, response_t& res) const
{
middleware.invoke(req, res, next);
}
};
request_handler_t next{std::move(handler)};
for (auto it = chain.rbegin(); it != chain.rend(); ++it) {
next = handler_t{*it, next};
}
return next;
}
} // namespace code::seafire::server

View File

@ -0,0 +1,84 @@
#ifndef code__seafire__server__middleware_hxx_
#define code__seafire__server__middleware_hxx_
#include <code/seafire/server/request-handler.hxx>
#include <memory>
#include <vector>
namespace code::seafire::server
{
/// Implements middleware functionality.
///
class middleware_t
{
public:
template<typename T>
middleware_t(T handler)
: handler_{std::make_shared<container_t<T>>(std::move(handler))}
{}
void
invoke(request_t&, response_t&, request_handler_t const&) const;
template<typename, typename... Args>
friend
middleware_t
make_middleware(Args&&...);
private:
struct concept_t
{
virtual
~concept_t() noexcept = default;
virtual
void
invoke(request_t&, response_t&, request_handler_t const&) const = 0;
};
template<typename T>
struct container_t
: concept_t
{
template<typename... Args>
container_t(Args&&... args)
: handler{std::forward<Args>(args)...}
{}
void
invoke(request_t& req,
response_t& res,
request_handler_t const& next) const override
{
handler(req, res, next);
}
T handler;
};
explicit
middleware_t(std::shared_ptr<concept_t const>);
std::shared_ptr<concept_t const> handler_;
};
template<typename T, typename... Args>
middleware_t
make_middleware(Args&&... args)
{
return middleware_t{
std::make_shared<middleware_t::container_t<T>>(std::forward<Args>(args)...)
};
}
request_handler_t
make_middleware(std::vector<middleware_t> const&, request_handler_t);
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,45 @@
#include <code/seafire/server/parameters.hxx>
namespace code::seafire::server
{
std::optional<string_parameter_t::value_type>
string_parameter_t::
try_parse(std::optional<std::string> const& input)
{
return input;
}
std::optional<int_parameter_t::value_type>
int_parameter_t::
try_parse(std::optional<std::string> const& input)
{
try {
if (input) {
return std::stoll(*input);
}
return std::nullopt;
}
catch (...) {
return std::nullopt;
}
}
std::optional<uint_parameter_t::value_type>
uint_parameter_t::
try_parse(std::optional<std::string> const& input)
{
try {
if (input) {
return std::stoull(*input);
}
return std::nullopt;
}
catch (...) {
return std::nullopt;
}
}
} // namespace code::seafire::server

View File

@ -0,0 +1,62 @@
#ifndef code__seafire__server__parameters_hxx_
#define code__seafire__server__parameters_hxx_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
namespace code::seafire::server
{
template<std::size_t N>
struct parameter_name_t
{
constexpr parameter_name_t(const char (&str)[N])
{
std::copy_n(str, N, name);
}
operator std::string const() const
{
return name;
}
char name[N];
};
struct string_parameter_t
{
using value_type = std::string;
static
std::optional<value_type>
try_parse(std::optional<std::string> const&);
};
struct int_parameter_t
{
using value_type = std::int64_t;
static
std::optional<value_type>
try_parse(std::optional<std::string> const&);
};
struct uint_parameter_t
{
using value_type = std::uint64_t;
static
std::optional<value_type>
try_parse(std::optional<std::string> const&);
};
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,64 @@
#ifndef code__seafire__server__query_parameter_hxx_
#define code__seafire__server__query_parameter_hxx_
#include <code/seafire/server/parameters.hxx>
#include <code/seafire/server/query-parameters.hxx>
#include <code/seafire/server/request.hxx>
#include <optional>
namespace code::seafire::server
{
template<parameter_name_t Name, typename ParameterType = string_parameter_t>
class query_parameter_t
{
public:
using parameter_type = ParameterType;
using value_type = typename parameter_type::value_type;
static
std::string const&
name()
{
static std::string const name{Name};
return name;
}
query_parameter_t(std::optional<value_type> value)
: value_{std::move(value)}
{}
std::optional<value_type> const&
value() const
{
return value_;
}
operator std::optional<value_type> const&() const
{
return value();
}
std::optional<value_type> const*
operator->() const
{
return &value();
}
static
query_parameter_t<Name, ParameterType>
fetch(request_t& r)
{
auto value = r.extensions().use<query_parameters_t>().get(name());
return std::optional<value_type>{parameter_type::try_parse(value)};
}
private:
std::optional<value_type> value_;
};
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,61 @@
#include <code/seafire/server/query-parameters.hxx>
namespace code::seafire::server
{
query_parameters_t::
query_parameters_t(std::map<std::string, std::string> keys)
: keys_{std::move(keys)}
{}
std::optional<std::string>
query_parameters_t::
get(std::string const& key) const
{
if (auto it = keys_.find(key); it != keys_.end())
return it->second;
return std::nullopt;
}
std::optional<query_parameters_t>
query_parameters_t::
try_parse(std::string const& q)
{
std::map<std::string, std::string> keys;
for (auto it = q.begin(); it != q.end();) {
auto k = it;
while (it != q.end() && *it != '=' && *it != '&') {
++it;
}
auto kend = it;
if (it != q.end() && *it == '=') {
++it; // skips '='
}
auto v = it;
while (it != q.end() && *it != '&') {
++it;
}
auto vend = it;
if (it != q.end() && *it == '&') {
++it; // skips '&'
}
std::string key{k, kend};
std::string value{v, vend};
keys.emplace(std::move(key), std::move(value));
}
return keys;
}
} // namespace code::seafire::server

View File

@ -0,0 +1,30 @@
#ifndef code__seafire__server__query_parameters_hxx_
#define code__seafire__server__query_parameters_hxx_
#include <map>
#include <optional>
#include <string>
namespace code::seafire::server
{
class query_parameters_t
{
public:
query_parameters_t(std::map<std::string, std::string>);
std::optional<std::string>
get(std::string const&) const;
static
std::optional<query_parameters_t>
try_parse(std::string const&);
private:
std::map<std::string, std::string> keys_;
};
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,13 @@
#include <code/seafire/server/request-handler.hxx>
namespace code::seafire::server
{
void
request_handler_t::
invoke(request_t& req, response_t& res) const
{
handler_->invoke(req, res);
}
} // namespace code::seafire::server

View File

@ -0,0 +1,82 @@
#ifndef code__seafire__server__request_handler_hxx_
#define code__seafire__server__request_handler_hxx_
#include <memory>
namespace code::seafire::server
{
class request_t;
class response_t;
class request_handler_t
{
public:
template<typename Handler>
request_handler_t(Handler handler)
: handler_{
std::make_shared<container_t<Handler>>(
std::move(handler)
)
}
{}
void
invoke(request_t& req, response_t& res) const;
template<typename Handler, typename... Args>
friend
request_handler_t
make_request_handler(Args&&...);
private:
struct concept_t
{
virtual
~concept_t() noexcept = default;
virtual
void
invoke(request_t&, response_t&) const = 0;
};
template<typename Handler>
struct container_t
: concept_t
{
template<typename... Args>
container_t(Args&&... args)
: handler_{std::forward<Args>(args)...}
{}
void
invoke(request_t& req, response_t& res) const override
{
handler_(req, res);
}
Handler handler_;
};
request_handler_t(std::shared_ptr<concept_t> handler)
: handler_{std::move(handler)}
{}
// Request handlers are shared for performance reasons and hence const.
//
std::shared_ptr<concept_t const> handler_;
};
template<typename Handler, typename... Args>
request_handler_t
make_request_handler(Args&&... args)
{
using container_t = request_handler_t::container_t<Handler>;
return std::make_shared<container_t>(std::forward<Args>(args)...);
}
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,35 @@
#include <code/seafire/server/request.hxx>
#include <code/seafire/protocol/rfc7230/connection.hxx>
namespace code::seafire::server
{
protocol::request_t const&
request_t::
get_message() const
{
return get_request();
}
std::istream&
request_t::
content()
{
return get_request_content();
}
common::extension_context_t&
request_t::
extensions()
{
return get_request_extensions();
}
request_t::
request_t() = default;
request_t::
~request_t() noexcept = default;
} // namespace code::seafire::server

View File

@ -0,0 +1,83 @@
#ifndef code__seafire__server__request_hxx_
#define code__seafire__server__request_hxx_
#include <code/seafire/protocol/request.hxx>
#include <code/seafire/common/allocator.hxx>
#include <code/seafire/common/extension-context.hxx>
#include <code/seafire/common/io/buffer.hxx>
#include <asio.hpp>
namespace code::seafire::server
{
/// Represents a server-side request.
///
class request_t
{
public:
protocol::request_t const&
get_message() const;
std::istream&
content();
// fixme: replace with non-virtual function and virtual
// get_request_allocator function.
//
virtual
common::allocator_t&
memory() = 0;
common::extension_context_t&
extensions();
protected:
request_t();
request_t(request_t const&) = delete;
request_t(request_t&&) = delete;
~request_t() noexcept;
request_t& operator=(request_t const&) = delete;
request_t& operator=(request_t&&) = delete;
private:
virtual
protocol::request_t const&
get_request() const = 0;
virtual
std::istream&
get_request_content() = 0;
virtual
common::extension_context_t&
get_request_extensions() = 0;
};
template<typename Header>
bool
has(request_t const&);
template<typename Header>
bool
has_quick(request_t const&);
template<typename Header>
auto
get(request_t const&);
template<typename Header>
auto
get(request_t const&, std::error_code&);
} // namespace code::seafire::server
#include <code/seafire/server/request.txx>
#endif

View File

@ -0,0 +1,32 @@
namespace code::seafire::server
{
template<typename Header>
bool
has(request_t const& r)
{
return protocol::has<Header>(r.get_message());
}
template<typename Header>
bool
has_quick(request_t const& r)
{
return protocol::has_quick<Header>(r.get_message());
}
template<typename Header>
auto
get(request_t const& r)
{
return protocol::get<Header>(r.get_message());
}
template<typename Header>
auto
get(request_t const& r, std::error_code& ec)
{
return protocol::get<Header>(r.get_message(), ec);
}
} // namespace code::seafire::server

View File

@ -0,0 +1,146 @@
#include <code/seafire/server/response.hxx>
namespace code::seafire::server
{
/// Access the response message.
///
protocol::response_t&
response_t::
get_message()
{
return get_response();
}
/// Access the response message.
///
protocol::response_t const&
response_t::
get_message() const
{
return get_response();
}
/// Send response.
///
void
response_t::
send(protocol::status_code_t s)
{
send(s, common::io::const_buffers_t{});
}
/// Send response.
///
void
response_t::
send(protocol::status_code_t s, common::io::const_buffer_t const& content)
{
send(s, common::io::const_buffers_t{content});
}
/// Send response.
///
void
response_t::
send(protocol::status_code_t s, common::io::const_buffers_t const& content)
{
do_send_response(s, content);
}
/// Send response.
///
void
response_t::
send(protocol::status_code_t s, stream_t const& content)
{
send(s, content.rdbuf()->data());
}
/// Send error.
///
void
response_t::
send(common_error_t error)
{
do_send_error(error);
}
/// Allocate a new stream.
///
response_t::stream_t
response_t::
allocate_stream()
{
return stream_t{&memory().alloc_emplace<asio::streambuf>()};
}
/// Access response extensions.
///
common::extension_context_t&
response_t::
extensions()
{
return get_response_extensions();
}
void
response_t::
invoke_finalizer(finalizer_t* f, request_t& r)
{
if (f) {
f->invoke(r, *this);
}
}
response_t::
response_t() = default;
response_t::
~response_t() noexcept = default;
asio::streambuf*
response_t::stream_t::
rdbuf() const
{
return rdbuf_;
}
std::size_t
response_t::stream_t::
size() const
{
return rdbuf_->data().size();
}
response_t::stream_t::
stream_t(asio::streambuf* rdbuf)
: std::iostream{rdbuf ? rdbuf : throw std::invalid_argument{"buf"}},
rdbuf_{rdbuf}
{}
/// Construct a new finalizer.
///
response_t::finalizer_t::
finalizer_t(response_t& r, function_t f)
: response_{r}, f_{f}
{
response_.register_finalizer(this);
}
/// Destroy this finalizer.
///
response_t::finalizer_t::
~finalizer_t() noexcept
{
response_.deregister_finalizer(this);
}
void
response_t::finalizer_t::
invoke(request_t& req, response_t& res)
{
f_(req, res);
}
} // namespace code::seafire::server

View File

@ -0,0 +1,209 @@
#ifndef code__seafire__server__response_hxx_
#define code__seafire__server__response_hxx_
#include <code/seafire/server/common-error.hxx>
#include <code/seafire/protocol/response.hxx>
#include <code/seafire/protocol/status-code.hxx>
#include <code/seafire/common/allocator.hxx>
#include <code/seafire/common/extension-context.hxx>
#include <asio.hpp>
#include <functional>
#include <iostream>
namespace code::seafire::server
{
class request_t;
/// Represents a server-side response.
///
class response_t
{
public:
class stream_t;
class finalizer_t;
friend finalizer_t;
protocol::response_t&
get_message();
protocol::response_t const&
get_message() const;
void
send(protocol::status_code_t);
void
send(protocol::status_code_t, common::io::const_buffer_t const&);
void
send(protocol::status_code_t, common::io::const_buffers_t const&);
void
send(protocol::status_code_t, stream_t const&);
void
send(common_error_t);
stream_t
allocate_stream();
// fixme: replace with non-virtual function and virtual
// get_response_allocator function.
//
virtual
common::allocator_t&
memory() = 0;
common::extension_context_t&
extensions();
protected:
response_t();
response_t(response_t const&) = delete;
response_t(response_t&&) = delete;
~response_t() noexcept;
virtual
void
register_finalizer(finalizer_t*) = 0;
virtual
void
deregister_finalizer(finalizer_t*) = 0;
void
invoke_finalizer(finalizer_t*, request_t&);
response_t& operator=(response_t const&) = delete;
response_t& operator=(response_t&&) = delete;
private:
virtual
protocol::response_t&
get_response() = 0;
virtual
protocol::response_t const&
get_response() const = 0;
virtual
common::extension_context_t&
get_response_extensions() = 0;
virtual
void
do_send_response(protocol::status_code_t,
common::io::const_buffers_t const&) = 0;
virtual
void
do_send_error(common_error_t) = 0;
};
template<typename Header>
bool
has(response_t const& r)
{
return protocol::has<Header>(r.get_message());
}
template<typename Header>
bool
has_quick(response_t const& r)
{
return protocol::has_quick<Header>(r.get_message());
}
template<typename Header>
auto
get(response_t const& r)
{
return protocol::get<Header>(r.get_message());
}
template<typename Header>
auto
get(response_t const& r, std::error_code& ec)
{
return protocol::get<Header>(r.get_message(), ec);
}
template<typename Header, typename... Args>
void
set(response_t& r, Args&&... args)
{
protocol::set<Header>(r.get_message(), std::forward<Args>(args)...);
}
template<typename Header>
void
erase(response_t& r)
{
protocol::erase<Header>(r.get_message());
}
template<typename Header, typename... Args>
void
set_if_not_set(response_t& r, Args&&... args)
{
protocol::set_if_not_set<Header>(r.get_message(), std::forward<Args>(args)...);
}
class response_t::stream_t
: public std::iostream
{
public:
asio::streambuf*
rdbuf() const;
std::size_t
size() const;
private:
friend response_t;
explicit
stream_t(asio::streambuf*);
asio::streambuf* rdbuf_;
};
class response_t::finalizer_t
{
public:
using function_t = std::function<void(request_t&, response_t&)>;
finalizer_t(response_t&, function_t);
finalizer_t(finalizer_t const&) = delete;
finalizer_t(finalizer_t&&) = delete;
~finalizer_t() noexcept;
finalizer_t& operator=(finalizer_t const&) = delete;
finalizer_t& operator=(finalizer_t&&) = delete;
private:
friend response_t;
void
invoke(request_t&, response_t&);
response_t& response_;
function_t f_;
};
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,331 @@
#include <code/seafire/server/diagnostics.hxx>
#include <code/seafire/server/response.hxx>
#include <code/seafire/server/server.hxx>
#include <code/seafire/protocol/rfc7231/content-type.hxx>
#include <code/seafire/common/io/buffer.hxx>
#include <code/seafire/common/io/error.hxx>
#include <asio.hpp>
namespace code::seafire::server
{
/// Construct a new HTTP server using the specified acceptors
/// and request handler.
///
server_t::
server_t(common::diagnostics_t& diagnostics,
configuration_t configuration,
acceptor_set_t acceptors,
request_handler_t handler)
: diagnostics_{diagnostics},
configuration_{configuration},
acceptors_{std::move(acceptors)},
handler_{handler},
supervisor_{diagnostics_, *this, handler_}
{}
void
server_t::
start()
{
trace() << "starting seafire server...";
// fixme: add tracing.
//
for (auto const& j : acceptors_) {
init_accept(*j);
}
}
void
server_t::
stop(bool quick)
{
trace() << "seafire server stop requested...";
if (quick) {
supervisor_.stop_all();
}
}
void
server_t::
on_error(request_t& req, response_t& res, common_error_t error)
{
trace() << "on_error()...";
namespace rfc7231 = protocol::rfc7231;
switch (error) {
case common_error_t::bad_request: {
static std::string const message{"Bad request\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(400, common::io::buffer(message));
return;
}
case common_error_t::payment_required: {
static std::string const message{"Payment required\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(402, common::io::buffer(message));
return;
}
case common_error_t::forbidden: {
static std::string const message{"Forbidden\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(403, common::io::buffer(message));
return;
}
case common_error_t::not_found: {
static std::string const message{"Not found\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(404, common::io::buffer(message));
return;
}
case common_error_t::method_not_allowed: {
static std::string const message{"Method not allowed\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(405, common::io::buffer(message));
return;
}
case common_error_t::not_acceptable: {
static std::string const message{"Not acceptable\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(406, common::io::buffer(message));
return;
}
case common_error_t::request_timeout: {
static std::string const message{"Request timeout\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(408, common::io::buffer(message));
return;
}
case common_error_t::conflict: {
static std::string const message{"Conflict\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(409, common::io::buffer(message));
return;
}
case common_error_t::gone: {
static std::string const message{"Gone\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(410, common::io::buffer(message));
return;
}
case common_error_t::length_required: {
static std::string const message{"Length required\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(411, common::io::buffer(message));
return;
}
case common_error_t::payload_too_large: {
static std::string const message{"Payload too large\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(413, common::io::buffer(message));
return;
}
case common_error_t::uri_too_long: {
static std::string const message{"Target URI too long\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(414, common::io::buffer(message));
return;
}
case common_error_t::unsupported_media_type: {
static std::string const message{"Unsupported media type\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(415, common::io::buffer(message));
return;
}
case common_error_t::expectation_failed: {
static std::string const message{"Expectation failed\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(417, common::io::buffer(message));
return;
}
case common_error_t::upgrade_required: {
static std::string const message{"Upgrade required\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(426, common::io::buffer(message));
return;
}
case common_error_t::internal_server_error: {
static std::string const message{"Internal server error\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(500, common::io::buffer(message));
return;
}
case common_error_t::not_implemented: {
static std::string const message{"Not implemented\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(501, common::io::buffer(message));
return;
}
case common_error_t::bad_gateway: {
static std::string const message{"Bad gateway\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(502, common::io::buffer(message));
return;
}
case common_error_t::service_unavailable: {
static std::string const message{"Service unavailable\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(503, common::io::buffer(message));
return;
}
case common_error_t::gateway_timeout: {
static std::string const message{"Gateway timeout\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(504, common::io::buffer(message));
return;
}
case common_error_t::http_version_not_supported: {
static std::string const message{"HTTP version not supported\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(505, common::io::buffer(message));
return;
}
// rfc 7232
//
case common_error_t::not_modified: {
static std::string const message{"Not modified\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(304, common::io::buffer(message));
return;
}
case common_error_t::precondition_failed: {
static std::string const message{"Precondition failed\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(412, common::io::buffer(message));
return;
}
// rfc 7235
//
case common_error_t::unauthorized: {
static std::string const message{"Unauthorized\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(401, common::io::buffer(message));
return;
}
case common_error_t::proxy_auth_required: {
static std::string const message{"Proxy authentication required\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(407, common::io::buffer(message));
return;
}
// Unofficial.
//
case common_error_t::enhance_your_calm: {
static std::string const message{"Enhance your calm\n"};
set<rfc7231::content_type_t>(res, "text", "plain");
res.send(420, common::io::buffer(message));
return;
}
}
throw std::invalid_argument{"invalid common error"};
}
void
server_t::
on_exception(request_t&, response_t&) noexcept
{
trace() << "on_exception()...";
}
common::diagnostics_t::proxy_t
server_t::
trace()
{
return diagnostics_ << server_category();
}
void
server_t::
init_accept(common::io::acceptor_t& acceptor)
{
trace() << "init_accept()...";
auto bound = [this, &acceptor](std::error_code const& ec)
{
on_accept(acceptor, ec);
};
supervisor_.async_accept(acceptor, bound);
}
void
server_t::
on_accept(common::io::acceptor_t& acceptor, std::error_code const& ec)
{
trace() << "on_accept()...";
if (!ec) {
// no error.
//
init_accept(acceptor);
}
else if (ec != asio::error::operation_aborted) {
// fixme: handle error accordingly.
//
}
}
} // namespace code::seafire::server

View File

@ -0,0 +1,75 @@
#ifndef code__seafire__server__hxx_
#define code__seafire__server__hxx_
#include <code/seafire/server/common-error.hxx>
#include <code/seafire/server/configuration.hxx>
#include <code/seafire/server/counter.hxx>
#include <code/seafire/server/request-handler.hxx>
#include <code/seafire/server/supervisor.hxx>
#include <code/seafire/common/diagnostics.hxx>
#include <code/seafire/common/io/acceptor.hxx>
namespace code::seafire::server
{
class request_t;
class response_t;
class transaction_t;
class server_t
: public error_handler_t
{
public:
/// Acceptor set type.
///
using acceptor_set_t = std::set<std::unique_ptr<common::io::acceptor_t>>;
server_t(common::diagnostics_t&,
configuration_t,
acceptor_set_t,
request_handler_t);
server_t(server_t const&) = delete;
server_t(server_t&&) = delete;
void
start();
void
stop(bool = false);
server_t& operator=(server_t const&) = delete;
server_t& operator=(server_t&&) = delete;
protected:
void
on_error(request_t&, response_t&, common_error_t) override;
void
on_exception(request_t&, response_t&) noexcept override;
// fixme: void shutdown_stream();
private:
common::diagnostics_t::proxy_t
trace();
void
init_accept(common::io::acceptor_t&);
void
on_accept(common::io::acceptor_t&, std::error_code const&);
common::diagnostics_t& diagnostics_;
configuration_t configuration_;
acceptor_set_t const acceptors_;
request_handler_t handler_;
supervisor_t supervisor_;
};
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,154 @@
#include <code/seafire/server/session.hxx>
#include <code/seafire/server/diagnostics.hxx>
#include <code/seafire/server/supervisor.hxx>
#include <code/seafire/server/transaction.hxx>
#include <code/seafire/common/io/error.hxx>
#include <asio.hpp>
namespace code::seafire::server
{
session_t::
~session_t() noexcept
{
trace() << "~session_t()...";
}
supervisor_t&
session_t::
owner()
{
return owner_;
}
session_t::stats_t
session_t::
stats() const
{
return stats_;
}
void
session_t::
start()
{
trace() << "start()...";
init_transaction();
}
void
session_t::
stop()
{
// fixme: should we really ignore the error code at this point?
//
std::error_code ignored_ec;
stream_->close(ignored_ec);
}
session_t::
session_t(common::diagnostics_t& diagnostics,
error_handler_t& error_handler,
supervisor_t& owner,
std::unique_ptr<common::io::stream_t> stream,
request_handler_t& handler)
: diagnostics_{diagnostics},
error_handler_{error_handler},
owner_{owner},
stream_{std::move(stream)},
handler_{handler},
connection_{*stream_, 1024*1024} // FIXME: magic number.
{
trace() << "session()...";
}
void
session_t::
init_transaction()
{
trace() << "init_transaction()";
auto self = shared_from_this();
auto bound = [this, self](std::error_code const& ec,
transaction_t::result_t result)
{
on_tx_complete(ec, result == transaction_t::result_t::complete_closed);
};
// fixme: This will potentially destroy the previous transaction.
// Should we detect this and throw? Shouldn't ever happen...
//
current_tx_ = make_transaction(diagnostics_,
std::chrono::seconds{0},
error_handler_,
connection_,
handler_,
bound);
current_tx_->start();
}
void
session_t::
on_tx_complete(std::error_code const& ec, bool close)
{
trace() << "on_tx_complete()...";
auto self = shared_from_this();
current_tx_.reset();
if (!ec) {
if (close) {
trace() << "on_tx_complete(): close requested...";
// Initiate a graceful close of this session/connection.
//
init_close();
}
else {
trace() << "on_tx_complete(): keep-alive requested...";
// We're apparently not done, initiate a new transaction.
//
init_transaction();
}
}
else {
trace() << "on_tx_complete(): error: " << ec;
// If an error occurred, we just remove ourself from the
// supervisor which will eventually close the connection
// when we are destroyed.
//
owner().remove(self);
}
}
void
session_t::
init_close()
{
auto self = shared_from_this();
auto bound = [this, self]()
{
owner().remove(self);
};
stream_->async_graceful_close(bound);
}
common::diagnostics_t::proxy_t
session_t::
trace()
{
return diagnostics_ << session_category();
}
} // namespace code::seafire::server

View File

@ -0,0 +1,130 @@
#ifndef code__seafire__server__session_hxx_
#define code__seafire__server__session_hxx_
#include <code/seafire/common/diagnostics.hxx>
#include <code/seafire/common/io/stream.hxx>
#include <code/seafire/server/counter.hxx>
#include <code/seafire/server/error-handler.hxx>
#include <code/seafire/server/request-handler.hxx>
#include <code/seafire/protocol/connection.hxx>
#include <asio.hpp>
#include <memory>
#include <system_error>
namespace code::seafire::server
{
class supervisor_t;
class transaction_t;
class session_t
: public std::enable_shared_from_this<session_t>
{
public:
class info_t;
struct stats_t
{
/// The total number of transactions handled by this session.
///
counter_t transaction_counter;
};
virtual
~session_t() noexcept;
supervisor_t&
owner();
stats_t
stats() const;
void
start();
void
stop();
public:
friend
std::shared_ptr<session_t>
make_shared_session(common::diagnostics_t&,
error_handler_t&,
supervisor_t&,
std::unique_ptr<common::io::stream_t>,
request_handler_t&);
session_t(common::diagnostics_t&,
error_handler_t&,
supervisor_t&,
std::unique_ptr<common::io::stream_t>,
request_handler_t&);
session_t(session_t const&) = delete;
session_t(session_t&&) = delete;
void
init_transaction();
void
on_tx_complete(std::error_code const&, bool);
void
init_close();
void
init_close_read();
void
on_close_read(std::error_code const&);
session_t& operator=(session_t const&) = delete;
session_t& operator=(session_t&&) = delete;
private:
common::diagnostics_t::proxy_t
trace();
std::mutex protector_;
common::diagnostics_t& diagnostics_;
error_handler_t& error_handler_;
supervisor_t& owner_;
std::unique_ptr<common::io::stream_t> stream_;
request_handler_t& handler_;
protocol::connection_t connection_;
stats_t stats_;
std::shared_ptr<transaction_t> current_tx_;
char throwaway_[1];
};
class session_t::info_t
{
};
inline
std::shared_ptr<session_t>
make_shared_session(common::diagnostics_t& diagnostics,
error_handler_t& error_handler,
supervisor_t& supervisor,
std::unique_ptr<common::io::stream_t> stream,
request_handler_t& request_handler)
{
return std::make_shared<session_t>(diagnostics,
error_handler,
supervisor,
std::move(stream),
request_handler);
}
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,88 @@
#include <code/seafire/server/supervisor.hxx>
#include <code/seafire/server/diagnostics.hxx>
namespace code::seafire::server
{
supervisor_t::
supervisor_t(common::diagnostics_t& diagnostics,
error_handler_t& error_handler,
request_handler_t& handler)
: diagnostics_{diagnostics},
error_handler_{error_handler},
handler_{handler}
{}
void
supervisor_t::
async_accept(common::io::acceptor_t& acceptor, accept_handler_t handler)
{
trace() << "async_accept()...";
auto bound = [this, handler](std::error_code const& ec,
std::unique_ptr<common::io::stream_t> stream)
{
handler(ec);
if (!ec) {
this->start(std::move(stream));
}
};
acceptor.async_accept(bound);
}
void
supervisor_t::
start(std::unique_ptr<common::io::stream_t> stream)
{
trace() << "start()...";
if (!stream) {
throw std::invalid_argument{"stream"};
}
auto session = make_shared_session(diagnostics_,
error_handler_,
*this,
std::move(stream),
handler_);
{
std::lock_guard lock{protector_};
sessions_.emplace(session);
}
session->start();
}
void
supervisor_t::
stop_all()
{
trace() << "stop_all()...";
std::lock_guard lock{protector_};
for (auto const& j : sessions_) {
j->stop();
}
}
common::diagnostics_t::proxy_t
supervisor_t::
trace() const
{
return diagnostics_ << supervisor_category();
}
void
supervisor_t::
remove(session_ptr_t session)
{
std::lock_guard lock{protector_};
sessions_.erase(session);
}
} // namespace code::seafire::server

View File

@ -0,0 +1,92 @@
#ifndef code__seafire__server__supervisor_hxx_
#define code__seafire__server__supervisor_hxx_
#include <code/seafire/common/diagnostics.hxx>
#include <code/seafire/common/io/acceptor.hxx>
#include <code/seafire/common/io/stream.hxx>
#include <code/seafire/server/counter.hxx>
#include <code/seafire/server/error-handler.hxx>
#include <code/seafire/server/request-handler.hxx>
#include <code/seafire/server/session.hxx>
#include <functional>
#include <memory>
#include <mutex>
#include <set>
namespace code::seafire::server
{
/// Implements a session supervisor.
///
class supervisor_t
{
friend session_t;
using session_ptr_t = std::shared_ptr<session_t>;
public:
class info_t;
using accept_handler_t = std::function<void(std::error_code)>;
/// Tracks supervisor statistics counters.
///
struct counters_t
{
/// Tracks the total number of sessions.
///
counter_t total_session_count;
/// Tracks the total number of currently active sessions.
///
counter_t active_session_count;
};
supervisor_t(common::diagnostics_t&,
error_handler_t&,
request_handler_t&);
supervisor_t(supervisor_t const&) = delete;
supervisor_t(supervisor_t&&) = delete;
void
async_accept(common::io::acceptor_t&, accept_handler_t);
void
start(std::unique_ptr<common::io::stream_t>);
void
stop_all();
supervisor_t& operator=(supervisor_t const&) = delete;
supervisor_t& operator=(supervisor_t&&) = delete;
private:
common::diagnostics_t::proxy_t
trace() const;
void
remove(session_ptr_t);
std::mutex protector_;
common::diagnostics_t& diagnostics_;
error_handler_t& error_handler_;
request_handler_t& handler_;
std::set<session_ptr_t> sessions_;
counters_t stats_;
};
class supervisor_t::info_t
{
public:
};
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,435 @@
#include "code/seafire/protocol/status-code.hxx"
#include <code/seafire/server/transaction.hxx>
#include <code/seafire/server/diagnostics.hxx>
#include <code/seafire/server/version.hxx>
#include <code/seafire/protocol/rfc7230/connection.hxx>
#include <code/seafire/protocol/rfc7230/content-length.hxx>
#include <code/seafire/protocol/rfc7231/content-type.hxx>
#include <code/seafire/protocol/rfc7231/date.hxx>
#include <code/seafire/protocol/rfc7231/server.hxx>
#include <chrono>
namespace code::seafire::server
{
// fixme: determine which member functions need to lock the mutex.
//
transaction_t::
~transaction_t() noexcept(false)
{
trace() << "~transaction_t()...";
}
error_handler_t&
transaction_t::
get_error_handler()
{
return error_handler_;
}
protocol::connection_t&
transaction_t::
connection()
{
return connection_;
}
asio::any_io_executor const&
transaction_t::
get_executor()
{
return connection().get_executor();
}
void
transaction_t::
start()
{
init_read();
}
void
transaction_t::
cancel()
{
// fixme: connection().cancel();
}
common::allocator_t&
transaction_t::
memory()
{
return allocator_;
}
protocol::request_t const&
transaction_t::
get_request() const
{
return request_;
}
std::istream&
transaction_t::
get_request_content()
{
return request_content_stream_;
}
common::extension_context_t&
transaction_t::
get_request_extensions()
{
return request_extensions_;
}
void
transaction_t::
register_finalizer(finalizer_t* f)
{
if (f == nullptr) {
throw std::invalid_argument{"invalid finalizer"};
}
std::lock_guard lock{protector_};
finalizers_.emplace_back(f);
}
void
transaction_t::
deregister_finalizer(finalizer_t* f)
{
if (f == nullptr) {
throw std::invalid_argument{"invalid finalizer"};
}
std::lock_guard lock{protector_};
std::erase(finalizers_, f);
}
protocol::response_t&
transaction_t::
get_response()
{
return response_;
}
protocol::response_t const&
transaction_t::
get_response() const
{
return response_;
}
common::extension_context_t&
transaction_t::
get_response_extensions()
{
return response_extensions_;
}
void
transaction_t::
do_send_response(protocol::status_code_t s,
common::io::const_buffers_t const& content)
{
trace() << "do_send_response()...";
std::lock_guard lock{protector_};
auto self = shared_from_this();
invoke_finalizers();
finalize_response(s, asio::buffer_size(content));
init_write(content);
}
void
transaction_t::
do_send_error(common_error_t error)
{
auto self = shared_from_this();
auto bound = [this, self, error]
{
error_handler_.on_error(*this, *this, error);
};
asio::post(get_executor(), bound);
}
transaction_t::
transaction_t(common::diagnostics_t& diagnostics,
std::chrono::seconds request_timeout,
error_handler_t& error_handler,
protocol::connection_t& connection,
request_handler_t& handler,
completion_handler_t on_completion)
: diagnostics_{diagnostics},
request_timeout_{request_timeout},
error_handler_{error_handler},
connection_{connection},
handler_{handler},
on_completion_{on_completion},
request_timeout_timer_{connection_.get_executor()},
request_content_{10}, // fixme: make configurable buffer max,
//request_content_{1024 * 1024 * 32}, // fixme: make configurable buffer max,
request_content_stream_{&request_content_}
{
trace() << "transaction_t()...";
}
common::diagnostics_t::proxy_t
transaction_t::
trace()
{
return diagnostics_ << transaction_category();
}
bool
transaction_t::
keep_alive()
{
auto c = get<protocol::rfc7230::connection_t>(get_request());
if (c && c->close()) {
return false;
}
if (get_request().version() == protocol::http_1_0 && c) {
return c->keep_alive();
}
if (get_request().version() == protocol::http_1_1) {
return true;
}
return false;
}
void
transaction_t::
init_read()
{
trace() << "init_read()...";
auto self = shared_from_this();
prepare_response();
if (request_timeout_ > std::chrono::seconds{0}) {
auto on_timeout = [this, self](std::error_code const& ec)
{
on_read_timeout(ec);
};
request_timeout_timer_.expires_after(request_timeout_);
request_timeout_timer_.async_wait(on_timeout);
}
auto on_read = [this, self](std::error_code const& ec)
{
this->on_read(ec);
};
connection().async_read(request_, request_content_, on_read);
}
void
transaction_t::
on_read_timeout(std::error_code const& ec)
{
auto self = shared_from_this();
std::lock_guard lock{protector_};
if (!ec) {
// fixme: add diagnostics...
//
connection_.cancel();
send(common_error_t::request_timeout);
}
else if (ec != asio::error::operation_aborted) {
// error occurred, restart timer.
//
request_timeout_timer_.expires_after(request_timeout_);
auto on_timeout = [this, self](std::error_code const& ec)
{
on_read_timeout(ec);
};
request_timeout_timer_.async_wait(on_timeout);
}
}
void
transaction_t::
on_read(std::error_code const& ec)
{
// Attempt to cancel the request timeout handler, if active.
//
if (request_timeout_ > std::chrono::seconds{0}) {
if (request_timeout_timer_.cancel() < 1) {
// Timeout already happened.
//
return;
}
}
// fixme: add tracing.
//
if (ec) {
// fixme: Send error response based on ec.
//
send(common_error_t::internal_server_error);
return;
}
// handle Expect: 100-continue
if (auto expect = request_.headers().get_one("expect"); expect) {
if (request_.version() == protocol::http_1_1 && *expect == "100-continue") {
static std::string const response{
"HTTP/1.1 100 Continue\r\n\r\n"
};
connection().get_stream().write(common::io::buffer(response));
}
}
init_dispatch();
}
void
transaction_t::
prepare_response()
{
using protocol::rfc7231::date_t;
using protocol::rfc7231::product_t;
using protocol::rfc7231::products_t;
using protocol::rfc7231::server_t;
using protocol::set;
set<server_t>(get_response(), products_t{
product_t{"Seafire", LIBCODE_SEAFIRE_SERVER_VERSION_STR}
});
set<date_t>(get_response(), std::chrono::system_clock::now());
// we always respond with HTTP/1.1 since that is the highest version we support.
//
get_response().set_version(protocol::http_1_1);
}
void
transaction_t::
init_dispatch()
{
trace() << "init_dispatch()...";
try {
handler_.invoke(*this, *this);
}
catch (...) {
trace() << "handler threw exception, dispatching to exception handler...";
try {
get_error_handler().on_exception(*this, *this);
}
catch (...) {
trace() << "exception handler threw, we're out of luck...";
// Fuck, we're out of luck.
//
send(common_error_t::internal_server_error);
}
}
}
void
transaction_t::
invoke_finalizers()
{
for (auto const& j : finalizers_) {
invoke_finalizer(j, *this);
}
}
void
transaction_t::
finalize_response(protocol::status_code_t const& s,
std::size_t content_length)
{
get_response().set_status(s);
namespace rfc7230 = protocol::rfc7230;
namespace rfc7231 = protocol::rfc7231;
if (get_request().version() == protocol::http_1_0) {
if (keep_alive()) {
set<rfc7230::connection_t>(get_response(), "keep-alive");
}
else {
set<rfc7230::connection_t>(get_response(), "close");
}
}
else if (get_request().version() == protocol::http_1_1) {
if (keep_alive()) {
erase<rfc7230::connection_t>(get_response());
}
else {
set<rfc7230::connection_t>(get_response(), "close");
}
}
// Make sure content-type is always set.
//
if (!has<rfc7231::content_type_t>(get_response())) {
set<rfc7231::content_type_t>(get_response(), protocol::media_type_t{"application", "octet-stream"});
}
// Always set content length to the actual content length.
//
set<rfc7230::content_length_t>(get_response(), content_length);
}
void
transaction_t::
init_write(common::io::const_buffers_t const& content)
{
trace() << "init write...()";
auto self = shared_from_this();
std::lock_guard lock{protector_};
auto bound = [this, self](std::error_code const& ec)
{
on_write(ec);
};
connection().async_write(get_response(), content, bound);
}
void
transaction_t::
on_write(std::error_code const& ec)
{
trace() << "on_write()...";
auto self = shared_from_this();
std::lock_guard lock{protector_};
auto bound = [self, ec, close = !keep_alive(), cb = on_completion_]()
{
cb(ec, close ? complete_closed : complete);
};
asio::post(get_executor(), bound);
}
} // namespace code::seafire::server

View File

@ -0,0 +1,216 @@
#ifndef code__seafire__server__transaction_hxx_
#define code__seafire__server__transaction_hxx_
#include <code/seafire/common/allocator.hxx>
#include <code/seafire/common/diagnostics.hxx>
#include <code/seafire/common/extension-context.hxx>
#include <code/seafire/common/io/buffer.hxx>
#include <code/seafire/server/error-handler.hxx>
#include <code/seafire/server/request-handler.hxx>
#include <code/seafire/server/request.hxx>
#include <code/seafire/server/response.hxx>
#include <code/seafire/protocol/connection.hxx>
#include <asio.hpp>
#include <iostream>
#include <memory>
#include <mutex>
namespace code::seafire::server
{
class transaction_t
: public std::enable_shared_from_this<transaction_t>,
request_t,
response_t
{
public:
struct configuration_t;
/// The result of this transaction.
///
enum result_t
{
/// Indicates a completed transation.
///
complete,
/// Indicates a completed transation and that the
/// underlying connection should be closed.
complete_closed
};
/// Completion handler type.
///
using completion_handler_t = std::function<void(std::error_code, result_t)>;
virtual
~transaction_t() noexcept(false);
error_handler_t&
get_error_handler();
protocol::connection_t&
connection();
asio::any_io_executor const&
get_executor();
void
start();
void
cancel();
// =================
// Common interface.
//
common::allocator_t&
memory() override;
// ==================
// Request interface.
//
// fixme: make these private.
//
protocol::request_t const&
get_request() const override;
std::istream&
get_request_content() override;
common::extension_context_t&
get_request_extensions() override;
// ===================
// Response interface.
//
// fixme: make these private.
//
void
register_finalizer(finalizer_t* f) override;
void
deregister_finalizer(finalizer_t* f) override;
protocol::response_t&
get_response() override;
protocol::response_t const&
get_response() const override;
common::extension_context_t&
get_response_extensions() override;
void
do_send_response(protocol::status_code_t,
common::io::const_buffers_t const&) override;
void
do_send_error(common_error_t error) override;
protected:
template<typename... Args>
friend
std::shared_ptr<transaction_t>
make_transaction(Args&&...);
protected:
/// fixme: replace request_timeout with a configuration_t object.
///
transaction_t(common::diagnostics_t&,
std::chrono::seconds request_timeout,
error_handler_t&,
protocol::connection_t&,
request_handler_t&,
completion_handler_t);
private:
common::diagnostics_t::proxy_t
trace();
bool
keep_alive();
void
init_read();
void
on_read_timeout(std::error_code const&);
void
on_read(std::error_code const&);
void
prepare_response();
void
init_dispatch();
void
invoke_finalizers();
void
finalize_response(protocol::status_code_t const&,
std::size_t);
void
init_write(common::io::const_buffers_t const&);
void
on_write(std::error_code const&);
std::recursive_mutex protector_;
common::diagnostics_t& diagnostics_;
std::chrono::seconds request_timeout_;
error_handler_t& error_handler_;
common::allocator_t allocator_;
common::extension_context_t request_extensions_;
common::extension_context_t response_extensions_;
protocol::connection_t& connection_;
request_handler_t& handler_;
completion_handler_t on_completion_;
asio::steady_timer request_timeout_timer_;
protocol::request_t request_;
asio::streambuf request_content_;
std::istream request_content_stream_;
protocol::response_t response_;
std::vector<finalizer_t*> finalizers_;
};
/// Holds transaction configuration parameters.
///
struct transaction_t::configuration_t
{
/// Holds the request timeout (in seconds).
///
/// Request timeout is completely disabled if this is 0.
///
std::chrono::seconds request_timeout;
};
template<typename... Args>
std::shared_ptr<transaction_t>
make_transaction(Args&&... args)
{
return std::shared_ptr<transaction_t>{
new transaction_t{
std::forward<Args>(args)...
}
};
}
} // namespace code::seafire::server
#endif

View File

@ -0,0 +1,37 @@
#ifndef code__seafire__server__version_hxx_
#define code__seafire__server__version_hxx_
// The numeric version format is AAAAABBBBBCCCCCDDDE where:
//
// AAAAA - major version number
// BBBBB - minor version number
// CCCCC - bugfix version number
// DDD - alpha / beta (DDD + 500) version number
// E - final (0) / snapshot (1)
//
// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example:
//
// Version AAAAABBBBBCCCCCDDDE
//
// 0.1.0 0000000001000000000
// 0.1.2 0000000001000020000
// 1.2.3 0000100002000030000
// 2.2.0-a.1 0000200001999990010
// 3.0.0-b.2 0000299999999995020
// 2.2.0-a.1.z 0000200001999990011
//
#define LIBCODE_SEAFIRE_SERVER_VERSION $libcode_seafire_server.version.project_number$ULL
#define LIBCODE_SEAFIRE_SERVER_VERSION_STR "$libcode_seafire_server.version.project$"
#define LIBCODE_SEAFIRE_SERVER_VERSION_ID "$libcode_seafire_server.version.project_id$"
#define LIBCODE_SEAFIRE_SERVER_VERSION_FULL "$libcode_seafire_server.version$"
#define LIBCODE_SEAFIRE_SERVER_VERSION_MAJOR $libcode_seafire_server.version.major$
#define LIBCODE_SEAFIRE_SERVER_VERSION_MINOR $libcode_seafire_server.version.minor$
#define LIBCODE_SEAFIRE_SERVER_VERSION_PATCH $libcode_seafire_server.version.patch$
#define LIBCODE_SEAFIRE_SERVER_PRE_RELEASE $libcode_seafire_server.version.pre_release$
#define LIBCODE_SEAFIRE_SERVER_SNAPSHOT_SN $libcode_seafire_server.version.snapshot_sn$ULL
#define LIBCODE_SEAFIRE_SERVER_SNAPSHOT_ID "$libcode_seafire_server.version.snapshot_id$"
#endif

15
manifest Normal file
View File

@ -0,0 +1,15 @@
: 1
name: libcode-seafire-server
version: 0.1.0-a.0.z
language: c++
summary: libcode-seafire-server C++ library
license: BSD-4-Clause
description-file: README.md
url: https://helloryan.se/code/
email: ryan@helloryan.se
depends: * build2 >= 0.17.0
depends: * bpkg >= 0.17.0
depends: libasio ^1.29.0
depends: libcode-uri ^0.1.0-
depends: libcode-seafire-common ^0.1.0-
depends: libcode-seafire-protocol ^0.1.0-

19
repositories.manifest Normal file
View File

@ -0,0 +1,19 @@
: 1
summary: libcode-seafire-server project repository
:
role: prerequisite
location: https://pkg.cppget.org/1/beta
trust: 70:64:FE:E4:E0:F3:60:F1:B4:51:E1:FA:12:5C:E0:B3:DB:DF:96:33:39:B9:2E:E5:C2:68:63:4C:A6:47:39:43
:
role: prerequisite
location: https://code.helloryan.se/code/libcode-uri.git##HEAD
:
role: prerequisite
location: https://code.helloryan.se/code/libcode-seafire-common.git##HEAD
:
role: prerequisite
location: https://code.helloryan.se/code/libcode-seafire-protocol.git##HEAD

8
tests/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Test executables.
#
driver
# Testscript output directories (can be symlinks).
#
test
test-*

4
tests/build/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

View File

@ -0,0 +1,5 @@
project = # Unnamed tests subproject.
using config
using test
using dist

16
tests/build/root.build Normal file
View File

@ -0,0 +1,16 @@
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# Every exe{} in this subproject is by default a test.
#
exe{*}: test = true
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

1
tests/buildfile Normal file
View File

@ -0,0 +1 @@
./: {*/ -build/}