LCOV - code coverage report
Current view: top level - http/client - client.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 39 39
Test Date: 2026-11-10 10:38:07 Functions: 100.0 % 24 24

            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 "../../asio/io_context_of.hpp"
       7              : #include <boost/asio/io_context.hpp>
       8              : #include <boost/asio/use_future.hpp>
       9              : #include <chrono>
      10              : #include <future>
      11              : #include <stdexcept>
      12              : 
      13              : namespace thinger::http {
      14              : 
      15              : /**
      16              :  * Standalone HTTP client with simple synchronous API.
      17              :  * Perfect for scripts, CLI tools, and simple applications.
      18              :  *
      19              :  * Usage:
      20              :  *   http::client client;
      21              :  *
      22              :  *   // Simple GET
      23              :  *   auto response = client.get("https://api.example.com/users");
      24              :  *   if (response) {
      25              :  *       std::cout << response.body() << std::endl;
      26              :  *   }
      27              :  *
      28              :  *   // POST with JSON
      29              :  *   auto response = client.post("https://api.example.com/users", R"({"name":"test"})");
      30              :  *
      31              :  *   // With configuration (fluent API)
      32              :  *   client.timeout(std::chrono::seconds(30)).verify_ssl(false);
      33              :  *   auto response = client.get("https://internal-api/data");
      34              :  *
      35              :  * For async/concurrent operations, use http::async_client instead.
      36              :  */
      37              : class client : public http_client_base {
      38              : private:
      39              :     boost::asio::io_context io_context_;
      40              : 
      41              :     // Internal helper to run an awaitable synchronously: until it completes (an open
      42              :     // WebSocket keeps the io_context busy with its keepalive). Refused within a handler of the
      43              :     // io_context (e.g. a stream callback), which would run it re-entrantly
      44              :     template<typename T>
      45         2671 :     T exec(awaitable<T> coro) {
      46         2671 :         if (io_context_.get_executor().running_in_this_thread()) {
      47            3 :             throw std::logic_error("http::client: synchronous call made within one of its own callbacks, "
      48              :                                    "where it would run its io_context re-entrantly");
      49              :         }
      50         2668 :         auto result = co_spawn(io_context_, std::move(coro), boost::asio::use_future);
      51        11276 :         asio::run_until(io_context_, [&] {
      52        41043 :             return result.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
      53              :         });
      54         5336 :         return result.get();
      55         2668 :     }
      56              : 
      57              : protected:
      58         2165 :     boost::asio::io_context& get_io_context() override {
      59         2165 :         return io_context_;
      60              :     }
      61              : 
      62          143 :     bool owns_io_context() const override { return true; }
      63              : 
      64              : public:
      65              :     // The io_context runs only during a call, until its operation completes (as the WebSocket
      66              :     // calls): idle connections are not closed by timers, but when found expired as a request
      67              :     // starts or ends. Between calls nothing runs: clear_connections() closes them
      68         1899 :     client() { pool_.use_idle_timers(false); }
      69              :     // The idle connections are closed while their io_context exists
      70         1908 :     ~client() override { clear_connections(); }
      71              : 
      72              :     // ============================================
      73              :     // Synchronous HTTP methods
      74              :     // ============================================
      75              : 
      76         1791 :     client_response get(const std::string& url, headers_map headers = {}) {
      77         1797 :         return exec(http_client_base::get(url, std::move(headers)));
      78              :     }
      79              : 
      80          317 :     client_response post(const std::string& url, std::string body = {},
      81              :                          std::string content_type = "application/json", headers_map headers = {}) {
      82          317 :         return exec(http_client_base::post(url, std::move(body), std::move(content_type), std::move(headers)));
      83              :     }
      84              : 
      85            3 :     client_response post(const std::string& url, const form& form, headers_map headers = {}) {
      86            3 :         return exec(http_client_base::post(url, form, std::move(headers)));
      87              :     }
      88              : 
      89           27 :     client_response put(const std::string& url, std::string body = {},
      90              :                         std::string content_type = "application/json", headers_map headers = {}) {
      91           27 :         return exec(http_client_base::put(url, std::move(body), std::move(content_type), std::move(headers)));
      92              :     }
      93              : 
      94           18 :     client_response patch(const std::string& url, std::string body = {},
      95              :                           std::string content_type = "application/json", headers_map headers = {}) {
      96           18 :         return exec(http_client_base::patch(url, std::move(body), std::move(content_type), std::move(headers)));
      97              :     }
      98              : 
      99           17 :     client_response del(const std::string& url, headers_map headers = {}) {
     100           17 :         return exec(http_client_base::del(url, std::move(headers)));
     101              :     }
     102              : 
     103           19 :     client_response head(const std::string& url, headers_map headers = {}) {
     104           19 :         return exec(http_client_base::head(url, std::move(headers)));
     105              :     }
     106              : 
     107            8 :     client_response options(const std::string& url, headers_map headers = {}) {
     108            8 :         return exec(http_client_base::options(url, std::move(headers)));
     109              :     }
     110              : 
     111              :     // Generic send with custom request
     112          215 :     client_response send(std::shared_ptr<http_request> request) {
     113          215 :         return exec(http_client_base::send(std::move(request)));
     114              :     }
     115              : 
     116              :     // Streaming send
     117           89 :     stream_result send_streaming(std::shared_ptr<http_request> request, stream_callback callback) {
     118           89 :         return exec(http_client_base::send_streaming(std::move(request), std::move(callback)));
     119              :     }
     120              : 
     121              :     // ============================================
     122              :     // Request builder for fluent API
     123              :     // ============================================
     124              : 
     125              :     /**
     126              :      * Create a request builder for fluent API with streaming support.
     127              :      *
     128              :      * Usage:
     129              :      *   auto res = client.request("https://api.com/data")
     130              :      *       .header("Authorization", "Bearer xxx")
     131              :      *       .get();
     132              :      */
     133          205 :     request_builder<client> request(const std::string& url) {
     134          205 :         return request_builder<client>(this, url);
     135              :     }
     136              : 
     137              :     // ============================================
     138              :     // WebSocket
     139              :     // ============================================
     140              : 
     141              :     /**
     142              :      * Connect to a WebSocket server (simple API).
     143              :      *
     144              :      * Usage:
     145              :      *   if (auto ws = client.websocket("ws://server.com/path")) {
     146              :      *       ws->send_text("Hello!");
     147              :      *       auto [msg, binary] = ws->receive();
     148              :      *       ws->close();
     149              :      *   }
     150              :      */
     151          150 :     std::optional<websocket_client> websocket(const std::string& url,
     152              :                                                const std::string& subprotocol = "") {
     153          150 :         return exec(http_client_base::upgrade_websocket(url, subprotocol));
     154              :     }
     155              : 
     156              :     /**
     157              :      * Connect to a WebSocket server with custom request/headers (used by request_builder).
     158              :      */
     159           17 :     std::optional<websocket_client> websocket(std::shared_ptr<http_request> request,
     160              :                                                const std::string& subprotocol = "") {
     161           17 :         return exec(http_client_base::upgrade_websocket(std::move(request), subprotocol));
     162              :     }
     163              : };
     164              : 
     165              : } // namespace thinger::http
     166              : 
     167              : #endif
        

Generated by: LCOV version 2.0-1