Commit d2830d80 authored by Dominik Charousset's avatar Dominik Charousset

Implement scaffolding for an HTTP transport

parent 1f3cc4e7
...@@ -16,6 +16,8 @@ caf_incubator_add_component( ...@@ -16,6 +16,8 @@ caf_incubator_add_component(
net.basp.connection_state net.basp.connection_state
net.basp.ec net.basp.ec
net.basp.message_type net.basp.message_type
net.http.method
net.http.status
net.operation net.operation
net.web_socket.status net.web_socket.status
HEADERS HEADERS
...@@ -31,6 +33,10 @@ caf_incubator_add_component( ...@@ -31,6 +33,10 @@ caf_incubator_add_component(
src/multiplexer.cpp src/multiplexer.cpp
src/net/abstract_actor_shell.cpp src/net/abstract_actor_shell.cpp
src/net/actor_shell.cpp src/net/actor_shell.cpp
src/net/http/header.cpp
src/net/http/method.cpp
src/net/http/status.cpp
src/net/http/v1.cpp
src/net/middleman.cpp src/net/middleman.cpp
src/net/middleman_backend.cpp src/net/middleman_backend.cpp
src/net/packet_writer.cpp src/net/packet_writer.cpp
...@@ -57,6 +63,7 @@ caf_incubator_add_component( ...@@ -57,6 +63,7 @@ caf_incubator_add_component(
multiplexer multiplexer
net.actor_shell net.actor_shell
net.consumer_adapter net.consumer_adapter
net.http.server
net.length_prefix_framing net.length_prefix_framing
net.operation net.operation
net.producer_adapter net.producer_adapter
......
...@@ -86,8 +86,10 @@ private: ...@@ -86,8 +86,10 @@ private:
} }
void on_wakeup() { void on_wakeup() {
if (buf_ && buf_->has_consumer_event()) if (buf_ && buf_->has_consumer_event()) {
puts("WAKEUP");
mgr_->mpx().register_writing(mgr_); mgr_->mpx().register_writing(mgr_);
}
} }
intrusive_ptr<socket_manager> mgr_; intrusive_ptr<socket_manager> mgr_;
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
namespace caf::net::http {
/// Stores context information for a request. For HTTP/2, this is the stream ID.
struct context {};
} // namespace caf::net::http
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/config_value.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/http/header_fields_map.hpp"
#include "caf/net/http/method.hpp"
#include "caf/net/http/status.hpp"
#include "caf/string_view.hpp"
#include "caf/uri.hpp"
#include <vector>
namespace caf::net::http {
class CAF_NET_EXPORT header {
public:
header() = default;
header(header&&) = default;
header& operator=(header&&) = default;
header(const header&);
header& operator=(const header&);
void assign(const header&);
http::method method() const noexcept {
return method_;
}
string_view path() const noexcept {
return uri_.path();
}
const uri::query_map& query() const noexcept {
return uri_.query();
}
string_view fragment() const noexcept {
return uri_.fragment();
}
string_view version() const noexcept {
return version_;
}
const header_fields_map& fields() const noexcept {
return fields_;
}
string_view field(string_view key) const noexcept {
if (auto i = fields_.find(key); i != fields_.end())
return i->second;
else
return {};
}
template <class T>
optional<T> field_as(string_view key) const noexcept {
if (auto i = fields_.find(key); i != fields_.end()) {
caf::config_value val{to_string(i->second)};
if (auto res = caf::get_as<T>(val))
return std::move(*res);
else
return {};
} else {
return {};
}
}
bool valid() const noexcept {
return !raw_.empty();
}
std::pair<status, string_view> parse(string_view raw);
bool chunked_transfer_encoding() const;
optional<size_t> content_length() const;
private:
std::vector<char> raw_;
http::method method_;
uri uri_;
string_view version_;
header_fields_map fields_;
};
} // namespace caf::net::http
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/string_view.hpp"
namespace caf::net::http {
/// Convenience type alias for a key-value map storing header fields.
using header_fields_map = detail::unordered_flat_map<string_view, string_view>;
} // namespace caf::net::http
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/string_view.hpp"
#include <cstdint>
#include <string>
#include <type_traits>
namespace caf::net::http {
/// Methods as defined by RFC 7231.
enum class method : uint8_t {
/// Requests transfer of a current selected representation for the target
/// resource.
get,
/// Identical to GET except that the server MUST NOT send a message body in
/// the response. The server SHOULD send the same header fields in response to
/// a HEAD request as it would have sent if the request had been a GET, except
/// that the payload header fields (Section 3.3) MAY be omitted.
head,
/// Requests that the target resource process the representation enclosed in
/// the request according to the resource's own specific semantics.
post,
/// Requests that the state of the target resource be created or replaced with
/// the state defined by the representation enclosed in the request message
/// payload.
put,
/// Requests that the origin server remove the association between the target
/// resource and its current functionality.
del,
/// Requests that the recipient establish a tunnel to the destination origin
/// server identified by the request-target and, if successful, thereafter
/// restrict its behavior to blind forwarding of packets, in both directions,
/// until the tunnel is closed.
connect,
/// Requests information about the communication options available for the
/// target resource, at either the origin server or an intervening
/// intermediary.
options,
/// Requests a remote, application-level loop-back of the request message.
trace,
};
/// @relates method
CAF_NET_EXPORT std::string to_string(method);
/// Converts @p x to the RFC string representation, i.e., all-uppercase.
/// @relates method
CAF_NET_EXPORT std::string to_rfc_string(method x);
/// @relates method
CAF_NET_EXPORT bool from_string(string_view, method&);
/// @relates method
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<method>, method&);
/// @relates method
template <class Inspector>
bool inspect(Inspector& f, method& x) {
return default_enum_inspect(f, x);
}
} // namespace caf::net::http
This diff is collapsed.
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/string_view.hpp"
#include <cstdint>
#include <string>
#include <type_traits>
namespace caf::net::http {
/// Status codes as defined by RFC 7231 and RFC 6585.
enum class status : uint16_t {
/// Indicates that the initial part of a request has been received and has not
/// yet been rejected by the server. The server intends to send a final
/// response after the request has been fully received and acted upon.
continue_request = 100,
/// Indicates that the server understands and is willing to comply with the
/// client's request for a change in the application protocol being used on
/// this connection.
switching_protocols = 101,
/// Indicates that the request has succeeded.
ok = 200,
/// Indicates that the request has been fulfilled and has resulted in one or
/// more new resources being created.
created = 201,
/// Indicates that the request has been accepted for processing, but the
/// processing has not been completed.
accepted = 202,
/// Indicates that the request was successful but the enclosed payload has
/// been modified from that of the origin server's 200 (OK) response by a
/// transforming proxy
non_authoritative_information = 203,
/// Indicates that the server has successfully fulfilled the request and that
/// there is no additional content to send in the response payload body.
no_content = 204,
/// Indicates that the server has fulfilled the request and desires that the
/// user agent reset the "document view".
reset_content = 205,
/// Indicates that the server is successfully fulfilling a range request for
/// the target resource by transferring one or more parts of the selected
/// representation that correspond to the satisfiable ranges found in the
/// request's Range header field.
partial_content = 206,
/// Indicates that the target resource has more than one representation and
/// information about the alternatives is being provided so that the user (or
/// user agent) can select a preferred representation.
multiple_choices = 300,
/// Indicates that the target resource has been assigned a new permanent URI.
moved_permanently = 301,
/// Indicates that the target resource resides temporarily under a different
/// URI.
found = 302,
/// Indicates that the server is redirecting the user agent to a different
/// resource, as indicated by a URI in the Location header field, which is
/// intended to provide an indirect response to the original request.
see_other = 303,
/// Indicates that a conditional GET or HEAD request has been received and
/// would have resulted in a 200 (OK) response if it were not for the fact
/// that the condition evaluated to false.
not_modified = 304,
/// Deprecated.
use_proxy = 305,
/// No longer valid.
temporary_redirect = 307,
/// Indicates that the server cannot or will not process the request due to
/// something that is perceived to be a client error (e.g., malformed request
/// syntax, invalid request message framing, or deceptive request routing).
bad_request = 400,
/// Indicates that the request has not been applied because it lacks valid
/// authentication credentials for the target resource.
unauthorized = 401,
/// Reserved for future use.
payment_required = 402,
/// Indicates that the server understood the request but refuses to authorize
/// it.
forbidden = 403,
/// Indicates that the origin server did not find a current representation for
/// the target resource or is not willing to disclose that one exists.
not_found = 404,
/// Indicates that the method received in the request-line is known by the
/// origin server but not supported by the target resource.
method_not_allowed = 405,
/// Indicates that the target resource does not have a current representation
/// that would be acceptable to the user agent, according to the proactive
/// negotiation header fields received in the request (Section 5.3), and the
/// server is unwilling to supply a default representation.
not_acceptable = 406,
/// Similar to 401 (Unauthorized), but it indicates that the client needs to
/// authenticate itself in order to use a proxy.
proxy_authentication_required = 407,
/// Indicates that the server did not receive a complete request message
/// within the time that it was prepared to wait.
request_timeout = 408,
/// Indicates that the request could not be completed due to a conflict with
/// the current state of the target resource. This code is used in situations
/// where the user might be able to resolve the conflict and resubmit the
/// request.
conflict = 409,
/// Indicates that access to the target resource is no longer available at the
/// origin server and that this condition is likely to be permanent.
gone = 410,
/// Indicates that the server refuses to accept the request without a defined
/// Content-Length.
length_required = 411,
/// Indicates that one or more conditions given in the request header fields
/// evaluated to false when tested on the server.
precondition_failed = 412,
/// Indicates that the server is refusing to process a request because the
/// request payload is larger than the server is willing or able to process.
payload_too_large = 413,
/// Indicates that the server is refusing to service the request because the
/// request-target is longer than the server is willing to interpret.
uri_too_long = 414,
/// Indicates that the origin server is refusing to service the request
/// because the payload is in a format not supported by this method on the
/// target resource.
unsupported_media_type = 415,
/// Indicates that none of the ranges in the request's Range header field
/// overlap the current extent of the selected resource or that the set of
/// ranges requested has been rejected due to invalid ranges or an excessive
/// request of small or overlapping ranges.
range_not_satisfiable = 416,
/// Indicates that the expectation given in the request's Expect header field
/// could not be met by at least one of the inbound servers.
expectation_failed = 417,
/// Indicates that the server refuses to perform the request using the current
/// protocol but might be willing to do so after the client upgrades to a
/// different protocol.
upgrade_required = 426,
/// Indicates that the origin server requires the request to be conditional.
precondition_required = 428,
/// Indicates that the user has sent too many requests in a given amount of
/// time ("rate limiting").
too_many_requests = 429,
/// Indicates that the server is unwilling to process the request because its
/// header fields are too large.
request_header_fields_too_large = 431,
/// Indicates that the server encountered an unexpected condition that
/// prevented it from fulfilling the request.
internal_server_error = 500,
/// Indicates that the server does not support the functionality required to
/// fulfill the request.
not_implemented = 501,
/// Indicates that the server, while acting as a gateway or proxy, received an
/// invalid response from an inbound server it accessed while attempting to
/// fulfill the request.
bad_gateway = 502,
/// Indicates that the server is currently unable to handle the request due to
/// a temporary overload or scheduled maintenance, which will likely be
/// alleviated after some delay.
service_unavailable = 503,
/// Indicates that the server, while acting as a gateway or proxy, did not
/// receive a timely response from an upstream server it needed to access in
/// order to complete the request.
gateway_timeout = 504,
/// Indicates that the server does not support, or refuses to support, the
/// major version of HTTP that was used in the request message.
http_version_not_supported = 505,
/// Indicates that the client needs to authenticate to gain network access.
network_authentication_required = 511,
};
/// Returns the recommended response phrase to a status code.
/// @relates status
CAF_NET_EXPORT string_view phrase(status);
/// @relates status
CAF_NET_EXPORT std::string to_string(status);
/// @relates status
CAF_NET_EXPORT bool from_string(string_view, status&);
/// @relates status
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<status>, status&);
/// @relates status
template <class Inspector>
bool inspect(Inspector& f, status& x) {
return default_enum_inspect(f, x);
}
} // namespace caf::net::http
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/byte_span.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/http/header_fields_map.hpp"
#include "caf/net/http/status.hpp"
#include "caf/string_view.hpp"
#include <utility>
namespace caf::net::http::v1 {
/// Tries splitting the given byte span into an HTTP header (`first`) and a
/// remainder (`second`). Returns an empty @ref string_view as `first` for
/// incomplete HTTP headers.
CAF_NET_EXPORT std::pair<string_view, byte_span> split_header(byte_span bytes);
/// Writes an HTTP header to the buffer.
CAF_NET_EXPORT void write_header(status code, const header_fields_map& fields,
byte_buffer& buf);
/// Writes a complete HTTP response to the buffer. Automatically sets
/// Content-Type and Content-Length header fields.
CAF_NET_EXPORT void write_response(status code, string_view content_type,
string_view content, byte_buffer& buf);
/// Writes a complete HTTP response to the buffer. Automatically sets
/// Content-Type and Content-Length header fields followed by the user-defined
/// @p fields.
CAF_NET_EXPORT void
write_response(status code, string_view content_type, string_view content,
const header_fields_map& fields, byte_buffer& buf);
} // namespace caf::net::http::v1
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/byte_span.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
namespace caf::net {
/// Wraps a pointer to a hypertext-oriented layer with a pointer to its lower
/// layer. Both pointers are then used to implement the interface required for a
/// hypertext-oriented layer when calling into its upper layer.
template <class Layer, class LowerLayerPtr>
class hypertext_oriented_layer_ptr {
public:
using context_type = typename Layer::context_type;
using status_code_type = typename Layer::status_code_type;
using header_fields_type = typename Layer::header_fields_type;
class access {
public:
access(Layer* layer, LowerLayerPtr down) : lptr_(layer), llptr_(down) {
// nop
}
/// Queries whether the underlying transport can send additional data.
bool can_send_more() const noexcept {
return lptr_->can_send_more(llptr_);
}
/// Asks the underlying transport to stop receiving additional data until
/// resumed.
void suspend_reading() {
return lptr_->suspend_reading(llptr_);
}
/// Returns the socket handle.
auto handle() const noexcept {
return lptr_->handle(llptr_);
}
/// Sends a response header for answering the request identified by the
/// context.
/// @param context Identifies which request this response belongs to.
/// @param code Indicates either success or failure to the client.
/// @param fields Various informational fields for the client. When sending
/// a payload afterwards, the fields should at least include
/// the content length.
bool send_header(context_type context, status_code_type code,
const header_fields_type& fields) {
return lptr_->send_header(llptr_, context, code, fields);
}
/// Sends a payload to the client. Must follow a header.
/// @param context Identifies which request this response belongs to.
/// @param bytes Arbitrary data for the client.
/// @pre `bytes.size() > 0`
[[nodiscard]] bool send_payload(context_type context,
const_byte_span bytes) {
return lptr_->send_payload(llptr_, context, bytes);
}
/// Sends a single chunk of arbitrary data. The chunks must follow a header.
/// @pre `bytes.size() > 0`
[[nodiscard]] bool send_chunk(context_type context, const_byte_span bytes) {
return lptr_->send_chunk(llptr_, context, bytes);
}
/// Informs the client that the transfer completed, i.e., that the server
/// will not send additional chunks.
[[nodiscard]] bool send_end_of_chunks(context_type context) {
return lptr_->send_end_of_chunks(llptr_, context);
}
/// Convenience function for completing a request 'raw' (without adding
/// additional header fields) in a single function call. Calls
/// `send_header`, `send_payload` and `fin`.
bool send_raw_response(context_type context, status_code_type code,
const header_fields_type& fields,
const_byte_span content) {
if (lptr_->send_header(llptr_, context, code, fields)
&& (content.empty()
|| lptr_->send_payload(llptr_, context, content))) {
lptr_->fin(llptr_, context);
return true;
} else {
return false;
}
}
/// Convenience function for completing a request in a single function call.
/// Automatically sets the header fields 'Content-Type' and
/// 'Content-Length'. Calls `send_header`, `send_payload` and `fin`.
bool send_response(context_type context, status_code_type code,
header_fields_type fields, string_view content_type,
const_byte_span content) {
std::string len;
if (!content.empty()) {
auto len = std::to_string(content.size());
fields.emplace("Content-Length", len);
fields.emplace("Content-Type", content_type);
}
return send_raw_response(context, code, fields, content);
}
/// Convenience function for completing a request in a single function call.
/// Automatically sets the header fields 'Content-Type' and
/// 'Content-Length'. Calls `send_header`, `send_payload` and `fin`.
bool send_response(context_type context, status_code_type code,
string_view content_type, const_byte_span content) {
std::string len;
header_fields_type fields;
if (!content.empty()) {
auto len = std::to_string(content.size());
fields.emplace("Content-Type", content_type);
fields.emplace("Content-Length", len);
}
return send_raw_response(context, code, fields, content);
}
bool send_response(context_type context, status_code_type code,
header_fields_type fields, string_view content_type,
string_view content) {
return send_response(context, code, content_type, std::move(fields),
as_bytes(make_span(content)));
}
bool send_response(context_type context, status_code_type code,
string_view content_type, string_view content) {
return send_response(context, code, content_type,
as_bytes(make_span(content)));
}
void fin(context_type context) {
lptr_->fin(llptr_, context);
}
/// Sets an abort reason on the transport.
void abort_reason(error reason) {
return lptr_->abort_reason(llptr_, std::move(reason));
}
/// Returns the current abort reason on the transport or a
/// default-constructed error is no error occurred yet.
const error& abort_reason() {
return lptr_->abort_reason(llptr_);
}
private:
Layer* lptr_;
LowerLayerPtr llptr_;
};
hypertext_oriented_layer_ptr(Layer* layer, LowerLayerPtr down)
: access_(layer, down) {
// nop
}
hypertext_oriented_layer_ptr(const hypertext_oriented_layer_ptr&) = default;
explicit operator bool() const noexcept {
return true;
}
access* operator->() const noexcept {
return &access_;
}
access& operator*() const noexcept {
return access_;
}
private:
mutable access access_;
};
template <class Layer, class LowerLayerPtr>
auto make_hypertext_oriented_layer_ptr(Layer* this_layer, LowerLayerPtr down) {
using result_t = hypertext_oriented_layer_ptr<Layer, LowerLayerPtr>;
return result_t{this_layer, down};
}
} // namespace caf::net
...@@ -111,6 +111,7 @@ public: ...@@ -111,6 +111,7 @@ public:
if (msg_size > 0 && static_cast<size_t>(msg_size) < max_message_length) { if (msg_size > 0 && static_cast<size_t>(msg_size) < max_message_length) {
auto u32_size = to_network_order(static_cast<uint32_t>(msg_size)); auto u32_size = to_network_order(static_cast<uint32_t>(msg_size));
memcpy(std::addressof(*msg_begin), &u32_size, 4); memcpy(std::addressof(*msg_begin), &u32_size, 4);
down->end_output();
return true; return true;
} else { } else {
auto err = make_error(sec::runtime_error, auto err = make_error(sec::runtime_error,
...@@ -256,9 +257,9 @@ error run_with_length_prefix_framing(multiplexer& mpx, Socket fd, ...@@ -256,9 +257,9 @@ error run_with_length_prefix_framing(multiplexer& mpx, Socket fd,
async::consumer_resource<T> in, async::consumer_resource<T> in,
async::producer_resource<T> out, async::producer_resource<T> out,
Trait trait) { Trait trait) {
using app_t = length_prefix_framing<message_flow_bridge<T, Trait>>; using app_t = Transport<length_prefix_framing<message_flow_bridge<T, Trait>>>;
auto mgr = make_socket_manager<app_t, Transport>(fd, &mpx, std::move(trait)); auto mgr = make_socket_manager<app_t>(fd, &mpx, std::move(in), std::move(out),
mgr->top_layer().connect_flows(mgr.get(), std::move(in), std::move(out)); std::move(trait));
return mgr->init(cfg); return mgr->init(cfg);
} }
......
...@@ -41,29 +41,36 @@ public: ...@@ -41,29 +41,36 @@ public:
using producer_resource_t = async::producer_resource<T>; using producer_resource_t = async::producer_resource<T>;
explicit message_flow_bridge(Trait trait) : trait_(std::move(trait)) { message_flow_bridge(consumer_resource_t in_res, producer_resource_t out_res,
Trait trait)
: in_res_(std::move(in_res)),
out_res_(std::move(out_res)),
trait_(std::move(trait)) {
// nop // nop
} }
void connect_flows(net::socket_manager* mgr, consumer_resource_t in, explicit message_flow_bridge(Trait trait) : trait_(std::move(trait)) {
producer_resource_t out) { // nop
in_ = consumer_adapter<buffer_type>::try_open(mgr, in);
out_ = producer_adapter<buffer_type>::try_open(mgr, out);
} }
template <class LowerLayerPtr> template <class LowerLayerPtr>
error error init(net::socket_manager* mgr, LowerLayerPtr, const settings& cfg) {
init(net::socket_manager* mgr, LowerLayerPtr down, const settings& cfg) {
mgr_ = mgr; mgr_ = mgr;
if constexpr (caf::detail::has_init_v<Trait>) { if constexpr (caf::detail::has_init_v<Trait>) {
if (auto err = init_res(trait_.init(cfg))) if (auto err = init_res(trait_.init(cfg)))
return err; return err;
} }
if (in_res_) {
in_ = consumer_adapter<buffer_type>::try_open(mgr, in_res_);
in_res_ = nullptr;
}
if (out_res_) {
out_ = producer_adapter<buffer_type>::try_open(mgr, out_res_);
out_res_ = nullptr;
}
if (!in_ && !out_) if (!in_ && !out_)
return make_error(sec::cannot_open_resource, return make_error(sec::cannot_open_resource,
"flow bridge cannot run without at least one resource"); "flow bridge cannot run without at least one resource");
if (!out_)
down->suspend_reading();
return none; return none;
} }
...@@ -77,12 +84,12 @@ public: ...@@ -77,12 +84,12 @@ public:
static_assert(std::is_same_v<Tag, tag::mixed_message_oriented>); static_assert(std::is_same_v<Tag, tag::mixed_message_oriented>);
if (trait_.converts_to_binary(item)) { if (trait_.converts_to_binary(item)) {
down->begin_binary_message(); down->begin_binary_message();
auto& buf = down->binary_message_buffer(); auto& bytes = down->binary_message_buffer();
return trait_.convert(item, buf) && down->end_binary_message(); return trait_.convert(item, bytes) && down->end_binary_message();
} else { } else {
down->begin_text_message(); down->begin_text_message();
auto& buf = down->text_message_buffer(); auto& text = down->text_message_buffer();
return trait_.convert(item, buf) && down->end_text_message(); return trait_.convert(item, text) && down->end_text_message();
} }
} }
} }
...@@ -208,7 +215,8 @@ private: ...@@ -208,7 +215,8 @@ private:
} }
error init_res(consumer_resource_t in, producer_resource_t out) { error init_res(consumer_resource_t in, producer_resource_t out) {
connect_flows(mgr_, std::move(in), std::move(out)); in_res_ = std::move(in);
out_res_ = std::move(out);
return caf::none; return caf::none;
} }
...@@ -241,6 +249,12 @@ private: ...@@ -241,6 +249,12 @@ private:
/// Converts between raw bytes and items. /// Converts between raw bytes and items.
Trait trait_; Trait trait_;
/// Discarded after initialization.
consumer_resource_t in_res_;
/// Discarded after initialization.
producer_resource_t out_res_;
}; };
} // namespace caf::net } // namespace caf::net
...@@ -10,9 +10,9 @@ ...@@ -10,9 +10,9 @@
namespace caf::net { namespace caf::net {
/// Wraps a pointer to a mixed-message-oriented layer with a pointer to its /// Wraps a pointer to a message-oriented layer with a pointer to its lower
/// lower layer. Both pointers are then used to implement the interface required /// layer. Both pointers are then used to implement the interface required for a
/// for a mixed-message-oriented layer when calling into its upper layer. /// message-oriented layer when calling into its upper layer.
template <class Layer, class LowerLayerPtr> template <class Layer, class LowerLayerPtr>
class message_oriented_layer_ptr { class message_oriented_layer_ptr {
public: public:
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "caf/net/socket.hpp" #include "caf/net/socket.hpp"
#include "caf/net/typed_actor_shell.hpp" #include "caf/net/typed_actor_shell.hpp"
#include "caf/ref_counted.hpp" #include "caf/ref_counted.hpp"
#include "caf/sec.hpp"
#include "caf/tag/io_event_oriented.hpp" #include "caf/tag/io_event_oriented.hpp"
namespace caf::net { namespace caf::net {
......
...@@ -43,10 +43,6 @@ public: ...@@ -43,10 +43,6 @@ public:
// nop // nop
} }
virtual ~stream_transport() {
// nop
}
// -- interface for stream_oriented_layer_ptr -------------------------------- // -- interface for stream_oriented_layer_ptr --------------------------------
template <class ParentPtr> template <class ParentPtr>
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "caf/hash/sha1.hpp" #include "caf/hash/sha1.hpp"
#include "caf/logger.hpp" #include "caf/logger.hpp"
#include "caf/net/fwd.hpp" #include "caf/net/fwd.hpp"
#include "caf/net/http/v1.hpp"
#include "caf/net/message_flow_bridge.hpp" #include "caf/net/message_flow_bridge.hpp"
#include "caf/net/receive_policy.hpp" #include "caf/net/receive_policy.hpp"
#include "caf/net/web_socket/framing.hpp" #include "caf/net/web_socket/framing.hpp"
...@@ -106,12 +107,10 @@ public: ...@@ -106,12 +107,10 @@ public:
return upper_layer_.consume(down, input, delta); return upper_layer_.consume(down, input, delta);
// Check whether received a HTTP header or else wait for more data or abort // Check whether received a HTTP header or else wait for more data or abort
// when exceeding the maximum size. // when exceeding the maximum size.
auto [hdr, remainder] = handshake::split_http_1_header(input); auto [hdr, remainder] = http::v1::split_header(input);
if (hdr.empty()) { if (hdr.empty()) {
if (input.size() >= handshake::max_http_size) { if (input.size() >= handshake::max_http_size) {
down->begin_output(); CAF_LOG_ERROR("server response exceeded maximum header size");
handshake::write_http_1_header_too_large(down->output_buffer());
down->end_output();
auto err = make_error(pec::too_many_characters, auto err = make_error(pec::too_many_characters,
"exceeded maximum header size"); "exceeded maximum header size");
down->abort_reason(std::move(err)); down->abort_reason(std::move(err));
...@@ -188,8 +187,8 @@ void run_client(multiplexer& mpx, Socket fd, handshake hs, ...@@ -188,8 +187,8 @@ void run_client(multiplexer& mpx, Socket fd, handshake hs,
using app_t = message_flow_bridge<T, Trait, tag::mixed_message_oriented>; using app_t = message_flow_bridge<T, Trait, tag::mixed_message_oriented>;
using stack_t = Transport<client<app_t>>; using stack_t = Transport<client<app_t>>;
auto mgr = make_socket_manager<stack_t>(fd, &mpx, std::move(hs), auto mgr = make_socket_manager<stack_t>(fd, &mpx, std::move(hs),
std::move(in), std::move(out),
std::move(trait)); std::move(trait));
mgr->top_layer().connect_flows(mgr.get(), std::move(in), std::move(out));
mpx.init(mgr); mpx.init(mgr);
} }
......
...@@ -125,13 +125,6 @@ public: ...@@ -125,13 +125,6 @@ public:
/// @pre `has_valid_key()` /// @pre `has_valid_key()`
void write_http_1_response(byte_buffer& buf) const; void write_http_1_response(byte_buffer& buf) const;
/// Writes an HTTP 1.1 'Bad Request' error to `buf` with `descr` providing
/// additional information to the client.
static void write_http_1_bad_request(byte_buffer& buf, string_view descr);
/// Writes a HTTP 1.1 431 (Request Header Fields Too Large) response.
static void write_http_1_header_too_large(byte_buffer& buf);
/// Checks whether the `http_response` contains a HTTP 1.1 response to the /// Checks whether the `http_response` contains a HTTP 1.1 response to the
/// generated HTTP GET request. A valid response contains: /// generated HTTP GET request. A valid response contains:
/// - HTTP status code 101 (Switching Protocols). /// - HTTP status code 101 (Switching Protocols).
...@@ -140,11 +133,6 @@ public: ...@@ -140,11 +133,6 @@ public:
/// - A `Sec-WebSocket-Accept` field with the value `response_key()`. /// - A `Sec-WebSocket-Accept` field with the value `response_key()`.
bool is_valid_http_1_response(string_view http_response) const; bool is_valid_http_1_response(string_view http_response) const;
/// Tries splitting the given byte span into an HTTP header (`first`) and a
/// remainder (`second`). Returns an empty @ref string_view as `first` for
/// incomplete HTTP headers.
static std::pair<string_view, byte_span> split_http_1_header(byte_span bytes);
private: private:
// -- utility ---------------------------------------------------------------- // -- utility ----------------------------------------------------------------
......
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
#include "caf/logger.hpp" #include "caf/logger.hpp"
#include "caf/net/connection_acceptor.hpp" #include "caf/net/connection_acceptor.hpp"
#include "caf/net/fwd.hpp" #include "caf/net/fwd.hpp"
#include "caf/net/http/header.hpp"
#include "caf/net/http/method.hpp"
#include "caf/net/http/status.hpp"
#include "caf/net/http/v1.hpp"
#include "caf/net/message_flow_bridge.hpp" #include "caf/net/message_flow_bridge.hpp"
#include "caf/net/multiplexer.hpp" #include "caf/net/multiplexer.hpp"
#include "caf/net/receive_policy.hpp" #include "caf/net/receive_policy.hpp"
...@@ -92,18 +96,22 @@ public: ...@@ -92,18 +96,22 @@ public:
template <class LowerLayerPtr> template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span input, byte_span delta) { ptrdiff_t consume(LowerLayerPtr down, byte_span input, byte_span delta) {
using namespace caf::literals;
CAF_LOG_TRACE(CAF_ARG2("socket", down->handle().id) CAF_LOG_TRACE(CAF_ARG2("socket", down->handle().id)
<< CAF_ARG2("bytes", input.size())); << CAF_ARG2("bytes", input.size()));
// Short circuit to the framing layer after the handshake completed. // Short circuit to the framing layer after the handshake completed.
if (handshake_complete_) if (handshake_complete_)
return upper_layer_.consume(down, input, delta); return upper_layer_.consume(down, input, delta);
// Check whether received a HTTP header or else wait for more data or abort // Check whether we received an HTTP header or else wait for more data.
// when exceeding the maximum size. // Abort when exceeding the maximum size.
auto [hdr, remainder] = handshake::split_http_1_header(input); auto [hdr, remainder] = http::v1::split_header(input);
if (hdr.empty()) { if (hdr.empty()) {
if (input.size() >= handshake::max_http_size) { if (input.size() >= handshake::max_http_size) {
down->begin_output(); down->begin_output();
handshake::write_http_1_header_too_large(down->output_buffer()); http::v1::write_response(http::status::request_header_fields_too_large,
"text/plain"_sv,
"Header exceeds maximum size."_sv,
down->output_buffer());
down->end_output(); down->end_output();
auto err = make_error(pec::too_many_characters, auto err = make_error(pec::too_many_characters,
"exceeded maximum header size"); "exceeded maximum header size");
...@@ -136,92 +144,68 @@ public: ...@@ -136,92 +144,68 @@ public:
private: private:
// -- HTTP request processing ------------------------------------------------ // -- HTTP request processing ------------------------------------------------
template <class LowerLayerPtr>
void write_response(LowerLayerPtr down, http::status code, string_view msg) {
down->begin_output();
http::v1::write_response(code, "text/plain", msg, down->output_buffer());
down->end_output();
}
template <class LowerLayerPtr> template <class LowerLayerPtr>
bool handle_header(LowerLayerPtr down, string_view http) { bool handle_header(LowerLayerPtr down, string_view http) {
using namespace std::literals; using namespace std::literals;
// Parse the first line, i.e., "METHOD REQUEST-URI VERSION". // Parse the header and reject invalid inputs.
auto [first_line, remainder] = split(http, "\r\n"); http::header hdr;
auto [method, request_uri_str, version] = split2(first_line, " "); auto [code, msg] = hdr.parse(http);
auto& hdr = cfg_["web-socket"].as_dictionary(); if (code != http::status::ok) {
if (method != "GET") { write_response(down, code, msg);
down->begin_output(); down->abort_reason(make_error(pec::invalid_argument, "malformed header"));
handshake::write_http_1_bad_request(down->output_buffer(),
"Expected WebSocket handshake.");
down->end_output();
auto err = make_error(pec::invalid_argument,
"invalid operation: expected GET, got "
+ to_string(method));
down->abort_reason(std::move(err));
return false; return false;
} }
// The path must be absolute. if (hdr.method() != http::method::get) {
if (request_uri_str.empty() || request_uri_str.front() != '/') { write_response(down, http::status::bad_request,
auto descr = "Malformed Request-URI path: expected absolute path."s; "Expected a WebSocket handshake.");
down->begin_output(); auto err = make_error(pec::invalid_argument,
handshake::write_http_1_bad_request(down->output_buffer(), descr); "invalid operation: expected method get, got "
down->end_output(); + to_string(hdr.method()));
down->abort_reason(make_error(pec::invalid_argument, std::move(descr))); down->abort_reason(std::move(err));
return false;
}
// The path must form a valid URI when prefixing a scheme. We don't actually
// care about the scheme, so just use "foo" here for the validation step.
uri request_uri;
if (auto res = make_uri("foo://localhost" + to_string(request_uri_str))) {
request_uri = std::move(*res);
} else {
auto descr = "Malformed Request-URI path: " + to_string(res.error());
descr += '.';
down->begin_output();
handshake::write_http_1_bad_request(down->output_buffer(), descr);
down->end_output();
down->abort_reason(make_error(pec::invalid_argument, std::move(descr)));
return false; return false;
} }
// Store the request information in the settings for the upper layer.
put(hdr, "method", method);
put(hdr, "path", request_uri.path());
put(hdr, "query", request_uri.query());
put(hdr, "fragment", request_uri.fragment());
put(hdr, "http-version", version);
// Store the remaining header fields.
auto& fields = hdr["fields"].as_dictionary();
for_each_line(remainder, [&fields](string_view line) {
if (auto sep = std::find(line.begin(), line.end(), ':');
sep != line.end()) {
auto key = trim({line.begin(), sep});
auto val = trim({sep + 1, line.end()});
if (!key.empty())
put(fields, key, val);
}
});
// Check whether the mandatory fields exist. // Check whether the mandatory fields exist.
handshake hs; auto sec_key = hdr.field("Sec-WebSocket-Key");
if (auto skey_field = get_if<std::string>(&fields, "Sec-WebSocket-Key"); if (sec_key.empty()) {
skey_field && hs.assign_key(*skey_field)) {
CAF_LOG_DEBUG("received Sec-WebSocket-Key" << *skey_field);
} else {
auto descr = "Mandatory field Sec-WebSocket-Key missing or invalid."s; auto descr = "Mandatory field Sec-WebSocket-Key missing or invalid."s;
down->begin_output(); write_response(down, http::status::bad_request, descr);
handshake::write_http_1_bad_request(down->output_buffer(), descr);
down->end_output();
CAF_LOG_DEBUG("received invalid WebSocket handshake"); CAF_LOG_DEBUG("received invalid WebSocket handshake");
down->abort_reason(make_error(pec::missing_field, std::move(descr))); down->abort_reason(make_error(pec::missing_field, std::move(descr)));
return false; return false;
} }
// Try initializing the upper layer. // Store the request information in the settings for the upper layer.
auto& ws = cfg_["web-socket"].as_dictionary();
put(ws, "method", to_rfc_string(hdr.method()));
put(ws, "path", "/" + to_string(hdr.path()));
put(ws, "query", hdr.query());
put(ws, "fragment", hdr.fragment());
put(ws, "http-version", hdr.version());
if (!hdr.fields().empty()) {
auto& fields = ws["fields"].as_dictionary();
for (auto& [key, val] : hdr.fields())
put(fields, to_string(key), to_string(val));
}
// Try to initialize the upper layer.
if (auto err = upper_layer_.init(owner_, down, cfg_)) { if (auto err = upper_layer_.init(owner_, down, cfg_)) {
auto descr = to_string(err); auto descr = to_string(err);
down->begin_output(); CAF_LOG_DEBUG("upper layer rejected a WebSocket connection:" << descr);
handshake::write_http_1_bad_request(down->output_buffer(), descr); write_response(down, http::status::bad_request, descr);
down->end_output();
down->abort_reason(std::move(err)); down->abort_reason(std::move(err));
return false; return false;
} }
// Send server handshake. // Finalize the WebSocket handshake.
handshake hs;
hs.assign_key(sec_key);
down->begin_output(); down->begin_output();
hs.write_http_1_response(down->output_buffer()); hs.write_http_1_response(down->output_buffer());
down->end_output(); down->end_output();
// Done.
CAF_LOG_DEBUG("completed WebSocket handshake"); CAF_LOG_DEBUG("completed WebSocket handshake");
handshake_complete_ = true; handshake_complete_ = true;
return true; return true;
...@@ -293,8 +277,8 @@ socket_manager_ptr make_server(multiplexer& mpx, Socket fd, ...@@ -293,8 +277,8 @@ socket_manager_ptr make_server(multiplexer& mpx, Socket fd,
async::producer_resource<T> out, Trait trait) { async::producer_resource<T> out, Trait trait) {
using app_t = message_flow_bridge<T, Trait, tag::mixed_message_oriented>; using app_t = message_flow_bridge<T, Trait, tag::mixed_message_oriented>;
using stack_t = Transport<server<app_t>>; using stack_t = Transport<server<app_t>>;
auto mgr = make_socket_manager<stack_t>(fd, &mpx, std::move(trait)); auto mgr = make_socket_manager<stack_t>(fd, &mpx, std::move(in),
mgr->top_layer().connect_flows(mgr.get(), std::move(in), std::move(out)); std::move(out), std::move(trait));
return mgr; return mgr;
} }
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "caf/detail/net_export.hpp" #include "caf/detail/net_export.hpp"
#include <cstdint> #include <cstdint>
#include <type_traits>
namespace caf::net::web_socket { namespace caf::net::web_socket {
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
namespace caf::tag {
/// Tags a layer that expects an HTTP-like transport.
struct hypertext_oriented {};
} // namespace caf::tag
#include "caf/net/http/header.hpp"
#include "caf/expected.hpp"
#include "caf/logger.hpp"
namespace caf::net::http {
namespace {
constexpr string_view eol = "\r\n";
template <class F>
bool for_each_line(string_view input, F&& f) {
for (auto pos = input.begin();;) {
auto line_end = std::search(pos, input.end(), eol.begin(), eol.end());
if (line_end == input.end() || std::distance(pos, input.end()) == 2) {
// Reaching the end or hitting the last empty line tells us we're done.
return true;
}
if (!f(string_view{pos, line_end}))
return false;
pos = line_end + eol.size();
}
return true;
}
string_view trim(string_view str) {
str.remove_prefix(std::min(str.find_first_not_of(' '), str.size()));
auto trim_pos = str.find_last_not_of(' ');
if (trim_pos != str.npos)
str.remove_suffix(str.size() - (trim_pos + 1));
return str;
}
/// Splits `str` at the first occurrence of `sep` into the head and the
/// remainder (excluding the separator).
static std::pair<string_view, string_view> split(string_view str,
string_view sep) {
auto i = std::search(str.begin(), str.end(), sep.begin(), sep.end());
if (i != str.end())
return {{str.begin(), i}, {i + sep.size(), str.end()}};
return {{str}, {}};
}
/// Convenience function for splitting twice.
static std::tuple<string_view, string_view, string_view>
split2(string_view str, string_view sep) {
auto [first, r1] = split(str, sep);
auto [second, third] = split(r1, sep);
return {first, second, third};
}
/// @pre `y` is all lowercase
bool case_insensitive_eq(string_view x, string_view y) {
return std::equal(x.begin(), x.end(), y.begin(), y.end(),
[](char a, char b) { return tolower(a) == b; });
}
} // namespace
header::header(const header& other) {
assign(other);
}
header& header::operator=(const header& other) {
assign(other);
return *this;
}
void header::assign(const header& other) {
auto remap = [](const char* base, string_view src, const char* new_base) {
auto offset = std::distance(base, src.data());
return string_view{new_base + offset, src.size()};
};
method_ = other.method_;
uri_ = other.uri_;
if (other.valid()) {
raw_.assign(other.raw_.begin(), other.raw_.end());
auto base = other.raw_.data();
auto new_base = raw_.data();
version_ = remap(base, other.version_, new_base);
auto& fields = fields_.container();
auto& other_fields = other.fields_.container();
fields.resize(other_fields.size());
for (size_t index = 0; index < fields.size(); ++index) {
fields[index].first = remap(base, other_fields[index].first, new_base);
fields[index].second = remap(base, other_fields[index].second, new_base);
}
} else {
raw_.clear();
version_ = string_view{};
fields_.clear();
}
}
std::pair<status, string_view> header::parse(string_view raw) {
CAF_LOG_TRACE(CAF_ARG(raw));
// Sanity checking and copying of the raw input.
using namespace literals;
if (raw.empty()) {
raw_.clear();
return {status::bad_request, "Empty header."};
};
raw_.assign(raw.begin(), raw.end());
// Parse the first line, i.e., "METHOD REQUEST-URI VERSION".
auto [first_line, remainder] = split(string_view{raw_.data(), raw_.size()},
eol);
auto [method_str, request_uri_str, version] = split2(first_line, " ");
// The path must be absolute.
if (request_uri_str.empty() || request_uri_str.front() != '/') {
CAF_LOG_DEBUG("Malformed Request-URI: expected an absolute path.");
raw_.clear();
return {status::bad_request,
"Malformed Request-URI: expected an absolute path."};
}
// The path must form a valid URI when prefixing a scheme. We don't actually
// care about the scheme, so just use "foo" here for the validation step.
if (auto res = make_uri("nil://host" + to_string(request_uri_str))) {
uri_ = std::move(*res);
} else {
CAF_LOG_DEBUG("Failed to parse URI" << request_uri_str << "->"
<< res.error());
raw_.clear();
return {status::bad_request, "Malformed Request-URI."};
}
// Verify and store the method.
if (case_insensitive_eq(method_str, "get")) {
method_ = method::get;
} else if (case_insensitive_eq(method_str, "head")) {
method_ = method::head;
} else if (case_insensitive_eq(method_str, "post")) {
method_ = method::post;
} else if (case_insensitive_eq(method_str, "put")) {
method_ = method::put;
} else if (case_insensitive_eq(method_str, "delete")) {
method_ = method::del;
} else if (case_insensitive_eq(method_str, "connect")) {
method_ = method::connect;
} else if (case_insensitive_eq(method_str, "options")) {
method_ = method::options;
} else if (case_insensitive_eq(method_str, "trace")) {
method_ = method::trace;
} else {
CAF_LOG_DEBUG("Invalid HTTP method.");
raw_.clear();
return {status::bad_request, "Invalid HTTP method."};
}
// Store the remaining header fields.
version_ = version;
fields_.clear();
bool ok = for_each_line(remainder, [this](string_view line) {
if (auto sep = std::find(line.begin(), line.end(), ':');
sep != line.end()) {
auto key = trim({line.begin(), sep});
auto val = trim({sep + 1, line.end()});
if (!key.empty()) {
fields_.emplace(key, val);
return true;
}
}
return false;
});
if (ok) {
return {status::ok, "OK"};
} else {
raw_.clear();
version_ = string_view{};
fields_.clear();
return {status::bad_request, "Malformed header fields."};
}
}
bool header::chunked_transfer_encoding() const {
return field("Transfer-Encoding").find("chunked") != string_view::npos;
}
optional<size_t> header::content_length() const {
return field_as<size_t>("Content-Length");
}
} // namespace caf::net::http
#include "caf/net/http/method.hpp"
namespace caf::net::http {
std::string to_rfc_string(method x) {
using namespace caf::literals;
switch (x) {
case method::get:
return "GET";
case method::head:
return "HEAD";
case method::post:
return "POST";
case method::put:
return "PUT";
case method::del:
return "DELETE";
case method::connect:
return "CONNECT";
case method::options:
return "OPTIONS";
case method::trace:
return "TRACE";
default:
return "INVALID";
}
}
} // namespace caf::net::http
#include "caf/net/http/status.hpp"
namespace caf::net::http {
string_view phrase(status code) {
using namespace caf::literals;
switch (code) {
case status::continue_request:
return "Continue"_sv;
case status::switching_protocols:
return "Switching Protocols"_sv;
case status::ok:
return "OK"_sv;
case status::created:
return "Created"_sv;
case status::accepted:
return "Accepted"_sv;
case status::non_authoritative_information:
return "Non-Authoritative Information"_sv;
case status::no_content:
return "No Content"_sv;
case status::reset_content:
return "Reset Content"_sv;
case status::partial_content:
return "Partial Content"_sv;
case status::multiple_choices:
return "Multiple Choices"_sv;
case status::moved_permanently:
return "Moved Permanently"_sv;
case status::found:
return "Found"_sv;
case status::see_other:
return "See Other"_sv;
case status::not_modified:
return "Not Modified"_sv;
case status::use_proxy:
return "Use Proxy"_sv;
case status::temporary_redirect:
return "Temporary Redirect"_sv;
case status::bad_request:
return "Bad Request"_sv;
case status::unauthorized:
return "Unauthorized"_sv;
case status::payment_required:
return "Payment Required"_sv;
case status::forbidden:
return "Forbidden"_sv;
case status::not_found:
return "Not Found"_sv;
case status::method_not_allowed:
return "Method Not Allowed"_sv;
case status::not_acceptable:
return "Not Acceptable"_sv;
case status::proxy_authentication_required:
return "Proxy Authentication Required"_sv;
case status::request_timeout:
return "Request Timeout"_sv;
case status::conflict:
return "Conflict"_sv;
case status::gone:
return "Gone"_sv;
case status::length_required:
return "Length Required"_sv;
case status::precondition_failed:
return "Precondition Failed"_sv;
case status::payload_too_large:
return "Payload Too Large"_sv;
case status::uri_too_long:
return "URI Too Long"_sv;
case status::unsupported_media_type:
return "Unsupported Media Type"_sv;
case status::range_not_satisfiable:
return "Range Not Satisfiable"_sv;
case status::expectation_failed:
return "Expectation Failed"_sv;
case status::upgrade_required:
return "Upgrade Required"_sv;
case status::precondition_required:
return "Precondition Required"_sv;
case status::too_many_requests:
return "Too Many Requests"_sv;
case status::request_header_fields_too_large:
return "Request Header Fields Too Large"_sv;
case status::internal_server_error:
return "Internal Server Error"_sv;
case status::not_implemented:
return "Not Implemented"_sv;
case status::bad_gateway:
return "Bad Gateway"_sv;
case status::service_unavailable:
return "Service Unavailable"_sv;
case status::gateway_timeout:
return "Gateway Timeout"_sv;
case status::http_version_not_supported:
return "HTTP Version Not Supported"_sv;
case status::network_authentication_required:
return "Network Authentication Required"_sv;
default:
return "Unrecognized";
}
}
} // namespace caf::net::http
#include "caf/net/http/v1.hpp"
#include <array>
#include <string_view>
using namespace std::literals;
namespace caf::net::http::v1 {
namespace {
struct writer {
byte_buffer* buf;
};
writer& operator<<(writer& out, char x) {
out.buf->push_back(static_cast<byte>(x));
return out;
}
writer& operator<<(writer& out, string_view str) {
auto bytes = as_bytes(make_span(str));
out.buf->insert(out.buf->end(), bytes.begin(), bytes.end());
return out;
}
writer& operator<<(writer& out, std::string_view str) {
auto bytes = as_bytes(make_span(str));
out.buf->insert(out.buf->end(), bytes.begin(), bytes.end());
return out;
}
writer& operator<<(writer& out, const std::string& str) {
return out << string_view{str};
}
} // namespace
std::pair<string_view, byte_span> split_header(byte_span bytes) {
std::array<byte, 4> end_of_header{{
byte{'\r'},
byte{'\n'},
byte{'\r'},
byte{'\n'},
}};
if (auto i = std::search(bytes.begin(), bytes.end(), end_of_header.begin(),
end_of_header.end());
i == bytes.end()) {
return {string_view{}, bytes};
} else {
auto offset = static_cast<size_t>(std::distance(bytes.begin(), i));
offset += end_of_header.size();
return {string_view{reinterpret_cast<const char*>(bytes.begin()), offset},
bytes.subspan(offset)};
}
}
void write_header(status code, const header_fields_map& fields,
byte_buffer& buf) {
writer out{&buf};
out << "HTTP/1.1 "sv << std::to_string(static_cast<int>(code)) << ' '
<< phrase(code) << "\r\n"sv;
for (auto& [key, val] : fields)
out << key << ": "sv << val << "\r\n"sv;
out << "\r\n"sv;
}
void write_response(status code, string_view content_type, string_view content,
byte_buffer& buf) {
header_fields_map fields;
write_response(code, content_type, content, fields, buf);
writer out{&buf};
out << content;
}
void write_response(status code, string_view content_type, string_view content,
const header_fields_map& fields, byte_buffer& buf) {
writer out{&buf};
out << "HTTP/1.1 "sv << std::to_string(static_cast<int>(code)) << ' '
<< phrase(code) << "\r\n"sv;
out << "Content-Type: "sv << content_type << "\r\n"sv;
out << "Content-Length: "sv << std::to_string(content.size()) << "\r\n"sv;
for (auto& [key, val] : fields)
out << key << ": "sv << val << "\r\n"sv;
out << "\r\n"sv;
out << content;
}
} // namespace caf::net::http::v1
...@@ -10,11 +10,6 @@ ...@@ -10,11 +10,6 @@
#include <random> #include <random>
#include <tuple> #include <tuple>
#include <iostream>
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/detail/base64.hpp" #include "caf/detail/base64.hpp"
#include "caf/hash/sha1.hpp" #include "caf/hash/sha1.hpp"
...@@ -122,23 +117,6 @@ void handshake::write_http_1_response(byte_buffer& buf) const { ...@@ -122,23 +117,6 @@ void handshake::write_http_1_response(byte_buffer& buf) const {
<< response_key() << "\r\n\r\n"; << response_key() << "\r\n\r\n";
} }
void handshake::write_http_1_bad_request(byte_buffer& buf, string_view descr) {
std::cout<<"BAD REQUEST: "<<descr<<'\n';
writer out{&buf};
out << "HTTP/1.1 400 Bad Request\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
<< descr << "\r\n";
}
void handshake::write_http_1_header_too_large(byte_buffer& buf) {
writer out{&buf};
out << "HTTP/1.1 431 Request Header Fields Too Large\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"Header exceeds 2048 Bytes.\r\n";
}
namespace { namespace {
template <class F> template <class F>
...@@ -244,26 +222,6 @@ bool handshake::is_valid_http_1_response(string_view http_response) const { ...@@ -244,26 +222,6 @@ bool handshake::is_valid_http_1_response(string_view http_response) const {
return checker.ok(); return checker.ok();
} }
std::pair<string_view, byte_span>
handshake::split_http_1_header(byte_span bytes) {
std::array<byte, 4> end_of_header{{
byte{'\r'},
byte{'\n'},
byte{'\r'},
byte{'\n'},
}};
if (auto i = std::search(bytes.begin(), bytes.end(), end_of_header.begin(),
end_of_header.end());
i == bytes.end()) {
return {string_view{}, bytes};
} else {
auto offset = static_cast<size_t>(std::distance(bytes.begin(), i));
offset += end_of_header.size();
return {string_view{reinterpret_cast<const char*>(bytes.begin()), offset},
bytes.subspan(offset)};
}
}
// -- utility ------------------------------------------------------------------ // -- utility ------------------------------------------------------------------
string_view handshake::lookup(string_view field_name) const noexcept { string_view handshake::lookup(string_view field_name) const noexcept {
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.http.server
#include "caf/net/http/server.hpp"
#include "net-test.hpp"
using namespace caf;
using namespace caf::literals;
using namespace std::literals::string_literals;
namespace {
struct app_t {
net::http::header hdr;
caf::byte_buffer payload;
template <class LowerLayerPtr>
error init(net::socket_manager*, LowerLayerPtr, const settings&) {
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr) {
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return true;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
CAF_FAIL("app::abort called: " << reason);
}
template <class LowerLayerPtr>
bool consume(LowerLayerPtr down, net::http::context ctx,
const net::http::header& request_hdr,
caf::const_byte_span body) {
hdr = request_hdr;
down->send_response(ctx, net::http::status::ok, "text/plain",
"Hello world!");
payload.assign(body.begin(), body.end());
return true;
}
string_view field(string_view key) {
if (auto i = hdr.fields().find(key); i != hdr.fields().end())
return i->second;
else
return {};
}
string_view param(string_view key) {
auto& qm = hdr.query();
if (auto i = qm.find(key); i != qm.end())
return i->second;
else
return {};
}
};
using mock_server_type = mock_stream_transport<net::http::server<app_t>>;
} // namespace
BEGIN_FIXTURE_SCOPE(host_fixture)
SCENARIO("the server parses HTTP GET requests into header fields") {
GIVEN("valid HTTP GET request") {
string_view req = "GET /foo/bar?user=foo&pw=bar HTTP/1.1\r\n"
"Host: localhost:8090\r\n"
"User-Agent: AwesomeLib/1.0\r\n"
"Accept-Encoding: gzip\r\n\r\n";
string_view res = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: 12\r\n"
"\r\n"
"Hello world!";
WHEN("sending it to an HTTP server") {
mock_server_type serv;
CHECK_EQ(serv.init(), error{});
serv.push(req);
THEN("the HTTP layer parses the data and calls the application layer") {
CHECK_EQ(serv.handle_input(), static_cast<ptrdiff_t>(req.size()));
auto& app = serv.upper_layer.upper_layer();
auto& hdr = app.hdr;
CHECK_EQ(hdr.method(), net::http::method::get);
CHECK_EQ(hdr.version(), "HTTP/1.1");
CHECK_EQ(hdr.path(), "foo/bar");
CHECK_EQ(app.field("Host"), "localhost:8090");
CHECK_EQ(app.field("User-Agent"), "AwesomeLib/1.0");
CHECK_EQ(app.field("Accept-Encoding"), "gzip");
}
AND("the server properly formats a response from the application layer") {
CHECK_EQ(serv.output_as_str(), res);
}
}
}
}
END_FIXTURE_SCOPE()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment