Skip to content

Commit 1c378d4

Browse files
committed
feat: add is_alive/1 dispatch and refused_stream retry
- Dispatch is_alive/1 to H1/H2 implementations - Classify {rst_stream, refused_stream} as retriable per RFC 9113 §8.7 - Add new error types for incomplete body and chunk overflow - Tests for retriable error classification
1 parent 6f4e9f2 commit 1c378d4

1 file changed

Lines changed: 40 additions & 1 deletion

File tree

src/gen_http.erl

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
connect/4,
2020
close/1,
2121
is_open/1,
22+
is_alive/1,
2223
get_socket/1,
2324
set_mode/2,
2425
controlling_process/2,
@@ -82,6 +83,10 @@
8283
invalid_status_line
8384
| invalid_header
8485
| invalid_chunk_size
86+
| chunk_size_overflow
87+
| too_many_headers
88+
| conflicting_content_length
89+
| invalid_trailer
8590
| invalid_content_length
8691
| {unknown_transfer_encoding, binary()}
8792
| {parse_error, term()}.
@@ -111,7 +116,9 @@
111116
connection_closed
112117
| pipeline_full
113118
| {invalid_request_ref, reference()}
114-
| unexpected_close.
119+
| unexpected_close
120+
| {incomplete_body, non_neg_integer(), non_neg_integer()}
121+
| incomplete_chunked_body.
115122

116123
%% @doc Structured error reason.
117124
%%
@@ -377,6 +384,19 @@ is_open(Conn) when is_tuple(Conn) ->
377384
gen_http_h2_conn -> gen_http_h2:is_open(Conn)
378385
end.
379386

387+
%% @doc Check if the underlying socket is still alive.
388+
%%
389+
%% Does a non-blocking recv to detect if the server has closed the connection
390+
%% while it was idle. Useful for connection pool implementations.
391+
%%
392+
%% Works with both HTTP/1.1 and HTTP/2 connections.
393+
-spec is_alive(conn()) -> boolean().
394+
is_alive(Conn) when is_tuple(Conn) ->
395+
case element(1, Conn) of
396+
gen_http_h1_conn -> gen_http_h1:is_alive(Conn);
397+
gen_http_h2_conn -> gen_http_h2:is_alive(Conn)
398+
end.
399+
380400
%% @doc Get the underlying socket.
381401
%%
382402
%% Works with both HTTP/1.1 and HTTP/2 connections.
@@ -474,6 +494,10 @@ delete_private(Conn, Key) when is_tuple(Conn) ->
474494
is_retriable_error({transport_error, _}) ->
475495
%% Transport errors are generally retriable
476496
true;
497+
is_retriable_error({protocol_error, {rst_stream, refused_stream}}) ->
498+
%% RFC 9113 Section 8.7: server rejected the stream without processing it.
499+
%% Safe to retry on a new connection.
500+
true;
477501
is_retriable_error({protocol_error, _}) ->
478502
%% Protocol errors indicate incompatibility or bugs
479503
false;
@@ -617,11 +641,26 @@ is_not_retriable_protocol_errors_test() ->
617641
?assertNot(is_retriable_error({protocol_error, {frame_size_error, 100}})),
618642
?assertNot(is_retriable_error({protocol_error, max_concurrent_streams_exceeded})).
619643

644+
refused_stream_is_retriable_test() ->
645+
%% RFC 9113 Section 8.7: refused_stream means the server rejected
646+
%% the stream without processing it, safe to retry
647+
?assert(is_retriable_error({protocol_error, {rst_stream, refused_stream}})).
648+
649+
other_rst_stream_not_retriable_test() ->
650+
%% Other RST_STREAM error codes are not retriable
651+
?assertNot(is_retriable_error({protocol_error, {rst_stream, stream_closed}})),
652+
?assertNot(is_retriable_error({protocol_error, {rst_stream, cancel}})).
653+
620654
is_retriable_some_application_errors_test() ->
621655
%% Some application errors are retriable
622656
?assert(is_retriable_error({application_error, connection_closed})),
623657
?assert(is_retriable_error({application_error, unexpected_close})).
624658

659+
incomplete_body_errors_not_retriable_test() ->
660+
%% Incomplete body errors are application-level, not retriable
661+
?assertNot(is_retriable_error({application_error, {incomplete_body, 500, 1000}})),
662+
?assertNot(is_retriable_error({application_error, incomplete_chunked_body})).
663+
625664
is_not_retriable_some_application_errors_test() ->
626665
%% Some application errors are not retriable
627666
?assertNot(is_retriable_error({application_error, pipeline_full})),

0 commit comments

Comments
 (0)