Skip to content
Merged

Work #18

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,21 +142,21 @@ jobs:
build-type: "Release"
build-cmake: true

- compiler: "gcc"
version: "15"
cxxstd: "20"
latest-cxxstd: "20"
cxx: "g++-15"
cc: "gcc-15"
runs-on: "ubuntu-latest"
container: "ubuntu:25.04"
b2-toolset: "gcc"
is-latest: true
name: "GCC 15: C++20 (asan+ubsan)"
shared: true
asan: true
ubsan: true
build-type: "RelWithDebInfo"
# - compiler: "gcc"
# version: "15"
# cxxstd: "20"
# latest-cxxstd: "20"
# cxx: "g++-15"
# cc: "gcc-15"
# runs-on: "ubuntu-latest"
# container: "ubuntu:25.04"
# b2-toolset: "gcc"
# is-latest: true
# name: "GCC 15: C++20 (asan+ubsan)"
# shared: true
# asan: true
# ubsan: true
# build-type: "RelWithDebInfo"

- compiler: "gcc"
version: "13"
Expand Down
19 changes: 14 additions & 5 deletions example/usage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,13 +387,22 @@ follow_redirects(corosio::tls_context tls_ctx)

burl::client client(co_await capy::this_coro::executor, tls_ctx, cfg);

auto [ec, r] = co_await client.get("http://boost.org").send();
auto [ec1, r1] = co_await client.get("http://boost.org").send();

if(ec)
throw std::system_error(ec);
if(ec1)
throw std::system_error(ec1);

// The final URL after following redirects
std::cout << r1.url() << '\n';

auto [ec2, r2] = co_await client.get("http://boost.org")
.followlocation(false) // per-request override
.send();

if(ec2)
throw std::system_error(ec2);

// Final URL after following redirects, e.g. https://www.boost.org
std::cout << r.url() << '\n';
std::cout << r2.status_int() << '\n'; // e.g. 301
}

//==============================================================
Expand Down
57 changes: 24 additions & 33 deletions include/boost/burl/connection_pool.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#include <boost/capy/timeout.hpp>
#include <boost/corosio/endpoint.hpp>
#include <boost/corosio/tls_context.hpp>
#include <boost/http/response_parser.hpp>
#include <boost/url/url.hpp>
#include <boost/url/url_view.hpp>

Expand All @@ -27,7 +26,6 @@
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>

namespace boost
Expand All @@ -53,6 +51,9 @@ class response;
Each @ref client owns a connection pool,
configured through @ref client::config::pool.

This class is a shared handle to the pool state.
Copies share the same underlying pool.

@see @ref client.
*/
class connection_pool
Expand Down Expand Up @@ -156,17 +157,15 @@ class connection_pool

@param cfg The configuration settings.
*/
BOOST_BURL_DECL
connection_pool(
capy::executor_ref exec,
corosio::tls_context tls_ctx,
config cfg)
: exec_(exec)
, tls_ctx_(std::move(tls_ctx))
, config_(std::move(cfg))
{
}
config cfg);

private:
class impl;

class connection
{
public:
Expand All @@ -185,15 +184,14 @@ class connection_pool
virtual ~connection() = default;
};

class tcp_connection;
class tls_connection;

class pooled_connection
{
friend class connection_pool;
friend class impl;
friend class response;

std::unique_ptr<connection> conn_;
std::weak_ptr<impl> pool_;
std::string key_;
std::optional<config::clock::duration> io_timeout_;
capy::detail::buffer_array<8, false> rba_; // TODO
capy::detail::buffer_array<8, true> wba_; // TODO
Expand All @@ -202,8 +200,12 @@ class connection_pool

pooled_connection(
std::unique_ptr<connection> conn,
std::weak_ptr<impl> pool,
std::string key,
std::optional<config::clock::duration> io_timeout = std::nullopt)
: conn_(std::move(conn))
, pool_(std::move(pool))
, key_(std::move(key))
, io_timeout_(io_timeout)
{
}
Expand All @@ -230,33 +232,22 @@ class connection_pool
return capy::timeout(conn_->write_some(wba_), *io_timeout_);
return conn_->write_some(wba_);
}
};

struct idle_connection
{
std::unique_ptr<connection> conn;
config::clock::time_point idle_since;
explicit
operator bool() const noexcept
{
return conn_ != nullptr;
}

BOOST_BURL_DECL
void
return_to_pool();
};

BOOST_BURL_DECL
capy::io_task<pooled_connection>
acquire(urls::url_view url);

BOOST_BURL_DECL
void
release(
urls::url_view url,
pooled_connection pc,
http::response_parser const& parser);

BOOST_BURL_DECL
capy::io_task<std::unique_ptr<connection>>
connect(urls::url_view url) const;

capy::executor_ref exec_;
corosio::tls_context tls_ctx_;
std::unordered_multimap<std::string, idle_connection> idle_;
config config_;
std::shared_ptr<impl> impl_;
};

} // namespace burl
Expand Down
34 changes: 34 additions & 0 deletions include/boost/burl/multipart_form.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,40 @@ class multipart_form
std::string_view filename = {},
std::string_view content_type = {});

/** Append a file part with in-memory contents to the form.

The part is written with a `filename` in its
`Content-Disposition` header, so servers
treat it as a file upload, but the contents
are held in memory rather than streamed from
disk.

@par Exception Safety
Calls to allocate may throw.

@param name The name of the form field.

@param data The contents of the part.

@param filename The filename to report in
the part header.

@param content_type The value for the
`Content-Type` header of the part. Deduced
from the filename extension when empty, with
`application/octet-stream` as the fallback.

@return A reference to this object, for
chaining.
*/
BOOST_BURL_DECL
multipart_form&
bytes(
std::string_view name,
std::string data,
std::string_view filename,
std::string_view content_type = {});

private:
static std::string
generate_boundary();
Expand Down
10 changes: 10 additions & 0 deletions include/boost/burl/request.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ struct request
@see @ref request_builder::timeout.
*/
std::optional<clock::duration> timeout;

/** Follow redirect responses automatically.

When set, overrides
@ref client::config::followlocation for
this request.

@see @ref request_builder::followlocation.
*/
std::optional<bool> followlocation;
};

/** The request method.
Expand Down
27 changes: 27 additions & 0 deletions include/boost/burl/request_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,33 @@ class request_builder
return std::move(*this);
}

/** Set whether redirect responses are followed.

Overrides
@ref client::config::followlocation for
this request. When disabled, the redirect
response is returned as-is; its `Location`
header can be inspected through the
@ref response.

@par Example
@code
auto [ec, r] = co_await c.get("https://example.com/moved")
.followlocation(false)
.send();
@endcode

@param enable `true` to follow redirects.

@return The builder, for chaining.
*/
request_builder&&
followlocation(bool enable) &&
{
request_.options.followlocation = enable;
return std::move(*this);
}

/** Set the request body.

The value is converted to a request body by
Expand Down
18 changes: 9 additions & 9 deletions include/boost/burl/response.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
#include <boost/url/url_view.hpp>

#include <chrono>
#include <cstddef>
#include <optional>
#include <string_view>
#include <system_error>
Expand All @@ -48,9 +47,13 @@ namespace burl

The response owns the connection it was received
on. Upon destruction, the connection is returned
to the pool for reuse when the body was read to
completion and the connection can be kept alive;
otherwise it is closed.
to the pool for reuse when it can be kept alive
and the entire message has arrived.

A response remains usable after the client which
produced it is destroyed; in that case the
connection is closed upon destruction instead of
being returned to the pool.

@par Example
@code
Expand All @@ -75,14 +78,12 @@ class response

urls::url url_;
connection_pool::pooled_connection conn_;
connection_pool* pool_ = nullptr;
http::response_parser parser_;
std::optional<clock::time_point> deadline_;

response(
urls::url url,
connection_pool::pooled_connection conn,
connection_pool* pool,
http::response_parser parser,
std::optional<clock::time_point> deadline);

Expand Down Expand Up @@ -127,9 +128,8 @@ class response
/** Destructor.

Returns the connection to the pool for reuse
when the body was read to completion and the
connection can be kept alive; otherwise the
connection is closed.
when it can be kept alive and the entire
message has arrived.
*/
BOOST_BURL_DECL
~response();
Expand Down
Loading
Loading