Skip to content

Commit

Permalink
Merge pull request #3 from terrakuh/dev
Browse files Browse the repository at this point in the history
Version 0.5.0
  • Loading branch information
terrakuh authored Aug 4, 2024
2 parents 0d0160e + 152bf27 commit 2b816cc
Show file tree
Hide file tree
Showing 20 changed files with 416 additions and 357 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Change Log

<h2><a href="https://github.com/terrakuh/curlio/compare/v0.4..v0.5.0">v0.5.0</a> - 2024-08-04</h2>

### Changed
- Less relying on pointers
- CMake target name `cURLio::cURLio`

### Fixed
- Fix segfaults on kick-starting new requests
- Fix CURL deprecation warnings
- Various bugs

<h2><a href="https://github.com/terrakuh/curlio/compare/v0.3.3..v0.4">v0.4</a> - 2022-11-27</h2>

### Changed
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16)

project(
cURLio
VERSION 0.4.0
VERSION 0.5.0
DESCRIPTION "The simple glue for cURL and Boost.ASIO"
HOMEPAGE_URL "https://github.com/terrakuh/curlio"
LANGUAGES CXX
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ The following examples uses the [coroutines](https://en.cppreference.com/w/cpp/l
```cpp
asio::io_service service{};

auto session = curlio::make_session<boost::asio::any_io_executor>(service.get_executor());
curlio::Session session{ service.get_executor() };

// Create request and set options.
auto request = curlio::make_request(session);
auto request = std::make_shared<curlio::Request>(session);
request->set_option<CURLOPT_URL>("http://example.com");
request->set_option<CURLOPT_USERAGENT>("cURLio");

// Launches the request which will then run in the background.
auto response = co_await session->async_start(request, asio::use_awaitable);
auto response = co_await session.async_start(request, asio::use_awaitable);

// Read all and do something with the data.
char data[4096];
Expand Down Expand Up @@ -52,8 +52,8 @@ cmake --install curlio/build
And then in your `CMakeLists.txt`:

```cmake
find_package(curlio 0.4 REQUIRED)
target_link_libraries(my-target PRIVATE curlio::curlio)
find_package(cURLio 0.5 REQUIRED)
target_link_libraries(my-target PRIVATE cURLio::cURLio)
```

## Debugging
Expand Down
1 change: 1 addition & 0 deletions curlio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ target_link_libraries(cURLio INTERFACE CURL::libcurl Threads::Threads Boost::boo
target_include_directories(
cURLio INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>" $<INSTALL_INTERFACE:include>
)
target_compile_features(cURLio INTERFACE cxx_std_17)

if(CURLIO_ENABLE_LOGGING)
target_compile_definitions(cURLio INTERFACE CURLIO_ENABLE_LOGGING)
Expand Down
36 changes: 21 additions & 15 deletions curlio/basic_request.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,54 @@
#include "config.hpp"
#include "detail/asio_include.hpp"
#include "detail/function.hpp"
#include "fwd.hpp"
#include "detail/option_type.hpp"
#include "fwd.hpp"

#include <curl/curl.h>

namespace curlio {

template<typename Executor>
class Basic_request : public std::enable_shared_from_this<Basic_request<Executor>> {
class BasicRequest {
public:
using executor_type = Executor;
using strand_type = CURLIO_ASIO_NS::strand<executor_type>;

Basic_request(const Basic_request& copy);
~Basic_request() noexcept;
BasicRequest(BasicSession<Executor>& session) noexcept;
BasicRequest(const BasicRequest& copy) noexcept;
BasicRequest(BasicRequest&& move) = delete;
~BasicRequest();

template<CURLoption Option>
void set_option(detail::option_type<Option> value);
void append_header(const char* header);
void free_headers();
auto async_write_some(const auto& buffers, auto&& token);
auto async_abort(auto&& token);
CURLIO_NO_DISCARD CURL* native_handle() const noexcept;
CURLIO_NO_DISCARD executor_type get_executor() const noexcept;
Basic_request& operator=(const Basic_request& copy) = delete;
CURLIO_NO_DISCARD strand_type& get_strand() noexcept;

BasicRequest& operator=(const BasicRequest& copy) = delete;
BasicRequest& operator=(BasicRequest&& move) = delete;

template<typename Executor_>
friend std::shared_ptr<Basic_request<Executor_>>
make_request(std::shared_ptr<Basic_session<Executor_>> session);
static std::shared_ptr<BasicRequest> make_request(BasicSession<Executor>& session);

private:
friend class Basic_session<Executor>;
friend class Basic_response<Executor>;
friend class BasicSession<Executor>;
friend class BasicResponse<Executor>;

std::shared_ptr<Basic_session<Executor>> _session;
std::shared_ptr<strand_type> _strand;
// The CURL easy handle. The response owns this instance.
CURL* _handle;
curl_slist* _additional_headers = nullptr;
detail::Function<std::size_t(detail::asio_error_code, char*, std::size_t)> _send_handler;
detail::Function<std::size_t(detail::asio_error_code, char*, std::size_t)> _send_handler{};

Basic_request(std::shared_ptr<Basic_session<Executor>>&& session);
void _mark_finished();
BasicRequest(std::shared_ptr<BasicSession<Executor>>&& session);
void _mark_finished() noexcept;
static std::size_t _read_callback(char* data, std::size_t size, std::size_t count, void* self_ptr) noexcept;
};

using Request = Basic_request<CURLIO_ASIO_NS::any_io_executor>;
using Request = BasicRequest<CURLIO_ASIO_NS::any_io_executor>;

} // namespace curlio
108 changes: 57 additions & 51 deletions curlio/basic_request.inl
Original file line number Diff line number Diff line change
Expand Up @@ -8,74 +8,91 @@
namespace curlio {

template<typename Executor>
inline Basic_request<Executor>::Basic_request(const Basic_request& copy) : _session{ copy._session }
inline BasicRequest<Executor>::BasicRequest(BasicSession<Executor>& session) noexcept
: _strand{ session._strand }
{
_handle = curl_easy_init();

curl_easy_setopt(_handle, CURLOPT_READFUNCTION, &BasicRequest::_read_callback);
curl_easy_setopt(_handle, CURLOPT_READDATA, this);
}

template<typename Executor>
inline BasicRequest<Executor>::BasicRequest(const BasicRequest& copy) noexcept : _strand{ copy._strand }
{
_handle = curl_easy_duphandle(copy._handle);

curl_easy_setopt(_handle, CURLOPT_READFUNCTION, &Basic_request::_read_callback);
curl_easy_setopt(_handle, CURLOPT_READFUNCTION, &BasicRequest::_read_callback);
curl_easy_setopt(_handle, CURLOPT_READDATA, this);
}

template<typename Executor>
inline Basic_request<Executor>::~Basic_request() noexcept
inline BasicRequest<Executor>::~BasicRequest()
{
CURLIO_DEBUG("Freeing handle @" << _handle);
curl_easy_cleanup(_handle);
curl_slist_free_all(_additional_headers);
free_headers();
}

template<typename Executor>
template<CURLoption Option>
inline void Basic_request<Executor>::set_option(detail::option_type<Option> value)
inline void BasicRequest<Executor>::set_option(detail::option_type<Option> value)
{
if (const auto status = curl_easy_setopt(_handle, Option, value); status != CURLE_OK) {
throw std::system_error{ Code::bad_option, curl_easy_strerror(status) };
}
}

template<typename Executor>
inline void Basic_request<Executor>::append_header(const char* header)
inline void BasicRequest<Executor>::append_header(const char* header)
{
_additional_headers = curl_slist_append(_additional_headers, header);
set_option<CURLOPT_HTTPHEADER>(_additional_headers);
}

template<typename Executor>
inline auto Basic_request<Executor>::async_write_some(const auto& buffers, auto&& token)
inline void BasicRequest<Executor>::free_headers()
{
curl_slist_free_all(_additional_headers);
_additional_headers = nullptr;
}

template<typename Executor>
inline auto BasicRequest<Executor>::async_write_some(const auto& buffers, auto&& token)
{
return CURLIO_ASIO_NS::async_initiate<decltype(token), void(detail::asio_error_code, std::size_t)>(
[this](auto handler, const auto& buffers) {
CURLIO_ASIO_NS::dispatch(
_session->get_strand(), [this, buffers, handler = std::move(handler)]() mutable {
auto executor = CURLIO_ASIO_NS::get_associated_executor(handler, get_executor());

if (_send_handler) {
CURLIO_ASIO_NS::post(
std::move(executor),
std::bind(std::move(handler), make_error_code(Code::multiple_writes), std::size_t{ 0 }));
} else {
_send_handler = [this, buffers = std::move(buffers), handler = std::move(handler),
executor = std::move(executor)](detail::asio_error_code ec, char* data,
std::size_t size) mutable {
const std::size_t copied =
CURLIO_ASIO_NS::buffer_copy(CURLIO_ASIO_NS::buffer(data, size), buffers);
CURLIO_ASIO_NS::post(std::move(executor), std::bind(std::move(handler), ec, copied));
return copied;
};

// Resume.
curl_easy_pause(_handle, CURLPAUSE_CONT);
}
});
CURLIO_ASIO_NS::dispatch(*_strand, [this, buffers, handler = std::move(handler)]() mutable {
auto executor = CURLIO_ASIO_NS::get_associated_executor(handler, get_executor());

if (_send_handler) {
CURLIO_ASIO_NS::post(
std::move(executor),
std::bind(std::move(handler), make_error_code(Code::multiple_writes), std::size_t{ 0 }));
} else {
_send_handler = [this, buffers = std::move(buffers), handler = std::move(handler),
executor = std::move(executor)](detail::asio_error_code ec, char* data,
std::size_t size) mutable {
const std::size_t copied =
CURLIO_ASIO_NS::buffer_copy(CURLIO_ASIO_NS::buffer(data, size), buffers);
CURLIO_ASIO_NS::post(std::move(executor), std::bind(std::move(handler), ec, copied));
return copied;
};

// Resume.
curl_easy_pause(_handle, CURLPAUSE_CONT);
}
});
},
token, buffers);
}

template<typename Executor>
inline auto Basic_request<Executor>::async_abort(auto&& token)
inline auto BasicRequest<Executor>::async_abort(auto&& token)
{
return CURLIO_ASIO_NS::async_initiate<decltype(token), void(detail::asio_error_code)>(
[this](auto handler) {
CURLIO_ASIO_NS::dispatch(_session->get_strand(), [this, handler = std::move(handler)]() mutable {
CURLIO_ASIO_NS::dispatch(*_strand, [this, handler = std::move(handler)]() mutable {
if (_send_handler) {
_send_handler(CURLIO_ASIO_NS::error::operation_aborted, nullptr, 0);
_send_handler.reset();
Expand All @@ -97,29 +114,25 @@ inline auto Basic_request<Executor>::async_abort(auto&& token)
}

template<typename Executor>
inline CURL* Basic_request<Executor>::native_handle() const noexcept
inline CURL* BasicRequest<Executor>::native_handle() const noexcept
{
return _handle;
}

template<typename Executor>
inline Basic_request<Executor>::executor_type Basic_request<Executor>::get_executor() const noexcept
inline BasicRequest<Executor>::executor_type BasicRequest<Executor>::get_executor() const noexcept
{
return _session->get_executor();
return _strand->get_inner_executor();
}

template<typename Executor>
inline Basic_request<Executor>::Basic_request(std::shared_ptr<Basic_session<Executor>>&& session)
: _session{ std::move(session) }
inline BasicRequest<Executor>::strand_type& BasicRequest<Executor>::get_strand() noexcept
{
_handle = curl_easy_init();

curl_easy_setopt(_handle, CURLOPT_READFUNCTION, &Basic_request::_read_callback);
curl_easy_setopt(_handle, CURLOPT_READDATA, this);
return *_strand;
}

template<typename Executor>
inline void Basic_request<Executor>::_mark_finished()
inline void BasicRequest<Executor>::_mark_finished() noexcept
{
CURLIO_INFO("Request marked as finished");
if (_send_handler) {
Expand All @@ -129,10 +142,10 @@ inline void Basic_request<Executor>::_mark_finished()
}

template<typename Executor>
inline std::size_t Basic_request<Executor>::_read_callback(char* data, std::size_t size, std::size_t count,
void* self_ptr) noexcept
inline std::size_t BasicRequest<Executor>::_read_callback(char* data, std::size_t size, std::size_t count,
void* self_ptr) noexcept
{
const auto self = static_cast<Basic_request*>(self_ptr);
const auto self = static_cast<BasicRequest*>(self_ptr);
const std::size_t total_length = size * count;

if (total_length == 0) {
Expand All @@ -149,11 +162,4 @@ inline std::size_t Basic_request<Executor>::_read_callback(char* data, std::size
return CURL_READFUNC_PAUSE;
}

template<typename Executor>
CURLIO_NO_DISCARD inline std::shared_ptr<Basic_request<Executor>>
make_request(std::shared_ptr<Basic_session<Executor>> session)
{
return std::shared_ptr<Basic_request<Executor>>{ new Basic_request<Executor>{ std::move(session) } };
}

} // namespace curlio
44 changes: 24 additions & 20 deletions curlio/basic_response.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,52 +4,56 @@
#include "detail/asio_include.hpp"
#include "detail/function.hpp"
#include "detail/header_collector.hpp"
#include "detail/option_type.hpp"
#include "fwd.hpp"

#include <curl/curl.h>
#include <map>
#include <memory>

namespace curlio {

using Headers = detail::HeaderCollector::fields_type;

template<typename Executor>
class Basic_response : public std::enable_shared_from_this<Basic_response<Executor>> {
class BasicResponse : public std::enable_shared_from_this<BasicResponse<Executor>> {
public:
using executor_type = Executor;
using headers_type = detail::Header_collector::fields_type;
using strand_type = CURLIO_ASIO_NS::strand<executor_type>;

Basic_response(const Basic_response& copy) = delete;
BasicResponse(const BasicResponse& copy) = delete;
BasicResponse(BasicResponse&& move) = delete;

template<CURLINFO Option>
auto get_info() const;
template<CURLINFO Option>
auto async_get_info(auto&& token) const;
auto async_read_some(const auto& buffers, auto&& token);
auto async_wait_headers(auto&& token);
CURLIO_NO_DISCARD executor_type get_executor() const noexcept;
Basic_response& operator=(const Basic_response& copy) = delete;
CURLIO_NO_DISCARD strand_type& get_strand() noexcept;

private:
friend class Basic_session<Executor>;
BasicResponse& operator=(const BasicResponse& copy) = delete;
BasicResponse& operator=(BasicResponse&& move) = delete;

std::shared_ptr<Basic_session<Executor>> _session;
std::shared_ptr<Basic_request<Executor>> _request;
CURLIO_ASIO_NS::streambuf _input_buffer;
detail::Function<std::size_t(detail::asio_error_code, const char*, std::size_t)> _receive_handler;
detail::Header_collector _header_collector;
private:
// Only the session may construct a response and call `_start()` / `_stop()`.
friend class BasicSession<Executor>;

std::shared_ptr<strand_type> _strand;
std::shared_ptr<BasicRequest<Executor>> _request;
CURLIO_ASIO_NS::streambuf _input_buffer{};
detail::Function<std::size_t(detail::asio_error_code, const char*, std::size_t)> _receive_handler{};
detail::HeaderCollector _header_collector;
bool _finished = false;

Basic_response(std::shared_ptr<Basic_session<Executor>>&& session,
std::shared_ptr<Basic_request<Executor>>&& request);
void _mark_finished();
BasicResponse(std::shared_ptr<strand_type> strand,
std::shared_ptr<BasicRequest<Executor>> request) noexcept;
void _start() noexcept;
void _stop() noexcept;
static std::size_t _write_callback(char* data, std::size_t size, std::size_t count,
void* self_ptr) noexcept;
};

template<typename Executor>
auto async_wait_last_headers(std::shared_ptr<Basic_response<Executor>> response, auto&& token);
auto async_wait_last_headers(std::shared_ptr<BasicResponse<Executor>> response, auto&& token);

using Response = Basic_response<CURLIO_ASIO_NS::any_io_executor>;
using Response = BasicResponse<CURLIO_ASIO_NS::any_io_executor>;

} // namespace curlio
Loading

0 comments on commit 2b816cc

Please sign in to comment.