NeoGraph 0.16.0
A C++20 Graph Agent Engine Library — LangGraph for C++
Loading...
Searching...
No Matches
http_errors.h
Go to the documentation of this file.
1
21#pragma once
22
23#include <neograph/build_config.h>
24
25#include <asio/error.hpp>
26#include <asio/ssl/error.hpp>
27
28#include <openssl/err.h>
29#include <openssl/ssl.h>
30
31#include <cerrno>
32#include <exception>
33#include <stdexcept>
34#include <string>
35#include <system_error>
36
37namespace neograph::async {
38
39enum class HttpErrorKind {
40 // ── Retryable ─────────────────────────────────────────────────
46 PeerReset,
52
53 // ── Not retryable ─────────────────────────────────────────────
59
60 // ── Catch-all ─────────────────────────────────────────────────
61 Unknown,
64};
65
68constexpr bool is_retryable(HttpErrorKind k) noexcept {
69 switch (k) {
70 case HttpErrorKind::ConnectRefused:
71 case HttpErrorKind::ConnectTimeout:
72 case HttpErrorKind::DnsTemporary:
73 case HttpErrorKind::ReadTimeout:
74 case HttpErrorKind::WriteTimeout:
75 case HttpErrorKind::PeerReset:
76 case HttpErrorKind::PeerEofEarly:
77 case HttpErrorKind::TlsHandshakeReset:
78 case HttpErrorKind::ServerError:
79 case HttpErrorKind::TooManyRequests:
80 return true;
81 case HttpErrorKind::DnsPermanent:
82 case HttpErrorKind::TlsVerifyFailed:
83 case HttpErrorKind::ProtocolError:
84 case HttpErrorKind::RequestTooLarge:
85 case HttpErrorKind::PayloadInvalid:
86 case HttpErrorKind::Unknown:
87 return false;
88 }
89 return false;
90}
91
95inline HttpErrorKind classify_asio_error(const asio::error_code& ec) noexcept {
96 if (!ec) return HttpErrorKind::Unknown;
97
98 const auto& cat = ec.category();
99
100 // asio::error::get_ssl_category() — OpenSSL error_codes.
101 // Short-read (peer closed the TLS stream without close_notify)
102 // is shaped differently in OpenSSL 1.x vs 3.x; asio normalizes
103 // it as asio::ssl::error::stream_truncated, so compare against
104 // the enum instead of a raw SSL_R_* reason.
105 if (ec == asio::ssl::error::stream_truncated) {
106 return HttpErrorKind::PeerEofEarly;
107 }
108 if (cat == asio::error::get_ssl_category()) {
109 const int reason = ERR_GET_REASON(ec.value());
110 if (reason == SSL_R_CERTIFICATE_VERIFY_FAILED) {
111 return HttpErrorKind::TlsVerifyFailed;
112 }
113 return HttpErrorKind::TlsHandshakeReset;
114 }
115
116 // Match known asio enumerators directly. Doing this BEFORE any
117 // category-based dispatch is important on Windows, where
118 // asio::error::get_netdb_category() and get_misc_category() both
119 // alias to system_category() — so a system errno like
120 // WSAECONNREFUSED would hit the netdb branch first and fall out
121 // as DnsPermanent. The ec==enum comparison matches by numeric
122 // value within the enum's native category and is stable across
123 // POSIX (errno) and Windows (WSA*).
124 if (ec == asio::error::connection_refused) return HttpErrorKind::ConnectRefused;
125 if (ec == asio::error::timed_out) return HttpErrorKind::ConnectTimeout;
126 if (ec == asio::error::connection_reset) return HttpErrorKind::PeerReset;
127 if (ec == asio::error::broken_pipe) return HttpErrorKind::PeerReset;
128 if (ec == asio::error::not_connected) return HttpErrorKind::PeerReset;
129 if (ec == asio::error::eof) return HttpErrorKind::PeerEofEarly;
130 if (ec == asio::error::host_not_found) return HttpErrorKind::DnsPermanent;
131 if (ec == asio::error::host_not_found_try_again)
132 return HttpErrorKind::DnsTemporary;
133
134 // Linux-only: netdb and misc categories are distinct from the
135 // system category, so unknown values there are still best
136 // classified as DNS / misc rather than fully Unknown. On Windows
137 // these checks are no-ops because the specific-enum block above
138 // already handled the real cases and the remaining system errors
139 // deserve Unknown (retry = false) rather than being misrouted.
140#if !defined(ASIO_WINDOWS) && !defined(_WIN32)
141 if (cat == asio::error::get_netdb_category()) {
142 return HttpErrorKind::DnsPermanent;
143 }
144 if (cat == asio::error::get_misc_category()) {
145 return HttpErrorKind::Unknown;
146 }
147#else
148 (void)cat;
149#endif
150 return HttpErrorKind::Unknown;
151}
152
156constexpr HttpErrorKind classify_http_status(int status) noexcept {
157 if (status >= 500 && status < 600) return HttpErrorKind::ServerError;
158 if (status == 429) return HttpErrorKind::TooManyRequests;
159 if (status == 413) return HttpErrorKind::RequestTooLarge;
160 if (status >= 400 && status < 500) return HttpErrorKind::PayloadInvalid;
161 return HttpErrorKind::Unknown;
162}
163
167struct HttpError : std::runtime_error {
168 HttpErrorKind kind = HttpErrorKind::Unknown;
169 int http_status = 0;
170 std::string retry_after;
171
172 HttpError(HttpErrorKind k, std::string msg)
173 : std::runtime_error(std::move(msg)), kind(k) {}
174};
175
176} // namespace neograph::async
constexpr bool is_retryable(HttpErrorKind k) noexcept
True if transparent retry on a different connection may succeed.
Definition http_errors.h:68
constexpr HttpErrorKind classify_http_status(int status) noexcept
Classify an HTTP status code on its own.
@ TlsVerifyFailed
Cert chain / hostname mismatch. Never retry.
@ WriteTimeout
send buffer full too long (1.5 territory).
@ DnsTemporary
EAI_AGAIN equivalent — try again later.
@ ServerError
HTTP 500-599 from classify_http_status.
@ TlsHandshakeReset
Any SSL error except verify failure — overloaded server or flaky link, worth retry.
@ ConnectRefused
TCP RST on connect — server down / booting.
@ PeerEofEarly
EOF before the response was complete.
@ TooManyRequests
HTTP 429 — caller should honor Retry-After.
@ ProtocolError
Malformed HTTP, unsupported encoding.
@ PeerReset
ECONNRESET / EPIPE mid-exchange.
@ PayloadInvalid
HTTP 4xx other than 429 / 413.
@ ConnectTimeout
connect() didn't ACK within budget.
@ ReadTimeout
no bytes within deadline (1.5 territory).
HttpErrorKind classify_asio_error(const asio::error_code &ec) noexcept
Classify an asio error_code — the kind returned inside asio::system_error::code() from any connect/re...
Definition http_errors.h:95
Structured exception.
std::string retry_after
Verbatim Retry-After header.
int http_status
When kind is 4xx/5xx-derived.