20 std::string lower_name = name;
21 std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(),
22 [](
unsigned char c) { return std::tolower(c); });
24 for (
const auto& header :
headers)
26 std::string lower_header_name = header.name;
27 std::transform(lower_header_name.begin(), lower_header_name.end(),
28 lower_header_name.begin(),
29 [](
unsigned char c) { return std::tolower(c); });
31 if (lower_header_name == lower_name)
41 return std::string(
body.begin(),
body.end());
46 : client_id_(client_id)
66 std::lock_guard<std::mutex> lifecycle_lock(lifecycle_mutex_);
73 span->set_attribute(
"net.peer.name", host)
74 .set_attribute(
"net.peer.port",
static_cast<int64_t
>(port))
75 .set_attribute(
"net.transport",
"tcp")
76 .set_attribute(
"http.flavor",
"2.0")
77 .set_attribute(
"client.id", client_id_);
84 span->set_error(
"Already connected");
87 "Already connected",
"http2_client::connect");
94 span->set_error(
"Host cannot be empty");
97 "Host cannot be empty",
"http2_client::connect");
110 ssl_context_.reset();
112 goaway_received_ =
false;
115 io_context_ = std::make_unique<asio::io_context>();
118 ssl_context_ = std::make_unique<asio::ssl::context>(asio::ssl::context::tlsv13_client);
121 SSL_CTX* native_ctx = ssl_context_->native_handle();
122 static const unsigned char alpn_protos[] = { 2,
'h',
'2' };
123 SSL_CTX_set_alpn_protos(native_ctx, alpn_protos,
sizeof(alpn_protos));
126 ssl_context_->set_default_verify_paths();
127 ssl_context_->set_verify_mode(asio::ssl::verify_peer);
130 asio::ip::tcp::resolver resolver(*io_context_);
131 auto endpoints = resolver.resolve(host, std::to_string(port));
133 socket_ = std::make_unique<asio::ssl::stream<asio::ip::tcp::socket>>(
134 *io_context_, *ssl_context_);
137 SSL_set_tlsext_host_name(socket_->native_handle(), host.c_str());
140 asio::connect(socket_->lowest_layer(), endpoints);
143 socket_->handshake(asio::ssl::stream_base::client);
146 const unsigned char* alpn_result =
nullptr;
147 unsigned int alpn_len = 0;
148 SSL_get0_alpn_selected(socket_->native_handle(), &alpn_result, &alpn_len);
150 if (alpn_len != 2 || alpn_result ==
nullptr ||
151 std::string(
reinterpret_cast<const char*
>(alpn_result), alpn_len) !=
"h2")
153 socket_->lowest_layer().close();
156 span->set_error(
"Server does not support HTTP/2 via ALPN");
159 "Server does not support HTTP/2 via ALPN",
160 "http2_client::connect");
166 auto preface_result = send_connection_preface();
167 if (preface_result.is_err())
169 is_connected_ =
false;
173 span->set_error(preface_result.error().message);
175 return preface_result;
179 auto settings_result = send_settings();
180 if (settings_result.is_err())
182 is_connected_ =
false;
186 span->set_error(settings_result.error().message);
188 return settings_result;
195 const auto deadline = std::chrono::steady_clock::now() + timeout_.load();
196 auto read_preface_bytes = [&](asio::mutable_buffer buffer) {
197 asio::steady_timer timer(*io_context_, deadline);
198 std::error_code read_error;
199 bool expired =
false;
200 timer.async_wait([&](std::error_code ec) {
203 std::error_code ignored;
204 socket_->lowest_layer().cancel(ignored);
207 asio::async_read(*socket_, buffer,
208 [&](std::error_code ec, std::size_t) {
212 io_context_->restart();
214 if (expired)
throw std::runtime_error(
"Server SETTINGS timed out");
215 if (read_error)
throw std::system_error(read_error);
217 std::vector<uint8_t> initial_header(FRAME_HEADER_SIZE);
218 read_preface_bytes(asio::buffer(initial_header));
221 header.value().stream_id != 0 ||
223 header.value().length > local_settings_.max_frame_size) {
224 throw std::runtime_error(
"Invalid server SETTINGS preface");
226 std::vector<uint8_t> initial_payload(header.value().length);
227 if (!initial_payload.empty()) read_preface_bytes(asio::buffer(initial_payload));
228 initial_header.insert(initial_header.end(), initial_payload.begin(), initial_payload.end());
230 if (initial_frame.is_err())
throw std::runtime_error(
"Invalid server SETTINGS payload");
231 auto initial_result = process_frame(std::move(initial_frame.value()));
232 if (initial_result.is_err())
throw std::runtime_error(initial_result.error().message);
237 work_guard_ = std::make_unique<asio::executor_work_guard<asio::io_context::executor_type>>(
238 asio::make_work_guard(*io_context_));
239 io_context_->restart();
241 std::lock_guard<std::mutex> lock(io_submission_mutex_);
243 is_connected_ =
true;
244 io_future_ = std::async(std::launch::async, [
this]() { run_io(); });
253 catch (
const std::exception& e)
255 is_connected_ =
false;
260 span->set_error(std::string(
"Connection failed: ") + e.what());
263 std::string(
"Connection failed: ") + e.what(),
264 "http2_client::connect");
270 std::lock_guard<std::mutex> lifecycle_lock(lifecycle_mutex_);
280 if (socket_ && socket_->lowest_layer().is_open())
294 is_connected_ =
false;
305 const std::vector<http_header>&
headers)
308 return send_request(
"GET", path,
headers, {});
312 const std::string& body,
313 const std::vector<http_header>&
headers)
316 std::vector<uint8_t> body_bytes(body.begin(), body.end());
317 return send_request(
"POST", path,
headers, body_bytes);
321 const std::vector<uint8_t>& body,
322 const std::vector<http_header>&
headers)
325 return send_request(
"POST", path,
headers, body);
329 const std::string& body,
330 const std::vector<http_header>&
headers)
333 std::vector<uint8_t> body_bytes(body.begin(), body.end());
334 return send_request(
"PUT", path,
headers, body_bytes);
338 const std::vector<http_header>&
headers)
341 return send_request(
"DELETE", path,
headers, {});
362 encoder_.set_max_table_size(
settings.header_table_size);
363 decoder_.set_max_table_size(
settings.header_table_size);
367 const std::vector<http_header>&
headers,
368 std::function<
void(std::vector<uint8_t>)> on_data,
369 std::function<
void(std::vector<http_header>)> on_headers,
370 std::function<
void(
int)> on_complete)
377 "http2_client::start_stream");
384 stream.
on_data = std::move(on_data);
389 auto request_headers = build_headers(
"POST", path,
headers);
393 auto encoded_headers = encoder_.encode(request_headers);
398 auto send_result = send_frame(hf);
399 if (send_result.is_err())
402 const auto& err = send_result.error();
404 "http2_client::start_stream",
409 return ok(std::move(result_id));
413 const std::vector<uint8_t>&
data,
420 "http2_client::write_stream");
423 auto* stream = get_stream(stream_id);
428 "http2_client::write_stream");
435 "Stream not writable",
436 "http2_client::write_stream");
442 auto send_result = send_frame(df);
443 if (send_result.is_err())
457 "http2_client::close_stream_writer");
460 auto* stream = get_stream(stream_id);
465 "http2_client::close_stream_writer");
478 auto send_result = send_frame(df);
479 if (send_result.is_err())
493 "http2_client::cancel_stream");
496 auto* stream = get_stream(stream_id);
501 "http2_client::cancel_stream");
511 auto send_result = send_frame(rsf);
512 if (send_result.is_err())
527 asio::write(*socket_,
528 asio::buffer(CONNECTION_PREFACE.data(), CONNECTION_PREFACE.size()));
531 catch (
const std::exception& e)
534 std::string(
"Failed to send connection preface: ") + e.what(),
535 "http2_client::send_connection_preface");
541 std::vector<setting_parameter> params = {
543 local_settings_.header_table_size},
545 local_settings_.enable_push ? 1u : 0u},
547 local_settings_.max_concurrent_streams},
549 local_settings_.initial_window_size},
551 local_settings_.max_frame_size},
553 local_settings_.max_header_list_size}
569 for (
const auto& param :
frame.settings())
574 remote_settings_.header_table_size = param.value;
575 encoder_.set_max_table_size(param.value);
578 remote_settings_.enable_push = (param.value != 0);
581 remote_settings_.max_concurrent_streams = param.value;
584 remote_settings_.initial_window_size = param.value;
587 remote_settings_.max_frame_size = param.value;
590 remote_settings_.max_header_list_size = param.value;
595 return send_settings_ack();
601 return send_frame(ack);
606 std::unique_lock<std::mutex> lock(io_submission_mutex_);
607 if (!socket_ || !is_running_)
610 "Connection closed",
"http2_client::send_frame");
615 auto data = f.serialize();
619 asio::write(*socket_, asio::buffer(
data));
623 auto completion = std::make_shared<std::promise<VoidResult>>();
624 auto result = completion->get_future();
625 const bool on_io_thread = io_context_->get_executor().running_in_this_thread();
626 asio::post(*io_context_, [
this,
data = std::move(
data), completion]()
mutable {
631 "Connection closed",
"http2_client::send_frame"));
634 const bool idle = pending_writes_.empty();
635 pending_writes_.push_back({std::move(
data), completion});
636 if (
idle) write_next_frame();
641 return on_io_thread ?
ok() : result.get();
643 catch (
const std::exception& e)
646 std::string(
"Failed to send frame: ") + e.what(),
647 "http2_client::send_frame");
653 if (pending_writes_.empty())
return;
654 asio::async_write(*socket_, asio::buffer(pending_writes_.front().data),
655 [
this](std::error_code ec, std::size_t) {
656 auto completion = std::move(pending_writes_.front().completion);
657 pending_writes_.pop_front();
660 is_connected_ = false;
661 completion->set_value(error_void(
662 error_codes::network_system::send_failed, ec.message(),
663 "http2_client::send_frame"));
664 while (!pending_writes_.empty())
666 pending_writes_.front().completion->set_value(error_void(
667 error_codes::network_system::send_failed, ec.message(),
668 "http2_client::send_frame"));
669 pending_writes_.pop_front();
673 completion->set_value(ok());
678 auto http2_client::read_next_frame() ->
void
680 if (!is_running_)
return;
681 auto bytes = std::make_shared<std::vector<uint8_t>>(FRAME_HEADER_SIZE);
682 asio::async_read(*socket_, asio::buffer(*bytes),
683 [
this, bytes](std::error_code ec, std::size_t) {
684 if (ec || !is_running_)
686 if (ec) is_connected_ =
false;
689 auto header = frame_header::parse(*bytes);
692 is_connected_ =
false;
695 const auto length = header.value().length;
696 bytes->resize(FRAME_HEADER_SIZE + length);
697 auto complete = [
this, bytes](std::error_code payload_error, std::size_t) {
698 if (payload_error || !is_running_)
700 if (payload_error) is_connected_ =
false;
705 auto parsed = frame::parse(*bytes);
708 is_connected_ =
false;
711 process_frame(std::move(parsed.value()));
716 is_connected_ =
false;
722 asio::async_read(*socket_,
723 asio::buffer(bytes->data() + FRAME_HEADER_SIZE, length),
724 std::move(complete));
728 auto http2_client::process_frame(std::unique_ptr<frame> f) ->
VoidResult
733 "Null frame",
"http2_client::process_frame");
736 switch (f->header().type)
738 case frame_type::settings:
741 return handle_settings_frame(*sf);
745 case frame_type::headers:
748 return handle_headers_frame(*hf);
752 case frame_type::data:
753 if (
auto* df =
dynamic_cast<data_frame*
>(f.get()))
755 return handle_data_frame(*df);
759 case frame_type::rst_stream:
762 return handle_rst_stream_frame(*rf);
766 case frame_type::goaway:
769 return handle_goaway_frame(*gf);
773 case frame_type::window_update:
776 return handle_window_update_frame(*wf);
780 case frame_type::ping:
781 if (
auto* pf =
dynamic_cast<ping_frame*
>(f.get()))
783 return handle_ping_frame(*pf);
795 auto http2_client::allocate_stream_id() -> uint32_t
797 uint32_t
id = next_stream_id_.fetch_add(2);
803 std::lock_guard<std::mutex> lock(streams_mutex_);
804 auto it = streams_.find(stream_id);
805 if (it != streams_.end())
814 std::lock_guard<std::mutex> lock(streams_mutex_);
815 uint32_t stream_id = allocate_stream_id();
816 auto& stream = streams_[stream_id];
817 stream.stream_id = stream_id;
818 stream.state = stream_state::idle;
819 stream.window_size = remote_settings_.initial_window_size;
823 auto http2_client::close_stream(uint32_t stream_id) ->
void
825 std::lock_guard<std::mutex> lock(streams_mutex_);
826 auto it = streams_.find(stream_id);
827 if (it != streams_.end())
829 it->second.state = stream_state::closed;
833 auto http2_client::send_request(
const std::string& method,
834 const std::string& path,
835 const std::vector<http_header>& headers,
836 const std::vector<uint8_t>& body)
845 span->set_attribute(
"http.method", method)
846 .set_attribute(
"http.url",
"https://" + host_ +
":" + std::to_string(port_) + path)
847 .set_attribute(
"http.flavor",
"2.0")
848 .set_attribute(
"http.request.body_size",
static_cast<int64_t
>(body.size()))
849 .set_attribute(
"net.peer.name", host_)
850 .set_attribute(
"net.peer.port",
static_cast<int64_t
>(port_));
857 span->set_error(
"Not connected");
861 "http2_client::send_request");
866 stream.
state = stream_state::open;
870 span->set_attribute(
"http2.stream_id",
static_cast<int64_t
>(stream.
stream_id));
874 auto request_headers = build_headers(method, path,
headers);
879 auto encoded_headers = encoder_.encode(request_headers);
882 bool end_stream = body.empty();
883 if (end_stream) stream.
state = stream_state::half_closed_local;
886 auto send_result = send_frame(hf);
887 if (send_result.is_err())
890 const auto& err = send_result.error();
893 span->set_error(err.message);
896 "http2_client::send_request",
903 stream.
state = stream_state::half_closed_local;
905 send_result = send_frame(df);
906 if (send_result.is_err())
909 const auto& err = send_result.error();
912 span->set_error(err.message);
915 "http2_client::send_request",
921 auto future = stream.
promise.get_future();
922 auto status = future.wait_for(timeout_.load());
924 if (status == std::future_status::timeout)
929 span->set_error(
"Request timeout");
933 "http2_client::send_request");
936 auto response = future.get();
939 span->set_attribute(
"http.status_code",
static_cast<int64_t
>(response.status_code))
940 .set_attribute(
"http.response.body_size",
static_cast<int64_t
>(response.body.size()));
941 if (response.status_code >= 400)
950 return ok(std::move(response));
953 auto http2_client::build_headers(
const std::string& method,
954 const std::string& path,
955 const std::vector<http_header>& additional)
956 -> std::vector<http_header>
958 std::vector<http_header>
headers;
961 headers.emplace_back(
":method", method);
962 headers.emplace_back(
":scheme",
"https");
963 headers.emplace_back(
":authority", host_);
964 headers.emplace_back(
":path", path.empty() ?
"/" : path);
967 headers.emplace_back(
"user-agent",
"network_system/http2_client");
970 for (
const auto& h : additional)
973 if (!h.name.empty() && h.name[0] !=
':')
984 uint32_t stream_id = f.header().stream_id;
985 auto* stream = get_stream(stream_id);
991 "http2_client::handle_headers_frame");
995 auto decode_result = decoder_.decode(f.header_block());
996 if (decode_result.is_err())
998 const auto& err = decode_result.error();
1000 "http2_client::handle_headers_frame",
1004 auto& decoded_headers = decode_result.value();
1007 stream->response_headers.insert(stream->response_headers.end(),
1008 decoded_headers.begin(),
1009 decoded_headers.end());
1011 if (f.is_end_headers())
1013 stream->headers_complete =
true;
1016 if (stream->is_streaming && stream->on_headers)
1018 stream->on_headers(stream->response_headers);
1022 if (f.is_end_stream())
1024 stream->body_complete =
true;
1025 stream->state = stream_state::closed;
1028 int status_code = 0;
1029 for (
const auto& h : stream->response_headers)
1031 if (h.name ==
":status")
1035 status_code = std::stoi(h.value);
1046 if (stream->is_streaming)
1048 if (stream->on_complete)
1050 stream->on_complete(status_code);
1057 response.
headers = stream->response_headers;
1058 response.
body = stream->response_body;
1060 stream->promise.set_value(std::move(response));
1069 uint32_t stream_id = f.header().stream_id;
1070 auto* stream = get_stream(stream_id);
1075 "Unknown stream ID",
1076 "http2_client::handle_data_frame");
1080 auto data = f.data();
1083 if (stream->is_streaming && stream->on_data)
1085 stream->on_data(std::vector<uint8_t>(
data.begin(),
data.end()));
1089 stream->response_body.insert(stream->response_body.end(),
1094 stream->window_size -=
static_cast<int32_t
>(
data.size());
1095 connection_window_size_ -=
static_cast<int32_t
>(
data.size());
1098 if (stream->window_size <
static_cast<int32_t
>(DEFAULT_WINDOW_SIZE / 2))
1100 int32_t increment = DEFAULT_WINDOW_SIZE - stream->window_size;
1103 stream->window_size += increment;
1106 if (connection_window_size_ <
static_cast<int32_t
>(DEFAULT_WINDOW_SIZE / 2))
1108 int32_t increment = DEFAULT_WINDOW_SIZE - connection_window_size_;
1111 connection_window_size_ += increment;
1114 if (f.is_end_stream())
1116 stream->body_complete =
true;
1117 stream->state = stream_state::closed;
1120 int status_code = 0;
1121 for (
const auto& h : stream->response_headers)
1123 if (h.name ==
":status")
1127 status_code = std::stoi(h.value);
1138 if (stream->is_streaming)
1140 if (stream->on_complete)
1142 stream->on_complete(status_code);
1148 response.
headers = stream->response_headers;
1149 response.
body = stream->response_body;
1151 stream->promise.set_value(std::move(response));
1160 uint32_t stream_id = f.header().stream_id;
1161 auto* stream = get_stream(stream_id);
1165 stream->state = stream_state::closed;
1170 stream->promise.set_value(std::move(response));
1178 goaway_received_ =
true;
1181 uint32_t last_stream = f.last_stream_id();
1183 std::lock_guard<std::mutex> lock(streams_mutex_);
1184 for (
auto& [
id, stream] : streams_)
1186 if (
id > last_stream && stream.state != stream_state::closed)
1188 stream.state = stream_state::closed;
1191 stream.promise.set_value(std::move(response));
1201 uint32_t stream_id = f.header().stream_id;
1202 int32_t increment =
static_cast<int32_t
>(f.window_size_increment());
1206 if (connection_window_size_ > std::numeric_limits<int32_t>::max() - increment)
1211 goaway_received_ =
true;
1212 is_connected_ =
false;
1213 return error_void(
static_cast<int>(error_code::flow_control_error),
1214 "Connection flow-control window overflow",
1215 "http2_client::handle_window_update_frame");
1217 connection_window_size_ += increment;
1221 auto* stream = get_stream(stream_id);
1224 if (stream->window_size > std::numeric_limits<int32_t>::max() - increment)
1226 rst_stream_frame reset(stream_id,
static_cast<uint32_t
>(error_code::flow_control_error));
1228 if (stream->state.exchange(stream_state::closed) != stream_state::closed)
1230 if (stream->is_streaming)
1232 if (stream->on_complete)
1233 stream->on_complete(
static_cast<int>(error_code::flow_control_error));
1239 stream->promise.set_value(std::move(response));
1242 return error_void(
static_cast<int>(error_code::flow_control_error),
1243 "Stream flow-control window overflow",
1244 "http2_client::handle_window_update_frame");
1246 stream->window_size += increment;
1259 return send_frame(ack);
1264 auto http2_client::run_io() ->
void
1270 auto http2_client::stop_io() ->
void
1273 std::lock_guard<std::mutex> lock(io_submission_mutex_);
1274 is_running_ =
false;
1275 is_connected_ =
false;
1276 if (io_active_ && io_future_.valid())
1280 asio::post(*io_context_, [
this]() {
1282 socket_->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
1283 socket_->lowest_layer().close(ec);
1284 work_guard_->reset();
1292 socket_->lowest_layer().close(ec);
1294 if (work_guard_) work_guard_->reset();
1298 if (io_future_.valid()) io_future_.wait();
1299 std::lock_guard<std::mutex> lock(io_submission_mutex_);
1302 if (io_context_) io_context_->stop();
DATA frame (RFC 7540 Section 6.1)
Base class for HTTP/2 frames.
static auto parse(std::span< const uint8_t > data) -> Result< std::unique_ptr< frame > >
Parse frame from raw bytes.
GOAWAY frame (RFC 7540 Section 6.8)
~http2_client()
Destructor - closes connection gracefully.
auto send_settings_ack() -> VoidResult
auto cancel_stream(uint32_t stream_id) -> VoidResult
Cancel a stream.
auto is_connected() const -> bool
Check if connected.
auto write_stream(uint32_t stream_id, const std::vector< uint8_t > &data, bool end_stream=false) -> VoidResult
Write data to an open stream.
auto put(const std::string &path, const std::string &body, const std::vector< http_header > &headers={}) -> Result< http2_response >
Perform HTTP/2 PUT request.
auto write_next_frame() -> void
auto set_settings(const http2_settings &settings) -> void
Update local settings.
auto get_settings() const -> http2_settings
Get local settings.
std::atomic< bool > goaway_received_
http2_client(std::string_view client_id)
Construct HTTP/2 client.
auto send_frame(const frame &f) -> VoidResult
std::atomic< std::chrono::milliseconds > timeout_
std::atomic< bool > is_connected_
auto post(const std::string &path, const std::string &body, const std::vector< http_header > &headers={}) -> Result< http2_response >
Perform HTTP/2 POST request.
auto close_stream_writer(uint32_t stream_id) -> VoidResult
Close the write side of a stream.
auto handle_settings_frame(const settings_frame &frame) -> VoidResult
http2_settings local_settings_
auto send_settings() -> VoidResult
auto disconnect() -> VoidResult
Disconnect from server.
auto get(const std::string &path, const std::vector< http_header > &headers={}) -> Result< http2_response >
Perform HTTP/2 GET request.
auto del(const std::string &path, const std::vector< http_header > &headers={}) -> Result< http2_response >
Perform HTTP/2 DELETE request.
auto get_timeout() const -> std::chrono::milliseconds
Get current timeout.
auto connect(const std::string &host, unsigned short port=443) -> VoidResult
Connect to HTTP/2 server.
auto start_stream(const std::string &path, const std::vector< http_header > &headers, std::function< void(std::vector< uint8_t >)> on_data, std::function< void(std::vector< http_header >)> on_headers, std::function< void(int)> on_complete) -> Result< uint32_t >
Start a streaming POST request.
auto set_timeout(std::chrono::milliseconds timeout) -> void
Set request timeout.
auto send_connection_preface() -> VoidResult
PING frame (RFC 7540 Section 6.7)
RST_STREAM frame (RFC 7540 Section 6.4)
SETTINGS frame (RFC 7540 Section 6.5)
WINDOW_UPDATE frame (RFC 7540 Section 6.9)
static auto create_span(std::string_view name) -> span
Create a new root span with a new trace context.
struct ssl_ctx_st SSL_CTX
constexpr int invalid_argument
constexpr int internal_error
constexpr int already_exists
constexpr int send_failed
constexpr int connection_closed
constexpr int connection_failed
@ cancel
Stream cancelled.
@ no_error
Graceful shutdown.
setting_identifier
SETTINGS frame parameter identifiers (RFC 7540 Section 6.5.2)
@ max_frame_size
SETTINGS_MAX_FRAME_SIZE.
@ max_header_list_size
SETTINGS_MAX_HEADER_LIST_SIZE.
@ max_concurrent_streams
SETTINGS_MAX_CONCURRENT_STREAMS.
@ enable_push
SETTINGS_ENABLE_PUSH.
@ header_table_size
SETTINGS_HEADER_TABLE_SIZE.
@ initial_window_size
SETTINGS_INITIAL_WINDOW_SIZE.
@ closed
Stream fully closed.
@ open
Stream open and active.
@ half_closed_local
Local end closed, remote can send.
@ idle
Stream not yet opened.
@ settings
SETTINGS frame.
auto is_tracing_enabled() -> bool
Check if tracing is enabled.
@ ok
Operation completed successfully.
std::string get_error_details(const simple_error &err)
VoidResult error_void(int code, const std::string &message, const std::string &source="network_system", const std::string &details="")
RAII span implementation for distributed tracing.
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.
int status_code
HTTP status code.
HTTP/2 connection settings.
HTTP/2 stream state and data.
std::vector< uint8_t > request_body
Request body.
std::atomic< stream_state > state
Current state.
std::promise< http2_response > promise
Response promise.
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)
uint32_t stream_id
Stream identifier.
bool is_streaming
Streaming support.
Distributed tracing context for OpenTelemetry-compatible tracing.
Configuration structures for OpenTelemetry tracing.