Line data Source code
1 : #ifndef THINGER_HTTP_CLIENT_STANDALONE_HPP
2 : #define THINGER_HTTP_CLIENT_STANDALONE_HPP
3 :
4 : #include "http_client_base.hpp"
5 : #include "request_builder.hpp"
6 : #include <boost/asio/io_context.hpp>
7 :
8 : namespace thinger::http {
9 :
10 : /**
11 : * Standalone HTTP client with simple synchronous API.
12 : * Perfect for scripts, CLI tools, and simple applications.
13 : *
14 : * Usage:
15 : * http::client client;
16 : *
17 : * // Simple GET
18 : * auto response = client.get("https://api.example.com/users");
19 : * if (response) {
20 : * std::cout << response.body() << std::endl;
21 : * }
22 : *
23 : * // POST with JSON
24 : * auto response = client.post("https://api.example.com/users", R"({"name":"test"})");
25 : *
26 : * // With configuration (fluent API)
27 : * client.timeout(std::chrono::seconds(30)).verify_ssl(false);
28 : * auto response = client.get("https://internal-api/data");
29 : *
30 : * For async/concurrent operations, use http::async_client instead.
31 : */
32 : class client : public http_client_base {
33 : private:
34 : boost::asio::io_context io_context_;
35 :
36 : // Internal helper to run an awaitable synchronously
37 : template<typename T>
38 408 : T exec(awaitable<T> coro) {
39 408 : T result;
40 816 : co_spawn(io_context_, [&result, coro = std::move(coro)]() mutable -> awaitable<void> {
41 : result = co_await std::move(coro);
42 : }, detached);
43 408 : io_context_.run();
44 408 : io_context_.restart();
45 408 : return result;
46 0 : }
47 :
48 : protected:
49 430 : boost::asio::io_context& get_io_context() override {
50 430 : return io_context_;
51 : }
52 :
53 : public:
54 461 : client() = default;
55 461 : ~client() override = default;
56 :
57 : // ============================================
58 : // Synchronous HTTP methods
59 : // ============================================
60 :
61 214 : client_response get(const std::string& url, headers_map headers = {}) {
62 214 : return exec(http_client_base::get(url, std::move(headers)));
63 : }
64 :
65 46 : client_response post(const std::string& url, std::string body = {},
66 : std::string content_type = "application/json", headers_map headers = {}) {
67 46 : return exec(http_client_base::post(url, std::move(body), std::move(content_type), std::move(headers)));
68 : }
69 :
70 : client_response post(const std::string& url, const form& form, headers_map headers = {}) {
71 : return exec(http_client_base::post(url, form, std::move(headers)));
72 : }
73 :
74 10 : client_response put(const std::string& url, std::string body = {},
75 : std::string content_type = "application/json", headers_map headers = {}) {
76 10 : return exec(http_client_base::put(url, std::move(body), std::move(content_type), std::move(headers)));
77 : }
78 :
79 6 : client_response patch(const std::string& url, std::string body = {},
80 : std::string content_type = "application/json", headers_map headers = {}) {
81 6 : return exec(http_client_base::patch(url, std::move(body), std::move(content_type), std::move(headers)));
82 : }
83 :
84 4 : client_response del(const std::string& url, headers_map headers = {}) {
85 4 : return exec(http_client_base::del(url, std::move(headers)));
86 : }
87 :
88 4 : client_response head(const std::string& url, headers_map headers = {}) {
89 4 : return exec(http_client_base::head(url, std::move(headers)));
90 : }
91 :
92 2 : client_response options(const std::string& url, headers_map headers = {}) {
93 2 : return exec(http_client_base::options(url, std::move(headers)));
94 : }
95 :
96 : // Unix socket variants
97 36 : client_response get(const std::string& url, const std::string& unix_socket, headers_map headers = {}) {
98 36 : return exec(http_client_base::get(url, unix_socket, std::move(headers)));
99 : }
100 :
101 6 : client_response post(const std::string& url, const std::string& unix_socket,
102 : std::string body = {}, std::string content_type = "application/json",
103 : headers_map headers = {}) {
104 6 : return exec(http_client_base::post(url, unix_socket, std::move(body), std::move(content_type), std::move(headers)));
105 : }
106 :
107 : // Generic send with custom request
108 50 : client_response send(std::shared_ptr<http_request> request) {
109 50 : return exec(http_client_base::send(std::move(request)));
110 : }
111 :
112 : // Streaming send
113 2 : stream_result send_streaming(std::shared_ptr<http_request> request, stream_callback callback) {
114 2 : return exec(http_client_base::send_streaming(std::move(request), std::move(callback)));
115 : }
116 :
117 : // ============================================
118 : // Request builder for fluent API
119 : // ============================================
120 :
121 : /**
122 : * Create a request builder for fluent API with streaming support.
123 : *
124 : * Usage:
125 : * auto res = client.request("https://api.com/data")
126 : * .header("Authorization", "Bearer xxx")
127 : * .get();
128 : */
129 55 : request_builder<client> request(const std::string& url) {
130 55 : return request_builder<client>(this, url);
131 : }
132 :
133 : // ============================================
134 : // WebSocket
135 : // ============================================
136 :
137 : /**
138 : * Connect to a WebSocket server (simple API).
139 : *
140 : * Usage:
141 : * if (auto ws = client.websocket("ws://server.com/path")) {
142 : * ws->send_text("Hello!");
143 : * auto [msg, binary] = ws->receive();
144 : * ws->close();
145 : * }
146 : */
147 20 : std::optional<websocket_client> websocket(const std::string& url,
148 : const std::string& subprotocol = "") {
149 20 : return exec(http_client_base::upgrade_websocket(url, subprotocol));
150 : }
151 :
152 : /**
153 : * Connect to a WebSocket server with custom request/headers (used by request_builder).
154 : */
155 8 : std::optional<websocket_client> websocket(std::shared_ptr<http_request> request,
156 : const std::string& subprotocol = "") {
157 8 : return exec(http_client_base::upgrade_websocket(std::move(request), subprotocol));
158 : }
159 : };
160 :
161 : } // namespace thinger::http
162 :
163 : #endif
|