NeoGraph 0.10.0
A C++17 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 <asio/error.hpp>
24#include <asio/ssl/error.hpp>
25
26#include <openssl/err.h>
27#include <openssl/ssl.h>
28
29#include <cerrno>
30#include <exception>
31#include <stdexcept>
32#include <string>
33#include <system_error>
34
35namespace neograph::async {
36
37enum class HttpErrorKind {
38 // ── Retryable ─────────────────────────────────────────────────
44 PeerReset,
50
51 // ── Not retryable ─────────────────────────────────────────────
57
58 // ── Catch-all ─────────────────────────────────────────────────
59 Unknown,
62};
63
66constexpr bool is_retryable(HttpErrorKind k) noexcept {
67 switch (k) {
68 case HttpErrorKind::ConnectRefused:
69 case HttpErrorKind::ConnectTimeout:
70 case HttpErrorKind::DnsTemporary:
71 case HttpErrorKind::ReadTimeout:
72 case HttpErrorKind::WriteTimeout:
73 case HttpErrorKind::PeerReset:
74 case HttpErrorKind::PeerEofEarly:
75 case HttpErrorKind::TlsHandshakeReset:
76 case HttpErrorKind::ServerError:
77 case HttpErrorKind::TooManyRequests:
78 return true;
79 case HttpErrorKind::DnsPermanent:
80 case HttpErrorKind::TlsVerifyFailed:
81 case HttpErrorKind::ProtocolError:
82 case HttpErrorKind::RequestTooLarge:
83 case HttpErrorKind::PayloadInvalid:
84 case HttpErrorKind::Unknown:
85 return false;
86 }
87 return false;
88}
89
93inline HttpErrorKind classify_asio_error(const asio::error_code& ec) noexcept {
94 if (!ec) return HttpErrorKind::Unknown;
95
96 const auto& cat = ec.category();
97
98 // asio::error::get_ssl_category() — OpenSSL error_codes.
99 // Short-read (peer closed the TLS stream without close_notify)
100 // is shaped differently in OpenSSL 1.x vs 3.x; asio normalizes
101 // it as asio::ssl::error::stream_truncated, so compare against
102 // the enum instead of a raw SSL_R_* reason.
103 if (ec == asio::ssl::error::stream_truncated) {
104 return HttpErrorKind::PeerEofEarly;
105 }
106 if (cat == asio::error::get_ssl_category()) {
107 const int reason = ERR_GET_REASON(ec.value());
108 if (reason == SSL_R_CERTIFICATE_VERIFY_FAILED) {
109 return HttpErrorKind::TlsVerifyFailed;
110 }
111 return HttpErrorKind::TlsHandshakeReset;
112 }
113
114 // Match known asio enumerators directly. Doing this BEFORE any
115 // category-based dispatch is important on Windows, where
116 // asio::error::get_netdb_category() and get_misc_category() both
117 // alias to system_category() — so a system errno like
118 // WSAECONNREFUSED would hit the netdb branch first and fall out
119 // as DnsPermanent. The ec==enum comparison matches by numeric
120 // value within the enum's native category and is stable across
121 // POSIX (errno) and Windows (WSA*).
122 if (ec == asio::error::connection_refused) return HttpErrorKind::ConnectRefused;
123 if (ec == asio::error::timed_out) return HttpErrorKind::ConnectTimeout;
124 if (ec == asio::error::connection_reset) return HttpErrorKind::PeerReset;
125 if (ec == asio::error::broken_pipe) return HttpErrorKind::PeerReset;
126 if (ec == asio::error::not_connected) return HttpErrorKind::PeerReset;
127 if (ec == asio::error::eof) return HttpErrorKind::PeerEofEarly;
128 if (ec == asio::error::host_not_found) return HttpErrorKind::DnsPermanent;
129 if (ec == asio::error::host_not_found_try_again)
130 return HttpErrorKind::DnsTemporary;
131
132 // Linux-only: netdb and misc categories are distinct from the
133 // system category, so unknown values there are still best
134 // classified as DNS / misc rather than fully Unknown. On Windows
135 // these checks are no-ops because the specific-enum block above
136 // already handled the real cases and the remaining system errors
137 // deserve Unknown (retry = false) rather than being misrouted.
138#if !defined(ASIO_WINDOWS) && !defined(_WIN32)
139 if (cat == asio::error::get_netdb_category()) {
140 return HttpErrorKind::DnsPermanent;
141 }
142 if (cat == asio::error::get_misc_category()) {
143 return HttpErrorKind::Unknown;
144 }
145#else
146 (void)cat;
147#endif
148 return HttpErrorKind::Unknown;
149}
150
154constexpr HttpErrorKind classify_http_status(int status) noexcept {
155 if (status >= 500 && status < 600) return HttpErrorKind::ServerError;
156 if (status == 429) return HttpErrorKind::TooManyRequests;
157 if (status == 413) return HttpErrorKind::RequestTooLarge;
158 if (status >= 400 && status < 500) return HttpErrorKind::PayloadInvalid;
159 return HttpErrorKind::Unknown;
160}
161
165struct HttpError : std::runtime_error {
166 HttpErrorKind kind = HttpErrorKind::Unknown;
167 int http_status = 0;
168 std::string retry_after;
169
170 HttpError(HttpErrorKind k, std::string msg)
171 : std::runtime_error(std::move(msg)), kind(k) {}
172};
173
174} // namespace neograph::async
constexpr bool is_retryable(HttpErrorKind k) noexcept
True if transparent retry on a different connection may succeed.
Definition http_errors.h:66
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:93
Structured exception.
std::string retry_after
Verbatim Retry-After header.
int http_status
When kind is 4xx/5xx-derived.