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 515 : T exec(awaitable<T> coro) {
39 515 : T result;
40 1030 : co_spawn(io_context_, [&result, coro = std::move(coro)]() mutable -> awaitable<void> {
41 : result = co_await std::move(coro);
42 : }, detached);
43 515 : io_context_.run();
44 515 : io_context_.restart();
45 515 : return result;
46 0 : }
47 :
48 : protected:
49 537 : boost::asio::io_context& get_io_context() override {
50 537 : return io_context_;
51 : }
52 :
53 : public:
54 568 : client() = default;
55 568 : ~client() override = default;
56 :
57 : // ============================================
58 : // Synchronous HTTP methods
59 : // ============================================
60 :
61 292 : client_response get(const std::string& url, headers_map headers = {}) {
62 292 : return exec(http_client_base::get(url, std::move(headers)));
63 : }
64 :
65 94 : client_response post(const std::string& url, std::string body = {},
66 : std::string content_type = "application/json", headers_map headers = {}) {
67 94 : 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 19 : client_response put(const std::string& url, std::string body = {},
75 : std::string content_type = "application/json", headers_map headers = {}) {
76 19 : return exec(http_client_base::put(url, std::move(body), std::move(content_type), std::move(headers)));
77 : }
78 :
79 15 : client_response patch(const std::string& url, std::string body = {},
80 : std::string content_type = "application/json", headers_map headers = {}) {
81 15 : return exec(http_client_base::patch(url, std::move(body), std::move(content_type), std::move(headers)));
82 : }
83 :
84 7 : client_response del(const std::string& url, headers_map headers = {}) {
85 7 : return exec(http_client_base::del(url, std::move(headers)));
86 : }
87 :
88 7 : client_response head(const std::string& url, headers_map headers = {}) {
89 7 : return exec(http_client_base::head(url, std::move(headers)));
90 : }
91 :
92 7 : client_response options(const std::string& url, headers_map headers = {}) {
93 7 : return exec(http_client_base::options(url, std::move(headers)));
94 : }
95 :
96 : // Generic send with custom request
97 44 : client_response send(std::shared_ptr<http_request> request) {
98 44 : return exec(http_client_base::send(std::move(request)));
99 : }
100 :
101 : // Streaming send
102 2 : stream_result send_streaming(std::shared_ptr<http_request> request, stream_callback callback) {
103 2 : return exec(http_client_base::send_streaming(std::move(request), std::move(callback)));
104 : }
105 :
106 : // ============================================
107 : // Request builder for fluent API
108 : // ============================================
109 :
110 : /**
111 : * Create a request builder for fluent API with streaming support.
112 : *
113 : * Usage:
114 : * auto res = client.request("https://api.com/data")
115 : * .header("Authorization", "Bearer xxx")
116 : * .get();
117 : */
118 55 : request_builder<client> request(const std::string& url) {
119 55 : return request_builder<client>(this, url);
120 : }
121 :
122 : // ============================================
123 : // WebSocket
124 : // ============================================
125 :
126 : /**
127 : * Connect to a WebSocket server (simple API).
128 : *
129 : * Usage:
130 : * if (auto ws = client.websocket("ws://server.com/path")) {
131 : * ws->send_text("Hello!");
132 : * auto [msg, binary] = ws->receive();
133 : * ws->close();
134 : * }
135 : */
136 20 : std::optional<websocket_client> websocket(const std::string& url,
137 : const std::string& subprotocol = "") {
138 20 : return exec(http_client_base::upgrade_websocket(url, subprotocol));
139 : }
140 :
141 : /**
142 : * Connect to a WebSocket server with custom request/headers (used by request_builder).
143 : */
144 8 : std::optional<websocket_client> websocket(std::shared_ptr<http_request> request,
145 : const std::string& subprotocol = "") {
146 8 : return exec(http_client_base::upgrade_websocket(std::move(request), subprotocol));
147 : }
148 : };
149 :
150 : } // namespace thinger::http
151 :
152 : #endif
|