Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
http2_client.h
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2024, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
5#pragma once
6
7#include "frame.h"
8#include "hpack.h"
10
11#include <asio.hpp>
12#include <asio/ssl.hpp>
13
14#include <atomic>
15#include <chrono>
16#include <condition_variable>
17#include <cstdint>
18#include <deque>
19#include <functional>
20#include <future>
21#include <map>
22#include <memory>
23#include <mutex>
24#include <optional>
25#include <string>
26#include <vector>
27
28#if defined(NETWORK_ENABLE_TEST_INJECTION)
29namespace kcenon::network::tests::support
30{
31 class http2_client_test_access;
32} // namespace kcenon::network::tests::support
33#endif
34
36{
41 enum class stream_state
42 {
43 idle,
44 open,
47 closed
48 };
49
55 {
56 int status_code = 0;
57 std::vector<http_header> headers;
58 std::vector<uint8_t> body;
59
65 auto get_header(const std::string& name) const -> std::optional<std::string>;
66
71 auto get_body_string() const -> std::string;
72 };
73
79 {
80 uint32_t stream_id = 0;
81 std::atomic<stream_state> state{stream_state::idle};
82 std::vector<http_header> request_headers;
83 std::vector<http_header> response_headers;
84 std::vector<uint8_t> request_body;
85 std::vector<uint8_t> response_body;
86 int32_t window_size = 65535;
87 std::promise<http2_response> promise;
88 bool headers_complete = false;
89 bool body_complete = false;
90
92 bool is_streaming = false;
93 std::function<void(std::vector<uint8_t>)> on_data;
94 std::function<void(std::vector<http_header>)> on_headers;
95 std::function<void(int)> on_complete;
96
97 http2_stream() = default;
98 http2_stream(http2_stream&& other) noexcept { *this = std::move(other); }
99 auto operator=(http2_stream&& other) noexcept -> http2_stream&
100 {
101 if (this == &other) return *this;
102 stream_id = other.stream_id;
103 state.store(other.state.load());
104 request_headers = std::move(other.request_headers);
105 response_headers = std::move(other.response_headers);
106 request_body = std::move(other.request_body);
107 response_body = std::move(other.response_body);
108 window_size = other.window_size;
109 promise = std::move(other.promise);
110 headers_complete = other.headers_complete;
111 body_complete = other.body_complete;
112 is_streaming = other.is_streaming;
113 on_data = std::move(other.on_data);
114 on_headers = std::move(other.on_headers);
115 on_complete = std::move(other.on_complete);
116 return *this;
117 }
118 };
119
125 {
126 uint32_t header_table_size = 4096;
127 bool enable_push = false;
128 uint32_t max_concurrent_streams = 100;
129 uint32_t initial_window_size = 65535;
130 uint32_t max_frame_size = 16384;
131 uint32_t max_header_list_size = 8192;
132 };
133
180 class http2_client : public std::enable_shared_from_this<http2_client>
181 {
182 public:
187 explicit http2_client(std::string_view client_id);
188
193
194 // Non-copyable
195 http2_client(const http2_client&) = delete;
197
198 // Non-movable (contains std::atomic members)
201
211 auto connect(const std::string& host, unsigned short port = 443) -> VoidResult;
212
219 auto disconnect() -> VoidResult;
220
225 auto is_connected() const -> bool;
226
233 auto get(const std::string& path,
234 const std::vector<http_header>& headers = {})
236
244 auto post(const std::string& path,
245 const std::string& body,
246 const std::vector<http_header>& headers = {})
248
256 auto post(const std::string& path,
257 const std::vector<uint8_t>& body,
258 const std::vector<http_header>& headers = {})
260
268 auto put(const std::string& path,
269 const std::string& body,
270 const std::vector<http_header>& headers = {})
271 -> Result<http2_response>;
272
279 auto del(const std::string& path,
280 const std::vector<http_header>& headers = {})
281 -> Result<http2_response>;
282
287 auto set_timeout(std::chrono::milliseconds timeout) -> void;
288
293 auto get_timeout() const -> std::chrono::milliseconds;
294
306 auto start_stream(const std::string& path,
307 const std::vector<http_header>& headers,
308 std::function<void(std::vector<uint8_t>)> on_data,
309 std::function<void(std::vector<http_header>)> on_headers,
310 std::function<void(int)> on_complete)
311 -> Result<uint32_t>;
312
320 auto write_stream(uint32_t stream_id,
321 const std::vector<uint8_t>& data,
322 bool end_stream = false) -> VoidResult;
323
329 auto close_stream_writer(uint32_t stream_id) -> VoidResult;
330
336 auto cancel_stream(uint32_t stream_id) -> VoidResult;
337
342 auto get_settings() const -> http2_settings;
343
348 auto set_settings(const http2_settings& settings) -> void;
349
350 private:
351 // Connection management
352 auto send_connection_preface() -> VoidResult;
353 auto send_settings() -> VoidResult;
354 auto handle_settings_frame(const settings_frame& frame) -> VoidResult;
355 auto send_settings_ack() -> VoidResult;
356
357 // Frame I/O
358 auto send_frame(const frame& f) -> VoidResult;
359 auto read_next_frame() -> void;
360 auto write_next_frame() -> void;
361 auto process_frame(std::unique_ptr<frame> f) -> VoidResult;
362
363 // Stream management
364 auto allocate_stream_id() -> uint32_t;
365 auto get_stream(uint32_t stream_id) -> http2_stream*;
366 auto create_stream() -> http2_stream&;
367 auto close_stream(uint32_t stream_id) -> void;
368
369 // Request handling
370 auto send_request(const std::string& method,
371 const std::string& path,
372 const std::vector<http_header>& headers,
373 const std::vector<uint8_t>& body)
374 -> Result<http2_response>;
375 auto build_headers(const std::string& method,
376 const std::string& path,
377 const std::vector<http_header>& additional) -> std::vector<http_header>;
378
379 // Response handling
380 auto handle_headers_frame(const headers_frame& f) -> VoidResult;
381 auto handle_data_frame(const data_frame& f) -> VoidResult;
382 auto handle_rst_stream_frame(const rst_stream_frame& f) -> VoidResult;
383 auto handle_goaway_frame(const goaway_frame& f) -> VoidResult;
384 auto handle_window_update_frame(const window_update_frame& f) -> VoidResult;
385 auto handle_ping_frame(const ping_frame& f) -> VoidResult;
386
387 // I/O thread
388 auto run_io() -> void;
389 auto stop_io() -> void;
390
391 // Member variables
392 std::string client_id_;
393 std::string host_;
394 unsigned short port_ = 443;
395
396 // ASIO context
397 // Serialize connection setup and teardown, including failed handshakes.
398 std::mutex lifecycle_mutex_;
399 std::unique_ptr<asio::io_context> io_context_;
400 std::unique_ptr<asio::ssl::context> ssl_context_;
401 std::unique_ptr<asio::ssl::stream<asio::ip::tcp::socket>> socket_;
402 std::unique_ptr<asio::executor_work_guard<asio::io_context::executor_type>> work_guard_;
403 std::future<void> io_future_;
404 // The I/O thread owns every SSL operation after connection setup.
405 // Protect submission against shutdown; queued buffers live through
406 // asynchronous completion and writes never overlap one another.
407 std::mutex io_submission_mutex_;
408 bool io_active_ = false;
410 {
411 std::vector<uint8_t> data;
412 std::shared_ptr<std::promise<VoidResult>> completion;
413 };
414 std::deque<pending_write> pending_writes_;
415
416 // Connection state
417 std::atomic<bool> is_connected_{false};
418 std::atomic<bool> is_running_{false};
419 std::atomic<bool> goaway_received_{false};
420
421 // Stream management
422 std::mutex streams_mutex_;
423 std::map<uint32_t, http2_stream> streams_;
424 std::atomic<uint32_t> next_stream_id_{1};
425 int32_t connection_window_size_ = 65535;
426
427 // Settings (must be declared before encoder_/decoder_ for initialization order)
430
431 // HPACK (uses local_settings_.header_table_size in constructor)
434
435 // Timeout
436 std::atomic<std::chrono::milliseconds> timeout_{std::chrono::milliseconds(30000)};
437
438 // Read buffer
439 std::vector<uint8_t> read_buffer_;
440
441 // Constants
442 static constexpr std::string_view CONNECTION_PREFACE =
443 "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
444 static constexpr size_t FRAME_HEADER_SIZE = 9;
445 static constexpr size_t DEFAULT_WINDOW_SIZE = 65535;
446
447#if defined(NETWORK_ENABLE_TEST_INJECTION)
448 // Test-only: grants tests/support/http2_client_test_access access to
449 // the private process_frame() dispatcher and selected member fields
450 // without leaking them through the public API. Used by
451 // tests/unit/http2_client_branch_test.cpp to bypass the SETTINGS
452 // handshake gate that exceeds 15 s under coverage instrumentation
453 // (Issue #1115).
454 friend class kcenon::network::tests::support::http2_client_test_access;
455#endif
456 };
457
458} // namespace kcenon::network::protocols::http2
HPACK header decoder (RFC 7541)
Definition hpack.h:209
HPACK header encoder (RFC 7541)
Definition hpack.h:159
http2_client & operator=(http2_client &&)=delete
http2_client & operator=(const http2_client &)=delete
http2_client(const http2_client &)=delete
std::map< uint32_t, http2_stream > streams_
void set_timeout(D duration)
Set a timeout using any duration type.
@ max_header_list_size
SETTINGS_MAX_HEADER_LIST_SIZE.
@ max_concurrent_streams
SETTINGS_MAX_CONCURRENT_STREAMS.
@ initial_window_size
SETTINGS_INITIAL_WINDOW_SIZE.
stream_state
HTTP/2 stream state (RFC 7540 Section 5.1)
@ half_closed_remote
Remote end closed, local can send.
@ half_closed_local
Local end closed, remote can send.
std::mutex mutex
Network-specific error and result type definitions.
std::shared_ptr< std::promise< VoidResult > > completion
std::vector< uint8_t > body
Response body.
auto get_header(const std::string &name) const -> std::optional< std::string >
Get header value by name.
auto get_body_string() const -> std::string
Get body as string.
std::vector< http_header > headers
Response headers.
std::vector< uint8_t > request_body
Request body.
std::vector< http_header > response_headers
Response headers.
std::promise< http2_response > promise
Response promise.
auto operator=(http2_stream &&other) noexcept -> http2_stream &
std::vector< http_header > request_headers
Request headers.
std::function< void(std::vector< uint8_t >)> on_data
Callback for streaming data.
std::function< void(std::vector< http_header >)> on_headers
Callback for headers.
std::function< void(int)> on_complete
Callback when stream ends (status code)
std::vector< uint8_t > response_body
Response body.
http2_stream(http2_stream &&other) noexcept
HTTP header name-value pair.
Definition hpack.h:24