mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
Reorganize source files
This commit is contained in:
300
include/beast/http/impl/basic_headers.ipp
Normal file
300
include/beast/http/impl/basic_headers.ipp
Normal file
@@ -0,0 +1,300 @@
|
||||
//
|
||||
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_IMPL_BASIC_HEADERS_IPP
|
||||
#define BEAST_HTTP_IMPL_BASIC_HEADERS_IPP
|
||||
|
||||
#include <beast/http/detail/writes.hpp>
|
||||
#include <beast/type_check.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline
|
||||
auto
|
||||
basic_headers_base::begin() const ->
|
||||
const_iterator
|
||||
{
|
||||
return list_.cbegin();
|
||||
}
|
||||
|
||||
inline
|
||||
auto
|
||||
basic_headers_base::end() const ->
|
||||
const_iterator
|
||||
{
|
||||
return list_.cend();
|
||||
}
|
||||
|
||||
inline
|
||||
auto
|
||||
basic_headers_base::cbegin() const ->
|
||||
const_iterator
|
||||
{
|
||||
return list_.cbegin();
|
||||
}
|
||||
|
||||
inline
|
||||
auto
|
||||
basic_headers_base::cend() const ->
|
||||
const_iterator
|
||||
{
|
||||
return list_.cend();
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class Allocator>
|
||||
void
|
||||
basic_headers<Allocator>::
|
||||
delete_all()
|
||||
{
|
||||
for(auto it = list_.begin(); it != list_.end();)
|
||||
{
|
||||
auto& e = *it++;
|
||||
e.~element();
|
||||
alloc_traits::deallocate(
|
||||
this->member(), &e, 1);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
inline
|
||||
void
|
||||
basic_headers<Allocator>::
|
||||
move_assign(basic_headers& other, std::false_type)
|
||||
{
|
||||
if(this->member() != other.member())
|
||||
{
|
||||
copy_from(other);
|
||||
other.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
set_ = std::move(other.set_);
|
||||
list_ = std::move(other.list_);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
inline
|
||||
void
|
||||
basic_headers<Allocator>::
|
||||
move_assign(basic_headers& other, std::true_type)
|
||||
{
|
||||
this->member() = std::move(other.member());
|
||||
set_ = std::move(other.set_);
|
||||
list_ = std::move(other.list_);
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
inline
|
||||
void
|
||||
basic_headers<Allocator>::
|
||||
copy_assign(basic_headers const& other, std::false_type)
|
||||
{
|
||||
copy_from(other);
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
inline
|
||||
void
|
||||
basic_headers<Allocator>::
|
||||
copy_assign(basic_headers const& other, std::true_type)
|
||||
{
|
||||
this->member() = other.member();
|
||||
copy_from(other);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class Allocator>
|
||||
basic_headers<Allocator>::
|
||||
~basic_headers()
|
||||
{
|
||||
delete_all();
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
basic_headers<Allocator>::
|
||||
basic_headers(Allocator const& alloc)
|
||||
: beast::detail::empty_base_optimization<
|
||||
alloc_type>(alloc)
|
||||
{
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
basic_headers<Allocator>::
|
||||
basic_headers(basic_headers&& other)
|
||||
: beast::detail::empty_base_optimization<alloc_type>(
|
||||
std::move(other.member()))
|
||||
, detail::basic_headers_base(
|
||||
std::move(other.set_), std::move(other.list_))
|
||||
{
|
||||
other.list_.clear();
|
||||
other.set_.clear();
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
auto
|
||||
basic_headers<Allocator>::
|
||||
operator=(basic_headers&& other) ->
|
||||
basic_headers&
|
||||
{
|
||||
if(this == &other)
|
||||
return *this;
|
||||
clear();
|
||||
move_assign(other, std::integral_constant<bool,
|
||||
alloc_traits::propagate_on_container_move_assignment::value>{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
basic_headers<Allocator>::
|
||||
basic_headers(basic_headers const& other)
|
||||
: basic_headers(alloc_traits::
|
||||
select_on_container_copy_construction(other.member()))
|
||||
{
|
||||
copy_from(other);
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
auto
|
||||
basic_headers<Allocator>::
|
||||
operator=(basic_headers const& other) ->
|
||||
basic_headers&
|
||||
{
|
||||
clear();
|
||||
copy_assign(other, std::integral_constant<bool,
|
||||
alloc_traits::propagate_on_container_copy_assignment::value>{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
template<class OtherAlloc>
|
||||
basic_headers<Allocator>::
|
||||
basic_headers(basic_headers<OtherAlloc> const& other)
|
||||
{
|
||||
copy_from(other);
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
template<class OtherAlloc>
|
||||
auto
|
||||
basic_headers<Allocator>::
|
||||
operator=(basic_headers<OtherAlloc> const& other) ->
|
||||
basic_headers&
|
||||
{
|
||||
clear();
|
||||
copy_from(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
template<class FwdIt>
|
||||
basic_headers<Allocator>::
|
||||
basic_headers(FwdIt first, FwdIt last)
|
||||
{
|
||||
for(;first != last; ++first)
|
||||
insert(first->name(), first->value());
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
auto
|
||||
basic_headers<Allocator>::
|
||||
find(boost::string_ref const& name) const ->
|
||||
iterator
|
||||
{
|
||||
auto const it = set_.find(name, less{});
|
||||
if(it == set_.end())
|
||||
return list_.end();
|
||||
return list_.iterator_to(*it);
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
boost::string_ref
|
||||
basic_headers<Allocator>::
|
||||
operator[](boost::string_ref const& name) const
|
||||
{
|
||||
// VFALCO This none object looks sketchy
|
||||
static boost::string_ref const none;
|
||||
auto const it = find(name);
|
||||
if(it == end())
|
||||
return none;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
void
|
||||
basic_headers<Allocator>::
|
||||
clear() noexcept
|
||||
{
|
||||
delete_all();
|
||||
list_.clear();
|
||||
set_.clear();
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
std::size_t
|
||||
basic_headers<Allocator>::
|
||||
erase(boost::string_ref const& name)
|
||||
{
|
||||
auto const it = set_.find(name, less{});
|
||||
if(it == set_.end())
|
||||
return 0;
|
||||
auto& e = *it;
|
||||
set_.erase(set_.iterator_to(e));
|
||||
list_.erase(list_.iterator_to(e));
|
||||
alloc_traits::deallocate(this->member(), &e, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
void
|
||||
basic_headers<Allocator>::
|
||||
insert(boost::string_ref const& name,
|
||||
boost::string_ref const& value)
|
||||
{
|
||||
typename set_t::insert_commit_data d;
|
||||
auto const result =
|
||||
set_.insert_check(name, less{}, d);
|
||||
if (result.second)
|
||||
{
|
||||
auto const p = alloc_traits::allocate(
|
||||
this->member(), 1);
|
||||
alloc_traits::construct(
|
||||
this->member(), p, name, value);
|
||||
list_.push_back(*p);
|
||||
set_.insert_commit(*p, d);
|
||||
return;
|
||||
}
|
||||
// If field already exists, insert comma
|
||||
// separated value as per RFC2616 section 4.2
|
||||
auto& cur = result.first->data.second;
|
||||
cur.reserve(cur.size() + 1 + value.size());
|
||||
cur.append(1, ',');
|
||||
cur.append(value.data(), value.size());
|
||||
}
|
||||
|
||||
template<class Allocator>
|
||||
void
|
||||
basic_headers<Allocator>::
|
||||
replace(boost::string_ref const& name,
|
||||
boost::string_ref const& value)
|
||||
{
|
||||
erase(name);
|
||||
insert(name, value);
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
||||
316
include/beast/http/impl/basic_parser.ipp
Normal file
316
include/beast/http/impl/basic_parser.ipp
Normal file
@@ -0,0 +1,316 @@
|
||||
//
|
||||
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_IMPL_BASIC_PARSER_IPP
|
||||
#define BEAST_HTTP_IMPL_BASIC_PARSER_IPP
|
||||
|
||||
#include <beast/http/impl/http_parser.h>
|
||||
#include <beast/http/rfc2616.hpp>
|
||||
#include <beast/http/detail/error.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline
|
||||
beast::http::method_t
|
||||
convert_http_method(http_method m)
|
||||
{
|
||||
using namespace beast;
|
||||
switch (m)
|
||||
{
|
||||
case HTTP_DELETE: return http::method_t::http_delete;
|
||||
case HTTP_GET: return http::method_t::http_get;
|
||||
case HTTP_HEAD: return http::method_t::http_head;
|
||||
case HTTP_POST: return http::method_t::http_post;
|
||||
case HTTP_PUT: return http::method_t::http_put;
|
||||
|
||||
// pathological
|
||||
case HTTP_CONNECT: return http::method_t::http_connect;
|
||||
case HTTP_OPTIONS: return http::method_t::http_options;
|
||||
case HTTP_TRACE: return http::method_t::http_trace;
|
||||
|
||||
// webdav
|
||||
case HTTP_COPY: return http::method_t::http_copy;
|
||||
case HTTP_LOCK: return http::method_t::http_lock;
|
||||
case HTTP_MKCOL: return http::method_t::http_mkcol;
|
||||
case HTTP_MOVE: return http::method_t::http_move;
|
||||
case HTTP_PROPFIND: return http::method_t::http_propfind;
|
||||
case HTTP_PROPPATCH: return http::method_t::http_proppatch;
|
||||
case HTTP_SEARCH: return http::method_t::http_search;
|
||||
case HTTP_UNLOCK: return http::method_t::http_unlock;
|
||||
case HTTP_BIND: return http::method_t::http_bind;
|
||||
case HTTP_REBIND: return http::method_t::http_rebind;
|
||||
case HTTP_UNBIND: return http::method_t::http_unbind;
|
||||
case HTTP_ACL: return http::method_t::http_acl;
|
||||
|
||||
// subversion
|
||||
case HTTP_REPORT: return http::method_t::http_report;
|
||||
case HTTP_MKACTIVITY: return http::method_t::http_mkactivity;
|
||||
case HTTP_CHECKOUT: return http::method_t::http_checkout;
|
||||
case HTTP_MERGE: return http::method_t::http_merge;
|
||||
|
||||
// upnp
|
||||
case HTTP_MSEARCH: return http::method_t::http_msearch;
|
||||
case HTTP_NOTIFY: return http::method_t::http_notify;
|
||||
case HTTP_SUBSCRIBE: return http::method_t::http_subscribe;
|
||||
case HTTP_UNSUBSCRIBE: return http::method_t::http_unsubscribe;
|
||||
|
||||
// RFC-5789
|
||||
case HTTP_PATCH: return http::method_t::http_patch;
|
||||
case HTTP_PURGE: return http::method_t::http_purge;
|
||||
|
||||
// CalDav
|
||||
case HTTP_MKCALENDAR: return http::method_t::http_mkcalendar;
|
||||
|
||||
// RFC-2068, section 19.6.1.2
|
||||
case HTTP_LINK: return http::method_t::http_link;
|
||||
case HTTP_UNLINK: return http::method_t::http_unlink;
|
||||
};
|
||||
|
||||
return http::method_t::http_get;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
template<class Derived>
|
||||
basic_parser<Derived>::
|
||||
basic_parser(basic_parser&& other)
|
||||
{
|
||||
state_ = other.state_;
|
||||
state_.data = this;
|
||||
complete_ = other.complete_;
|
||||
url_ = std::move(other.url_);
|
||||
status_ = std::move(other.status_);
|
||||
field_ = std::move(other.field_);
|
||||
value_ = std::move(other.value_);
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
auto
|
||||
basic_parser<Derived>::operator=(basic_parser&& other) ->
|
||||
basic_parser&
|
||||
{
|
||||
state_ = other.state_;
|
||||
state_.data = this;
|
||||
complete_ = other.complete_;
|
||||
url_ = std::move(other.url_);
|
||||
status_ = std::move(other.status_);
|
||||
field_ = std::move(other.field_);
|
||||
value_ = std::move(other.value_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
basic_parser<Derived>::
|
||||
basic_parser(basic_parser const& other)
|
||||
{
|
||||
state_ = other.state_;
|
||||
state_.data = this;
|
||||
complete_ = other.complete_;
|
||||
url_ = other.url_;
|
||||
status_ = other.status_;
|
||||
field_ = other.field_;
|
||||
value_ = other.value_;
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
auto
|
||||
basic_parser<Derived>::
|
||||
operator=(basic_parser const& other) ->
|
||||
basic_parser&
|
||||
{
|
||||
state_ = other.state_;
|
||||
state_.data = this;
|
||||
complete_ = other.complete_;
|
||||
url_ = other.url_;
|
||||
status_ = other.status_;
|
||||
field_ = other.field_;
|
||||
value_ = other.value_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
basic_parser<Derived>::basic_parser(bool request) noexcept
|
||||
{
|
||||
state_.data = this;
|
||||
http_parser_init(&state_, request
|
||||
? http_parser_type::HTTP_REQUEST
|
||||
: http_parser_type::HTTP_RESPONSE);
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
std::size_t
|
||||
basic_parser<Derived>::write(void const* data,
|
||||
std::size_t size, error_code& ec)
|
||||
{
|
||||
ec_ = &ec;
|
||||
auto const n = http_parser_execute(
|
||||
&state_, hooks(),
|
||||
static_cast<const char*>(data), size);
|
||||
if(! ec)
|
||||
ec = detail::make_error(
|
||||
static_cast<int>(state_.http_errno));
|
||||
if(ec)
|
||||
return 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
void
|
||||
basic_parser<Derived>::write_eof(error_code& ec)
|
||||
{
|
||||
ec_ = &ec;
|
||||
http_parser_execute(
|
||||
&state_, hooks(), nullptr, 0);
|
||||
if(! ec)
|
||||
ec = detail::make_error(
|
||||
static_cast<int>(state_.http_errno));
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
void
|
||||
basic_parser<Derived>::check_header()
|
||||
{
|
||||
if (! value_.empty())
|
||||
{
|
||||
rfc2616::trim_right_in_place(value_);
|
||||
call_on_field(field_, value_,
|
||||
has_on_field<Derived>{});
|
||||
field_.clear();
|
||||
value_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
int
|
||||
basic_parser<Derived>::cb_message_start(http_parser* p)
|
||||
{
|
||||
auto& t = *reinterpret_cast<basic_parser*>(p->data);
|
||||
t.complete_ = false;
|
||||
t.url_.clear();
|
||||
t.status_.clear();
|
||||
t.field_.clear();
|
||||
t.value_.clear();
|
||||
t.call_on_start(has_on_start<Derived>{});
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
int
|
||||
basic_parser<Derived>::cb_url(http_parser* p,
|
||||
char const* in, std::size_t bytes)
|
||||
{
|
||||
auto& t = *reinterpret_cast<basic_parser*>(p->data);
|
||||
t.url_.append(in, bytes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
int
|
||||
basic_parser<Derived>::cb_status(http_parser* p,
|
||||
char const* in, std::size_t bytes)
|
||||
{
|
||||
auto& t = *reinterpret_cast<basic_parser*>(p->data);
|
||||
t.status_.append(in, bytes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
int
|
||||
basic_parser<Derived>::cb_header_field(http_parser* p,
|
||||
char const* in, std::size_t bytes)
|
||||
{
|
||||
auto& t = *reinterpret_cast<basic_parser*>(p->data);
|
||||
t.check_header();
|
||||
t.field_.append(in, bytes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
int
|
||||
basic_parser<Derived>::cb_header_value(http_parser* p,
|
||||
char const* in, std::size_t bytes)
|
||||
{
|
||||
auto& t = *reinterpret_cast<basic_parser*>(p->data);
|
||||
t.value_.append(in, bytes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called when all the headers are complete but before
|
||||
the content body, if present.
|
||||
Returning 1 from here tells the nodejs parser
|
||||
that the message has no body (e.g. a HEAD request).
|
||||
*/
|
||||
template<class Derived>
|
||||
int
|
||||
basic_parser<Derived>::cb_headers_complete(http_parser* p)
|
||||
{
|
||||
auto& t = *reinterpret_cast<basic_parser*>(p->data);
|
||||
t.check_header();
|
||||
t.call_on_headers_complete(*t.ec_,
|
||||
has_on_headers_complete<Derived>{});
|
||||
if(*t.ec_)
|
||||
return 1;
|
||||
bool const keep_alive =
|
||||
http_should_keep_alive(p) != 0;
|
||||
if(p->type == http_parser_type::HTTP_REQUEST)
|
||||
{
|
||||
t.call_on_request(detail::convert_http_method(
|
||||
http_method(p->method)), t.url_,
|
||||
p->http_major, p->http_minor, keep_alive,
|
||||
p->upgrade, has_on_request<Derived>{});
|
||||
return 0;
|
||||
}
|
||||
return t.call_on_response(p->status_code, t.status_,
|
||||
p->http_major, p->http_minor, keep_alive,
|
||||
p->upgrade, has_on_response<Derived>{}) ? 0 : 1;
|
||||
}
|
||||
|
||||
// Called repeatedly for the content body,
|
||||
// after any transfer-encoding is applied.
|
||||
template<class Derived>
|
||||
int
|
||||
basic_parser<Derived>::cb_body(http_parser* p,
|
||||
char const* in, std::size_t bytes)
|
||||
{
|
||||
auto& t = *reinterpret_cast<basic_parser*>(p->data);
|
||||
t.call_on_body(in, bytes, *t.ec_, has_on_body<Derived>{});
|
||||
return *t.ec_ ? 1 : 0;
|
||||
}
|
||||
|
||||
// Called when the both the headers
|
||||
// and content body (if any) are complete.
|
||||
template<class Derived>
|
||||
int
|
||||
basic_parser<Derived>::cb_message_complete(http_parser* p)
|
||||
{
|
||||
auto& t = *reinterpret_cast<basic_parser*>(p->data);
|
||||
t.complete_ = true;
|
||||
t.call_on_complete(has_on_complete<Derived>{});
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
int
|
||||
basic_parser<Derived>::cb_chunk_header(http_parser*)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class Derived>
|
||||
int
|
||||
basic_parser<Derived>::cb_chunk_complete(http_parser*)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
||||
362
include/beast/http/impl/http_parser.h
Normal file
362
include/beast/http/impl/http_parser.h
Normal file
@@ -0,0 +1,362 @@
|
||||
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef http_parser_h
|
||||
#define http_parser_h
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Also update SONAME in the Makefile whenever you change these. */
|
||||
#define HTTP_PARSER_VERSION_MAJOR 2
|
||||
#define HTTP_PARSER_VERSION_MINOR 7
|
||||
#define HTTP_PARSER_VERSION_PATCH 0
|
||||
|
||||
#include <sys/types.h>
|
||||
#if defined(_WIN32) && !defined(__MINGW32__) && \
|
||||
(!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
|
||||
#include <BaseTsd.h>
|
||||
#include <stddef.h>
|
||||
typedef __int8 int8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef __int16 int16_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
|
||||
* faster
|
||||
*/
|
||||
#ifndef HTTP_PARSER_STRICT
|
||||
# define HTTP_PARSER_STRICT 1
|
||||
#endif
|
||||
|
||||
/* Maximium header size allowed. If the macro is not defined
|
||||
* before including this header then the default is used. To
|
||||
* change the maximum header size, define the macro in the build
|
||||
* environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
|
||||
* the effective limit on the size of the header, define the macro
|
||||
* to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
|
||||
*/
|
||||
#ifndef HTTP_MAX_HEADER_SIZE
|
||||
# define HTTP_MAX_HEADER_SIZE (80*1024)
|
||||
#endif
|
||||
|
||||
typedef struct http_parser http_parser;
|
||||
typedef struct http_parser_settings http_parser_settings;
|
||||
|
||||
|
||||
/* Callbacks should return non-zero to indicate an error. The parser will
|
||||
* then halt execution.
|
||||
*
|
||||
* The one exception is on_headers_complete. In a HTTP_RESPONSE parser
|
||||
* returning '1' from on_headers_complete will tell the parser that it
|
||||
* should not expect a body. This is used when receiving a response to a
|
||||
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
|
||||
* chunked' headers that indicate the presence of a body.
|
||||
*
|
||||
* Returning `2` from on_headers_complete will tell parser that it should not
|
||||
* expect neither a body nor any futher responses on this connection. This is
|
||||
* useful for handling responses to a CONNECT request which may not contain
|
||||
* `Upgrade` or `Connection: upgrade` headers.
|
||||
*
|
||||
* http_data_cb does not return data chunks. It will be called arbitrarily
|
||||
* many times for each string. E.G. you might get 10 callbacks for "on_url"
|
||||
* each providing just a few characters more data.
|
||||
*/
|
||||
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
|
||||
typedef int (*http_cb) (http_parser*);
|
||||
|
||||
|
||||
/* Request Methods */
|
||||
#define HTTP_METHOD_MAP(XX) \
|
||||
XX(0, DELETE, DELETE) \
|
||||
XX(1, GET, GET) \
|
||||
XX(2, HEAD, HEAD) \
|
||||
XX(3, POST, POST) \
|
||||
XX(4, PUT, PUT) \
|
||||
/* pathological */ \
|
||||
XX(5, CONNECT, CONNECT) \
|
||||
XX(6, OPTIONS, OPTIONS) \
|
||||
XX(7, TRACE, TRACE) \
|
||||
/* WebDAV */ \
|
||||
XX(8, COPY, COPY) \
|
||||
XX(9, LOCK, LOCK) \
|
||||
XX(10, MKCOL, MKCOL) \
|
||||
XX(11, MOVE, MOVE) \
|
||||
XX(12, PROPFIND, PROPFIND) \
|
||||
XX(13, PROPPATCH, PROPPATCH) \
|
||||
XX(14, SEARCH, SEARCH) \
|
||||
XX(15, UNLOCK, UNLOCK) \
|
||||
XX(16, BIND, BIND) \
|
||||
XX(17, REBIND, REBIND) \
|
||||
XX(18, UNBIND, UNBIND) \
|
||||
XX(19, ACL, ACL) \
|
||||
/* subversion */ \
|
||||
XX(20, REPORT, REPORT) \
|
||||
XX(21, MKACTIVITY, MKACTIVITY) \
|
||||
XX(22, CHECKOUT, CHECKOUT) \
|
||||
XX(23, MERGE, MERGE) \
|
||||
/* upnp */ \
|
||||
XX(24, MSEARCH, M-SEARCH) \
|
||||
XX(25, NOTIFY, NOTIFY) \
|
||||
XX(26, SUBSCRIBE, SUBSCRIBE) \
|
||||
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
|
||||
/* RFC-5789 */ \
|
||||
XX(28, PATCH, PATCH) \
|
||||
XX(29, PURGE, PURGE) \
|
||||
/* CalDAV */ \
|
||||
XX(30, MKCALENDAR, MKCALENDAR) \
|
||||
/* RFC-2068, section 19.6.1.2 */ \
|
||||
XX(31, LINK, LINK) \
|
||||
XX(32, UNLINK, UNLINK) \
|
||||
|
||||
enum http_method
|
||||
{
|
||||
#define XX(num, name, string) HTTP_##name = num,
|
||||
HTTP_METHOD_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
|
||||
|
||||
|
||||
/* Flag values for http_parser.flags field */
|
||||
enum flags
|
||||
{ F_CHUNKED = 1 << 0
|
||||
, F_CONNECTION_KEEP_ALIVE = 1 << 1
|
||||
, F_CONNECTION_CLOSE = 1 << 2
|
||||
, F_CONNECTION_UPGRADE = 1 << 3
|
||||
, F_TRAILING = 1 << 4
|
||||
, F_UPGRADE = 1 << 5
|
||||
, F_SKIPBODY = 1 << 6
|
||||
, F_CONTENTLENGTH = 1 << 7
|
||||
};
|
||||
|
||||
|
||||
/* Map for errno-related constants
|
||||
*
|
||||
* The provided argument should be a macro that takes 2 arguments.
|
||||
*/
|
||||
#define HTTP_ERRNO_MAP(XX) \
|
||||
/* No error */ \
|
||||
XX(OK, "success") \
|
||||
\
|
||||
/* Callback-related errors */ \
|
||||
XX(CB_message_begin, "the on_message_begin callback failed") \
|
||||
XX(CB_url, "the on_url callback failed") \
|
||||
XX(CB_header_field, "the on_header_field callback failed") \
|
||||
XX(CB_header_value, "the on_header_value callback failed") \
|
||||
XX(CB_headers_complete, "the on_headers_complete callback failed") \
|
||||
XX(CB_body, "the on_body callback failed") \
|
||||
XX(CB_message_complete, "the on_message_complete callback failed") \
|
||||
XX(CB_status, "the on_status callback failed") \
|
||||
XX(CB_chunk_header, "the on_chunk_header callback failed") \
|
||||
XX(CB_chunk_complete, "the on_chunk_complete callback failed") \
|
||||
\
|
||||
/* Parsing-related errors */ \
|
||||
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
|
||||
XX(HEADER_OVERFLOW, \
|
||||
"too many header bytes seen; overflow detected") \
|
||||
XX(CLOSED_CONNECTION, \
|
||||
"data received after completed connection: close message") \
|
||||
XX(INVALID_VERSION, "invalid HTTP version") \
|
||||
XX(INVALID_STATUS, "invalid HTTP status code") \
|
||||
XX(INVALID_METHOD, "invalid HTTP method") \
|
||||
XX(INVALID_URL, "invalid URL") \
|
||||
XX(INVALID_HOST, "invalid host") \
|
||||
XX(INVALID_PORT, "invalid port") \
|
||||
XX(INVALID_PATH, "invalid path") \
|
||||
XX(INVALID_QUERY_STRING, "invalid query string") \
|
||||
XX(INVALID_FRAGMENT, "invalid fragment") \
|
||||
XX(LF_EXPECTED, "LF character expected") \
|
||||
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
|
||||
XX(INVALID_CONTENT_LENGTH, \
|
||||
"invalid character in content-length header") \
|
||||
XX(UNEXPECTED_CONTENT_LENGTH, \
|
||||
"unexpected content-length header") \
|
||||
XX(INVALID_CHUNK_SIZE, \
|
||||
"invalid character in chunk size header") \
|
||||
XX(INVALID_CONSTANT, "invalid constant string") \
|
||||
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
|
||||
XX(STRICT, "strict mode assertion failed") \
|
||||
XX(PAUSED, "parser is paused") \
|
||||
XX(UNKNOWN, "an unknown error occurred")
|
||||
|
||||
|
||||
/* Define HPE_* values for each errno value above */
|
||||
#define HTTP_ERRNO_GEN(n, s) HPE_##n,
|
||||
enum http_errno {
|
||||
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
|
||||
};
|
||||
#undef HTTP_ERRNO_GEN
|
||||
|
||||
|
||||
/* Get an http_errno value from an http_parser */
|
||||
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
|
||||
|
||||
|
||||
struct http_parser {
|
||||
/** PRIVATE **/
|
||||
unsigned int type : 2; /* enum http_parser_type */
|
||||
unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
|
||||
unsigned int state : 7; /* enum state from http_parser.c */
|
||||
unsigned int header_state : 7; /* enum header_state from http_parser.c */
|
||||
unsigned int index : 7; /* index into current matcher */
|
||||
unsigned int lenient_http_headers : 1;
|
||||
|
||||
uint32_t nread; /* # bytes read in various scenarios */
|
||||
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
|
||||
|
||||
/** READ-ONLY **/
|
||||
unsigned short http_major;
|
||||
unsigned short http_minor;
|
||||
unsigned int status_code : 16; /* responses only */
|
||||
unsigned int method : 8; /* requests only */
|
||||
unsigned int http_errno : 7;
|
||||
|
||||
/* 1 = Upgrade header was present and the parser has exited because of that.
|
||||
* 0 = No upgrade header present.
|
||||
* Should be checked when http_parser_execute() returns in addition to
|
||||
* error checking.
|
||||
*/
|
||||
unsigned int upgrade : 1;
|
||||
|
||||
/** PUBLIC **/
|
||||
void *data; /* A pointer to get hook to the "connection" or "socket" object */
|
||||
};
|
||||
|
||||
|
||||
struct http_parser_settings {
|
||||
http_cb on_message_begin;
|
||||
http_data_cb on_url;
|
||||
http_data_cb on_status;
|
||||
http_data_cb on_header_field;
|
||||
http_data_cb on_header_value;
|
||||
http_cb on_headers_complete;
|
||||
http_data_cb on_body;
|
||||
http_cb on_message_complete;
|
||||
/* When on_chunk_header is called, the current chunk length is stored
|
||||
* in parser->content_length.
|
||||
*/
|
||||
http_cb on_chunk_header;
|
||||
http_cb on_chunk_complete;
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_url_fields
|
||||
{ UF_SCHEMA = 0
|
||||
, UF_HOST = 1
|
||||
, UF_PORT = 2
|
||||
, UF_PATH = 3
|
||||
, UF_QUERY = 4
|
||||
, UF_FRAGMENT = 5
|
||||
, UF_USERINFO = 6
|
||||
, UF_MAX = 7
|
||||
};
|
||||
|
||||
|
||||
/* Result structure for http_parser_parse_url().
|
||||
*
|
||||
* Callers should index into field_data[] with UF_* values iff field_set
|
||||
* has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
|
||||
* because we probably have padding left over), we convert any port to
|
||||
* a uint16_t.
|
||||
*/
|
||||
struct http_parser_url {
|
||||
uint16_t field_set; /* Bitmask of (1 << UF_*) values */
|
||||
uint16_t port; /* Converted UF_PORT string */
|
||||
|
||||
struct {
|
||||
uint16_t off; /* Offset into buffer in which field starts */
|
||||
uint16_t len; /* Length of run in buffer */
|
||||
} field_data[UF_MAX];
|
||||
};
|
||||
|
||||
|
||||
/* Returns the library version. Bits 16-23 contain the major version number,
|
||||
* bits 8-15 the minor version number and bits 0-7 the patch level.
|
||||
* Usage example:
|
||||
*
|
||||
* unsigned long version = http_parser_version();
|
||||
* unsigned major = (version >> 16) & 255;
|
||||
* unsigned minor = (version >> 8) & 255;
|
||||
* unsigned patch = version & 255;
|
||||
* printf("http_parser v%u.%u.%u\n", major, minor, patch);
|
||||
*/
|
||||
unsigned long http_parser_version(void);
|
||||
|
||||
void http_parser_init(http_parser *parser, enum http_parser_type type);
|
||||
|
||||
|
||||
/* Initialize http_parser_settings members to 0
|
||||
*/
|
||||
void http_parser_settings_init(http_parser_settings *settings);
|
||||
|
||||
|
||||
/* Executes the parser. Returns number of parsed bytes. Sets
|
||||
* `parser->http_errno` on error. */
|
||||
size_t http_parser_execute(http_parser *parser,
|
||||
const http_parser_settings *settings,
|
||||
const char *data,
|
||||
size_t len);
|
||||
|
||||
|
||||
/* If http_should_keep_alive() in the on_headers_complete or
|
||||
* on_message_complete callback returns 0, then this should be
|
||||
* the last message on the connection.
|
||||
* If you are the server, respond with the "Connection: close" header.
|
||||
* If you are the client, close the connection.
|
||||
*/
|
||||
int http_should_keep_alive(const http_parser *parser);
|
||||
|
||||
/* Returns a string version of the HTTP method. */
|
||||
const char *http_method_str(enum http_method m);
|
||||
|
||||
/* Return a string name of the given error */
|
||||
const char *http_errno_name(enum http_errno err);
|
||||
|
||||
/* Return a string description of the given error */
|
||||
const char *http_errno_description(enum http_errno err);
|
||||
|
||||
/* Initialize all http_parser_url members to 0 */
|
||||
void http_parser_url_init(struct http_parser_url *u);
|
||||
|
||||
/* Parse a URL; return nonzero on failure */
|
||||
int http_parser_parse_url(const char *buf, size_t buflen,
|
||||
int is_connect,
|
||||
struct http_parser_url *u);
|
||||
|
||||
/* Pause or un-pause the parser; a nonzero value pauses */
|
||||
void http_parser_pause(http_parser *parser, int paused);
|
||||
|
||||
/* Checks if this is the final chunk of the body. */
|
||||
int http_body_is_final(const http_parser *parser);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
308
include/beast/http/impl/message.ipp
Normal file
308
include/beast/http/impl/message.ipp
Normal file
@@ -0,0 +1,308 @@
|
||||
//
|
||||
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_IMPL_MESSAGE_IPP
|
||||
#define BEAST_HTTP_IMPL_MESSAGE_IPP
|
||||
|
||||
#include <beast/http/chunk_encode.hpp>
|
||||
#include <beast/http/type_check.hpp>
|
||||
#include <beast/http/detail/writes.hpp>
|
||||
#include <beast/http/detail/write_preparation.hpp>
|
||||
#include <beast/http/rfc2616.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
template<bool isRequest, class Body, class Headers>
|
||||
message<isRequest, Body, Headers>::
|
||||
message()
|
||||
{
|
||||
static_assert(is_Body<Body>::value,
|
||||
"Body requirements not met");
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Headers>
|
||||
message<isRequest, Body, Headers>::
|
||||
message(request_params params)
|
||||
{
|
||||
static_assert(is_Body<Body>::value,
|
||||
"Body requirements not met");
|
||||
static_assert(isRequest, "message is not a request");
|
||||
this->method = params.method;
|
||||
this->url = std::move(params.url);
|
||||
version = params.version;
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Headers>
|
||||
message<isRequest, Body, Headers>::
|
||||
message(response_params params)
|
||||
{
|
||||
static_assert(is_Body<Body>::value,
|
||||
"Body requirements not met");
|
||||
static_assert(! isRequest, "message is not a response");
|
||||
this->status = params.status;
|
||||
this->reason = std::move(params.reason);
|
||||
version = params.version;
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Headers>
|
||||
template<class Streambuf>
|
||||
void
|
||||
message<isRequest, Body, Headers>::
|
||||
write_firstline(Streambuf& streambuf,
|
||||
std::true_type) const
|
||||
{
|
||||
detail::write(streambuf, to_string(this->method));
|
||||
detail::write(streambuf, " ");
|
||||
detail::write(streambuf, this->url);
|
||||
switch(version)
|
||||
{
|
||||
case 10:
|
||||
detail::write(streambuf, " HTTP/1.0\r\n");
|
||||
break;
|
||||
case 11:
|
||||
detail::write(streambuf, " HTTP/1.1\r\n");
|
||||
break;
|
||||
default:
|
||||
detail::write(streambuf, " HTTP/");
|
||||
detail::write(streambuf, version / 10);
|
||||
detail::write(streambuf, ".");
|
||||
detail::write(streambuf, version % 10);
|
||||
detail::write(streambuf, "\r\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Headers>
|
||||
template<class Streambuf>
|
||||
void
|
||||
message<isRequest, Body, Headers>::
|
||||
write_firstline(Streambuf& streambuf,
|
||||
std::false_type) const
|
||||
{
|
||||
switch(version)
|
||||
{
|
||||
case 10:
|
||||
detail::write(streambuf, "HTTP/1.0 ");
|
||||
break;
|
||||
case 11:
|
||||
detail::write(streambuf, "HTTP/1.1 ");
|
||||
break;
|
||||
default:
|
||||
detail::write(streambuf, " HTTP/");
|
||||
detail::write(streambuf, version / 10);
|
||||
detail::write(streambuf, ".");
|
||||
detail::write(streambuf, version % 10);
|
||||
detail::write(streambuf, " ");
|
||||
break;
|
||||
}
|
||||
detail::write(streambuf, this->status);
|
||||
detail::write(streambuf, " ");
|
||||
detail::write(streambuf, this->reason);
|
||||
detail::write(streambuf, "\r\n");
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<class ConstBufferSequence>
|
||||
std::string
|
||||
buffers_to_string(ConstBufferSequence const& buffers)
|
||||
{
|
||||
using boost::asio::buffer_cast;
|
||||
using boost::asio::buffer_size;
|
||||
std::string s;
|
||||
s.reserve(buffer_size(buffers));
|
||||
for(auto const& b : buffers)
|
||||
s.append(buffer_cast<char const*>(b),
|
||||
buffer_size(b));
|
||||
return s;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
// Diagnostic output only
|
||||
template<bool isRequest, class Body, class Headers>
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os,
|
||||
message<isRequest, Body, Headers> const& msg)
|
||||
{
|
||||
static_assert(is_WritableBody<Body>::value,
|
||||
"WritableBody requirements not met");
|
||||
error_code ec;
|
||||
detail::write_preparation<isRequest, Body, Headers> wp(msg);
|
||||
wp.init(ec);
|
||||
if(ec)
|
||||
return os;
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
bool ready = false;
|
||||
resume_context resume{
|
||||
[&]
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
ready = true;
|
||||
cv.notify_one();
|
||||
}};
|
||||
auto copy = resume;
|
||||
os << detail::buffers_to_string(wp.sb.data());
|
||||
wp.sb.consume(wp.sb.size());
|
||||
auto writef =
|
||||
[&os, &wp](auto const& buffers)
|
||||
{
|
||||
if(wp.chunked)
|
||||
os << detail::buffers_to_string(
|
||||
chunk_encode(buffers));
|
||||
else
|
||||
os << detail::buffers_to_string(
|
||||
buffers);
|
||||
};
|
||||
for(;;)
|
||||
{
|
||||
{
|
||||
auto result = wp.w(std::move(copy), ec, writef);
|
||||
if(ec)
|
||||
return os;
|
||||
if(result)
|
||||
break;
|
||||
if(boost::indeterminate(result))
|
||||
{
|
||||
copy = resume;
|
||||
std::unique_lock<std::mutex> lock(m);
|
||||
cv.wait(lock, [&]{ return ready; });
|
||||
ready = false;
|
||||
}
|
||||
}
|
||||
wp.sb.consume(wp.sb.size());
|
||||
for(;;)
|
||||
{
|
||||
auto result = wp.w(std::move(copy), ec, writef);
|
||||
if(ec)
|
||||
return os;
|
||||
if(result)
|
||||
break;
|
||||
if(boost::indeterminate(result))
|
||||
{
|
||||
copy = resume;
|
||||
std::unique_lock<std::mutex> lock(m);
|
||||
cv.wait(lock, [&]{ return ready; });
|
||||
ready = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(wp.chunked)
|
||||
{
|
||||
// VFALCO Unfortunately the current interface to the
|
||||
// Writer concept prevents us from using coalescing the
|
||||
// final body chunk with the final chunk delimiter.
|
||||
//
|
||||
// write final chunk
|
||||
os << detail::buffers_to_string(chunk_encode_final());
|
||||
if(ec)
|
||||
return os;
|
||||
}
|
||||
os << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<bool isRequest, class Body, class Headers>
|
||||
void
|
||||
set_connection(bool keep_alive,
|
||||
message<isRequest, Body, Headers>& req)
|
||||
{
|
||||
if(req.version >= 11)
|
||||
{
|
||||
if(! keep_alive)
|
||||
req.headers.replace("Connection", "close");
|
||||
else
|
||||
req.headers.erase("Connection");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(keep_alive)
|
||||
req.headers.replace("Connection", "keep-alive");
|
||||
else
|
||||
req.headers.erase("Connection");
|
||||
}
|
||||
}
|
||||
|
||||
template<class Body, class Headers,
|
||||
class OtherBody, class OtherAllocator>
|
||||
void
|
||||
set_connection(bool keep_alive,
|
||||
message<false, Body, Headers>& resp,
|
||||
message<true, OtherBody, OtherAllocator> const& req)
|
||||
{
|
||||
if(req.version >= 11)
|
||||
{
|
||||
if(rfc2616::token_in_list(req["Connection"], "close"))
|
||||
keep_alive = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(! rfc2616::token_in_list(req["Connection"], "keep-alive"))
|
||||
keep_alive = false;
|
||||
}
|
||||
set_connection(keep_alive, resp);
|
||||
}
|
||||
|
||||
template<class Streambuf, class FieldSequence>
|
||||
void
|
||||
write_fields(Streambuf& streambuf, FieldSequence const& fields)
|
||||
{
|
||||
static_assert(is_Streambuf<Streambuf>::value,
|
||||
"Streambuf requirements not met");
|
||||
//static_assert(is_FieldSequence<FieldSequence>::value,
|
||||
// "FieldSequence requirements not met");
|
||||
for(auto const& field : fields)
|
||||
{
|
||||
detail::write(streambuf, field.name());
|
||||
detail::write(streambuf, ": ");
|
||||
detail::write(streambuf, field.value());
|
||||
detail::write(streambuf, "\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Headers>
|
||||
bool
|
||||
is_keep_alive(message<isRequest, Body, Headers> const& msg)
|
||||
{
|
||||
if(msg.version >= 11)
|
||||
{
|
||||
if(rfc2616::token_in_list(
|
||||
msg.headers["Connection"], "close"))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
if(rfc2616::token_in_list(
|
||||
msg.headers["Connection"], "keep-alive"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template<bool isRequest, class Body, class Headers>
|
||||
bool
|
||||
is_upgrade(message<isRequest, Body, Headers> const& msg)
|
||||
{
|
||||
if(msg.version < 11)
|
||||
return false;
|
||||
if(rfc2616::token_in_list(
|
||||
msg.headers["Connection"], "upgrade"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#include <beast/http/impl/message.ipp>
|
||||
|
||||
#endif
|
||||
285
include/beast/http/impl/read.ipp
Normal file
285
include/beast/http/impl/read.ipp
Normal file
@@ -0,0 +1,285 @@
|
||||
//
|
||||
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_IMPL_READ_IPP_HPP
|
||||
#define BEAST_HTTP_IMPL_READ_IPP_HPP
|
||||
|
||||
#include <beast/http/type_check.hpp>
|
||||
#include <beast/async_completion.hpp>
|
||||
#include <beast/bind_handler.hpp>
|
||||
#include <beast/handler_alloc.hpp>
|
||||
#include <cassert>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<class Stream, class Streambuf,
|
||||
bool isRequest, class Body, class Headers,
|
||||
class Handler>
|
||||
class read_op
|
||||
{
|
||||
using alloc_type =
|
||||
handler_alloc<char, Handler>;
|
||||
|
||||
using parser_type =
|
||||
parser<isRequest, Body, Headers>;
|
||||
|
||||
using message_type =
|
||||
message<isRequest, Body, Headers>;
|
||||
|
||||
struct data
|
||||
{
|
||||
Stream& s;
|
||||
Streambuf& sb;
|
||||
message_type& m;
|
||||
parser_type p;
|
||||
Handler h;
|
||||
bool cont;
|
||||
int state = 0;
|
||||
|
||||
template<class DeducedHandler>
|
||||
data(DeducedHandler&& h_, Stream& s_,
|
||||
Streambuf& sb_, message_type& m_)
|
||||
: s(s_)
|
||||
, sb(sb_)
|
||||
, m(m_)
|
||||
, h(std::forward<DeducedHandler>(h_))
|
||||
, cont(boost_asio_handler_cont_helpers::
|
||||
is_continuation(h))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<data> d_;
|
||||
|
||||
public:
|
||||
read_op(read_op&&) = default;
|
||||
read_op(read_op const&) = default;
|
||||
|
||||
template<class DeducedHandler, class... Args>
|
||||
read_op(DeducedHandler&& h, Stream&s, Args&&... args)
|
||||
: d_(std::allocate_shared<data>(alloc_type{h},
|
||||
std::forward<DeducedHandler>(h), s,
|
||||
std::forward<Args>(args)...))
|
||||
{
|
||||
(*this)(error_code{}, 0, false);
|
||||
}
|
||||
|
||||
void
|
||||
operator()(error_code ec,
|
||||
std::size_t bytes_transferred, bool again = true);
|
||||
|
||||
friend
|
||||
auto asio_handler_allocate(
|
||||
std::size_t size, read_op* op)
|
||||
{
|
||||
return boost_asio_handler_alloc_helpers::
|
||||
allocate(size, op->d_->h);
|
||||
}
|
||||
|
||||
friend
|
||||
auto asio_handler_deallocate(
|
||||
void* p, std::size_t size, read_op* op)
|
||||
{
|
||||
return boost_asio_handler_alloc_helpers::
|
||||
deallocate(p, size, op->d_->h);
|
||||
}
|
||||
|
||||
friend
|
||||
auto asio_handler_is_continuation(read_op* op)
|
||||
{
|
||||
return op->d_->cont;
|
||||
}
|
||||
|
||||
template <class Function>
|
||||
friend
|
||||
auto asio_handler_invoke(Function&& f, read_op* op)
|
||||
{
|
||||
return boost_asio_handler_invoke_helpers::
|
||||
invoke(f, op->d_->h);
|
||||
}
|
||||
};
|
||||
|
||||
template<class Stream, class Streambuf,
|
||||
bool isRequest, class Body, class Headers,
|
||||
class Handler>
|
||||
void
|
||||
read_op<Stream, Streambuf, isRequest, Body, Headers, Handler>::
|
||||
operator()(error_code ec, std::size_t bytes_transferred, bool again)
|
||||
{
|
||||
auto& d = *d_;
|
||||
d.cont = d.cont || again;
|
||||
while(d.state != 99)
|
||||
{
|
||||
switch(d.state)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
auto const used =
|
||||
d.p.write(d.sb.data(), ec);
|
||||
if(ec)
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
d.s.get_io_service().post(
|
||||
bind_handler(std::move(*this), ec, 0));
|
||||
return;
|
||||
}
|
||||
d.sb.consume(used);
|
||||
if(d.p.complete())
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
d.m = d.p.release();
|
||||
d.s.get_io_service().post(
|
||||
bind_handler(std::move(*this), ec, 0));
|
||||
return;
|
||||
}
|
||||
d.state = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
case 1:
|
||||
// read
|
||||
d.state = 2;
|
||||
d.s.async_read_some(d.sb.prepare(
|
||||
read_size_helper(d.sb, 65536)),
|
||||
std::move(*this));
|
||||
return;
|
||||
|
||||
// got data
|
||||
case 2:
|
||||
{
|
||||
if(ec == boost::asio::error::eof)
|
||||
{
|
||||
if(! d.p.started())
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
break;
|
||||
}
|
||||
// Caller will see eof on next read.
|
||||
ec = {};
|
||||
d.p.write_eof(ec);
|
||||
if(! ec)
|
||||
{
|
||||
assert(d.p.complete());
|
||||
d.m = d.p.release();
|
||||
}
|
||||
// call handler
|
||||
d.state = 99;
|
||||
break;
|
||||
}
|
||||
if(ec)
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
break;
|
||||
}
|
||||
d.sb.commit(bytes_transferred);
|
||||
d.sb.consume(d.p.write(d.sb.data(), ec));
|
||||
if(ec)
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
break;
|
||||
}
|
||||
if(d.p.complete())
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
d.m = d.p.release();
|
||||
break;
|
||||
}
|
||||
d.state = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
d.h(ec);
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class SyncReadStream, class Streambuf,
|
||||
bool isRequest, class Body, class Headers>
|
||||
void
|
||||
read(SyncReadStream& stream, Streambuf& streambuf,
|
||||
message<isRequest, Body, Headers>& m,
|
||||
error_code& ec)
|
||||
{
|
||||
static_assert(is_SyncReadStream<SyncReadStream>::value,
|
||||
"SyncReadStream requirements not met");
|
||||
static_assert(is_Streambuf<Streambuf>::value,
|
||||
"Streambuf requirements not met");
|
||||
static_assert(is_ReadableBody<Body>::value,
|
||||
"ReadableBody requirements not met");
|
||||
parser<isRequest, Body, Headers> p;
|
||||
for(;;)
|
||||
{
|
||||
auto used =
|
||||
p.write(streambuf.data(), ec);
|
||||
if(ec)
|
||||
return;
|
||||
streambuf.consume(used);
|
||||
if(p.complete())
|
||||
{
|
||||
m = p.release();
|
||||
break;
|
||||
}
|
||||
streambuf.commit(stream.read_some(
|
||||
streambuf.prepare(read_size_helper(
|
||||
streambuf, 65536)), ec));
|
||||
if(ec && ec != boost::asio::error::eof)
|
||||
return;
|
||||
if(ec == boost::asio::error::eof)
|
||||
{
|
||||
if(! p.started())
|
||||
return;
|
||||
// Caller will see eof on next read.
|
||||
ec = {};
|
||||
p.write_eof(ec);
|
||||
if(ec)
|
||||
return;
|
||||
assert(p.complete());
|
||||
m = p.release();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class AsyncReadStream, class Streambuf,
|
||||
bool isRequest, class Body, class Headers,
|
||||
class CompletionToken>
|
||||
auto
|
||||
async_read(AsyncReadStream& stream, Streambuf& streambuf,
|
||||
message<isRequest, Body, Headers>& m,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
static_assert(is_AsyncReadStream<AsyncReadStream>::value,
|
||||
"AsyncReadStream requirements not met");
|
||||
static_assert(is_Streambuf<Streambuf>::value,
|
||||
"Streambuf requirements not met");
|
||||
static_assert(is_ReadableBody<Body>::value,
|
||||
"ReadableBody requirements not met");
|
||||
beast::async_completion<CompletionToken,
|
||||
void(error_code)> completion(token);
|
||||
detail::read_op<AsyncReadStream, Streambuf,
|
||||
isRequest, Body, Headers, decltype(
|
||||
completion.handler)>{completion.handler,
|
||||
stream, streambuf, m};
|
||||
return completion.result.get();
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
||||
384
include/beast/http/impl/write.ipp
Normal file
384
include/beast/http/impl/write.ipp
Normal file
@@ -0,0 +1,384 @@
|
||||
//
|
||||
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef BEAST_HTTP_IMPL_WRITE_IPP
|
||||
#define BEAST_HTTP_IMPL_WRITE_IPP
|
||||
|
||||
#include <beast/http/chunk_encode.hpp>
|
||||
#include <beast/http/resume_context.hpp>
|
||||
#include <beast/http/type_check.hpp>
|
||||
#include <beast/http/detail/writes.hpp>
|
||||
#include <beast/http/detail/write_preparation.hpp>
|
||||
#include <beast/buffer_cat.hpp>
|
||||
#include <beast/async_completion.hpp>
|
||||
#include <beast/bind_handler.hpp>
|
||||
#include <beast/handler_alloc.hpp>
|
||||
#include <beast/streambuf.hpp>
|
||||
#include <beast/type_check.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/logic/tribool.hpp>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<class Stream, class Handler,
|
||||
bool isRequest, class Body, class Headers>
|
||||
class write_op
|
||||
{
|
||||
using alloc_type =
|
||||
handler_alloc<char, Handler>;
|
||||
|
||||
struct data
|
||||
{
|
||||
Stream& s;
|
||||
// VFALCO How do we use handler_alloc in write_preparation?
|
||||
write_preparation<
|
||||
isRequest, Body, Headers> wp;
|
||||
Handler h;
|
||||
resume_context resume;
|
||||
resume_context copy;
|
||||
bool cont;
|
||||
int state = 0;
|
||||
|
||||
template<class DeducedHandler>
|
||||
data(DeducedHandler&& h_, Stream& s_,
|
||||
message<isRequest, Body, Headers> const& m_)
|
||||
: s(s_)
|
||||
, wp(m_)
|
||||
, h(std::forward<DeducedHandler>(h_))
|
||||
, cont(boost_asio_handler_cont_helpers::
|
||||
is_continuation(h))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<data> d_;
|
||||
|
||||
public:
|
||||
write_op(write_op&&) = default;
|
||||
write_op(write_op const&) = default;
|
||||
|
||||
template<class DeducedHandler, class... Args>
|
||||
write_op(DeducedHandler&& h, Stream& s, Args&&... args)
|
||||
: d_(std::allocate_shared<data>(alloc_type{h},
|
||||
std::forward<DeducedHandler>(h), s,
|
||||
std::forward<Args>(args)...))
|
||||
{
|
||||
auto& d = *d_;
|
||||
d.resume = {
|
||||
[self = *this]() mutable
|
||||
{
|
||||
self.d_->cont = false;
|
||||
auto& ios = self.d_->s.get_io_service();
|
||||
ios.dispatch(bind_handler(std::move(self),
|
||||
error_code{}, 0, false));
|
||||
}};
|
||||
d.copy = d.resume;
|
||||
(*this)(error_code{}, 0, false);
|
||||
}
|
||||
|
||||
explicit
|
||||
write_op(std::shared_ptr<data> d)
|
||||
: d_(std::move(d))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(error_code ec,
|
||||
std::size_t bytes_transferred, bool again = true);
|
||||
|
||||
friend
|
||||
auto asio_handler_allocate(
|
||||
std::size_t size, write_op* op)
|
||||
{
|
||||
return boost_asio_handler_alloc_helpers::
|
||||
allocate(size, op->d_->h);
|
||||
}
|
||||
|
||||
friend
|
||||
auto asio_handler_deallocate(
|
||||
void* p, std::size_t size, write_op* op)
|
||||
{
|
||||
return boost_asio_handler_alloc_helpers::
|
||||
deallocate(p, size, op->d_->h);
|
||||
}
|
||||
|
||||
friend
|
||||
auto asio_handler_is_continuation(write_op* op)
|
||||
{
|
||||
return op->d_->cont;
|
||||
}
|
||||
|
||||
template <class Function>
|
||||
friend
|
||||
auto asio_handler_invoke(Function&& f, write_op* op)
|
||||
{
|
||||
return boost_asio_handler_invoke_helpers::
|
||||
invoke(f, op->d_->h);
|
||||
}
|
||||
};
|
||||
|
||||
template<class Stream, class Handler,
|
||||
bool isRequest, class Body, class Headers>
|
||||
void
|
||||
write_op<Stream, Handler, isRequest, Body, Headers>::
|
||||
operator()(error_code ec, std::size_t, bool again)
|
||||
{
|
||||
auto& d = *d_;
|
||||
d.cont = d.cont || again;
|
||||
while(! ec && d.state != 99)
|
||||
{
|
||||
switch(d.state)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
d.wp.init(ec);
|
||||
if(ec)
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
d.s.get_io_service().post(bind_handler(
|
||||
std::move(*this), ec, 0, false));
|
||||
return;
|
||||
}
|
||||
d.state = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
case 1:
|
||||
{
|
||||
auto const result = d.wp.w(std::move(d.copy), ec,
|
||||
[&](auto const& buffers)
|
||||
{
|
||||
// write headers and body
|
||||
if(d.wp.chunked)
|
||||
boost::asio::async_write(d.s,
|
||||
buffer_cat(d.wp.sb.data(),
|
||||
chunk_encode(buffers)),
|
||||
std::move(*this));
|
||||
else
|
||||
boost::asio::async_write(d.s,
|
||||
buffer_cat(d.wp.sb.data(),
|
||||
buffers), std::move(*this));
|
||||
});
|
||||
if(ec)
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
d.s.get_io_service().post(bind_handler(
|
||||
std::move(*this), ec, false));
|
||||
return;
|
||||
}
|
||||
if(boost::indeterminate(result))
|
||||
{
|
||||
// suspend
|
||||
d.copy = d.resume;
|
||||
return;
|
||||
}
|
||||
if(result)
|
||||
d.state = d.wp.chunked ? 4 : 5;
|
||||
else
|
||||
d.state = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
// sent headers and body
|
||||
case 2:
|
||||
d.wp.sb.consume(d.wp.sb.size());
|
||||
d.state = 3;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
{
|
||||
auto const result = d.wp.w(std::move(d.copy), ec,
|
||||
[&](auto const& buffers)
|
||||
{
|
||||
// write body
|
||||
if(d.wp.chunked)
|
||||
boost::asio::async_write(d.s,
|
||||
chunk_encode(buffers),
|
||||
std::move(*this));
|
||||
else
|
||||
boost::asio::async_write(d.s,
|
||||
buffers, std::move(*this));
|
||||
});
|
||||
if(ec)
|
||||
{
|
||||
// call handler
|
||||
d.state = 99;
|
||||
break;
|
||||
}
|
||||
if(boost::indeterminate(result))
|
||||
{
|
||||
// suspend
|
||||
d.copy = d.resume;
|
||||
return;
|
||||
}
|
||||
if(result)
|
||||
d.state = d.wp.chunked ? 4 : 5;
|
||||
else
|
||||
d.state = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
case 4:
|
||||
// VFALCO Unfortunately the current interface to the
|
||||
// Writer concept prevents us from using coalescing the
|
||||
// final body chunk with the final chunk delimiter.
|
||||
//
|
||||
// write final chunk
|
||||
d.state = 5;
|
||||
boost::asio::async_write(d.s,
|
||||
chunk_encode_final(), std::move(*this));
|
||||
return;
|
||||
|
||||
case 5:
|
||||
if(d.wp.close)
|
||||
{
|
||||
// VFALCO TODO Decide on an error code
|
||||
ec = boost::asio::error::eof;
|
||||
}
|
||||
d.state = 99;
|
||||
break;
|
||||
}
|
||||
}
|
||||
d.h(ec);
|
||||
d.resume = {};
|
||||
d.copy = {};
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class SyncWriteStream,
|
||||
bool isRequest, class Body, class Headers>
|
||||
void
|
||||
write(SyncWriteStream& stream,
|
||||
message<isRequest, Body, Headers> const& msg,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
static_assert(is_WritableBody<Body>::value,
|
||||
"WritableBody requirements not met");
|
||||
detail::write_preparation<isRequest, Body, Headers> wp(msg);
|
||||
wp.init(ec);
|
||||
if(ec)
|
||||
return;
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
bool ready = false;
|
||||
resume_context resume{
|
||||
[&]
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
ready = true;
|
||||
cv.notify_one();
|
||||
}};
|
||||
auto copy = resume;
|
||||
for(;;)
|
||||
{
|
||||
{
|
||||
auto result = wp.w(std::move(copy), ec,
|
||||
[&](auto const& buffers)
|
||||
{
|
||||
// write headers and body
|
||||
if(wp.chunked)
|
||||
boost::asio::write(stream, buffer_cat(
|
||||
wp.sb.data(), chunk_encode(buffers)), ec);
|
||||
else
|
||||
boost::asio::write(stream, buffer_cat(
|
||||
wp.sb.data(), buffers), ec);
|
||||
});
|
||||
if(ec)
|
||||
return;
|
||||
if(result)
|
||||
break;
|
||||
if(boost::indeterminate(result))
|
||||
{
|
||||
boost::asio::write(stream, wp.sb.data(), ec);
|
||||
if(ec)
|
||||
return;
|
||||
wp.sb.consume(wp.sb.size());
|
||||
copy = resume;
|
||||
std::unique_lock<std::mutex> lock(m);
|
||||
cv.wait(lock, [&]{ return ready; });
|
||||
ready = false;
|
||||
}
|
||||
}
|
||||
wp.sb.consume(wp.sb.size());
|
||||
for(;;)
|
||||
{
|
||||
auto result = wp.w(std::move(copy), ec,
|
||||
[&](auto const& buffers)
|
||||
{
|
||||
// write body
|
||||
if(wp.chunked)
|
||||
boost::asio::write(stream,
|
||||
chunk_encode(buffers), ec);
|
||||
else
|
||||
boost::asio::write(stream, buffers, ec);
|
||||
});
|
||||
if(ec)
|
||||
return;
|
||||
if(result)
|
||||
break;
|
||||
if(boost::indeterminate(result))
|
||||
{
|
||||
copy = resume;
|
||||
std::unique_lock<std::mutex> lock(m);
|
||||
cv.wait(lock, [&]{ return ready; });
|
||||
ready = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(wp.chunked)
|
||||
{
|
||||
// VFALCO Unfortunately the current interface to the
|
||||
// Writer concept prevents us from using coalescing the
|
||||
// final body chunk with the final chunk delimiter.
|
||||
//
|
||||
// write final chunk
|
||||
boost::asio::write(stream, chunk_encode_final(), ec);
|
||||
if(ec)
|
||||
return;
|
||||
}
|
||||
if(wp.close)
|
||||
{
|
||||
// VFALCO TODO Decide on an error code
|
||||
ec = boost::asio::error::eof;
|
||||
}
|
||||
}
|
||||
|
||||
template<class AsyncWriteStream,
|
||||
bool isRequest, class Body, class Headers,
|
||||
class CompletionToken>
|
||||
auto
|
||||
async_write(AsyncWriteStream& stream,
|
||||
message<isRequest, Body, Headers> const& msg,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
static_assert(
|
||||
is_AsyncWriteStream<AsyncWriteStream>::value,
|
||||
"AsyncWriteStream requirements not met");
|
||||
static_assert(is_WritableBody<Body>::value,
|
||||
"WritableBody requirements not met");
|
||||
beast::async_completion<CompletionToken,
|
||||
void(error_code)> completion(token);
|
||||
detail::write_op<AsyncWriteStream, decltype(completion.handler),
|
||||
isRequest, Body, Headers>{completion.handler, stream, msg};
|
||||
return completion.result.get();
|
||||
}
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user