Line data Source code
1 : #ifndef THINGER_HTTP_CLIENT_EXECUTOR_CLIENT_HPP
2 : #define THINGER_HTTP_CLIENT_EXECUTOR_CLIENT_HPP
3 :
4 : #include "http_client_base.hpp"
5 : #include "request_builder.hpp"
6 : #include "../../asio/io_context_of.hpp"
7 : #include <boost/asio/io_context.hpp>
8 : #include <memory>
9 : #include <optional>
10 : #include <string>
11 :
12 : namespace thinger::http {
13 :
14 : /**
15 : * An HTTP client running on an io_context the caller already runs, e.g. the IO thread of the
16 : * coroutine using it (see for_current_coroutine()): no threads of its own, no idle timers, and
17 : * its connections are closed when it is destroyed. Requests are awaited (as with async_client),
18 : * and run on that io_context: no hop to another thread.
19 : *
20 : * Meant for occasional calls from server coroutines (a token refresh, a webhook, an order),
21 : * far apart: it keeps no idle connection by default, so each request opens its own. For many
22 : * calls to the same host, use a pooled client (async_client); max_idle_connections_per_host()
23 : * enables keep-alive here too, closing expired connections when the next request comes.
24 : *
25 : * Use it on the threads running the io_context, as its sockets (e.g. from a coroutine on it or
26 : * on a strand of it), and destroy it there, before the io_context. Cancelling the coroutine
27 : * awaiting a request (a cancellation slot, or `||` with a timer) cancels the request.
28 : *
29 : * WebSockets opened through it run on the executor of the caller, its strand included: their
30 : * keepalive and closing handshake never run at the same time as the caller.
31 : *
32 : * Usage:
33 : * server.get("/weather", [](http::response& res) -> thinger::awaitable<void> {
34 : * auto client = co_await http::executor_client::for_current_coroutine();
35 : * auto forecast = co_await client->get("https://api.example.com/forecast");
36 : * res.json(forecast.json());
37 : * });
38 : */
39 : class executor_client : public http_client_base {
40 : public:
41 : explicit executor_client(boost::asio::io_context& io_context);
42 : // The idle connections (if any) are closed while their io_context exists
43 : ~executor_client() override;
44 :
45 : executor_client(const executor_client&) = delete;
46 : executor_client& operator=(const executor_client&) = delete;
47 :
48 : // A client on the io_context of the calling coroutine (see asio::io_context_of()): its
49 : // worker's in a coroutine of async_client::run(). Throws std::logic_error if the
50 : // coroutine runs on another kind of executor (e.g. a thread_pool)
51 : static awaitable<std::unique_ptr<executor_client>> for_current_coroutine();
52 :
53 : /**
54 : * Create a request builder for fluent API.
55 : *
56 : * Usage:
57 : * auto res = co_await client.request("https://api.com/data")
58 : * .header("Authorization", "Bearer xxx")
59 : * .get();
60 : */
61 3 : request_builder<executor_client> request(const std::string& url) {
62 3 : return request_builder<executor_client>(this, url);
63 : }
64 :
65 : // Connect to a WebSocket server (its socket runs on the same io_context)
66 87 : awaitable<std::optional<websocket_client>> websocket(const std::string& url,
67 : const std::string& subprotocol = "") {
68 87 : return upgrade_websocket(url, subprotocol);
69 : }
70 :
71 : // With a request of its own (for the request builder)
72 : awaitable<std::optional<websocket_client>> websocket(std::shared_ptr<http_request> request,
73 : const std::string& subprotocol = "") {
74 : return upgrade_websocket(std::move(request), subprotocol);
75 : }
76 :
77 : protected:
78 231 : boost::asio::io_context& get_io_context() override { return io_context_; }
79 :
80 : private:
81 : boost::asio::io_context& io_context_;
82 : };
83 :
84 : } // namespace thinger::http
85 :
86 : #endif // THINGER_HTTP_CLIENT_EXECUTOR_CLIENT_HPP
|