Add gen_http to your rebar.config dependencies:
{deps, [
{gen_http, {git, "https://github.com/codeadict/gen_http.git", {branch, "main"}}}
]}.Then fetch and compile:
rebar3 compileConnect to a server and send a GET request:
{ok, Conn} = gen_http:connect(http, "httpbin.org", 80),
{ok, Conn2, Ref} = gen_http:request(Conn, <<"GET">>, <<"/get">>, [], <<>>).Ref is a reference that tags all response messages for this request.
You need it to match responses when you have multiple requests in flight.
By default, connections run in active mode. The socket delivers data as
Erlang messages to your process. You hand each message to gen_http:stream/2,
which parses it into structured response events:
receive
Msg ->
{ok, Conn3, Responses} = gen_http:stream(Conn2, Msg)
endResponses is a list of tuples. A complete response looks like:
[
{status, Ref, 200},
{headers, Ref, [{<<"content-type">>, <<"application/json">>}, ...]},
{data, Ref, <<"response body">>},
{done, Ref}
]The response might arrive across several messages, so keep calling
stream/2 in a loop until you see {done, Ref}.
Putting it together with a collection loop:
fetch(Scheme, Host, Port, Path) ->
{ok, Conn} = gen_http:connect(Scheme, Host, Port),
{ok, Conn2, Ref} = gen_http:request(Conn, <<"GET">>, Path, [], <<>>),
collect(Conn2, Ref, #{}).
collect(Conn, Ref, Acc) ->
receive
Msg ->
case gen_http:stream(Conn, Msg) of
{ok, Conn2, Responses} ->
case process_responses(Responses, Ref, Acc) of
{done, Result} ->
gen_http:close(Conn2),
Result;
{continue, Acc2} ->
collect(Conn2, Ref, Acc2)
end;
{error, _Conn2, Reason, _Partial} ->
{error, Reason}
end
after 10000 ->
gen_http:close(Conn),
{error, timeout}
end.
process_responses([], _Ref, Acc) ->
{continue, Acc};
process_responses([{status, Ref, Status} | Rest], Ref, Acc) ->
process_responses(Rest, Ref, Acc#{status => Status});
process_responses([{headers, Ref, Headers} | Rest], Ref, Acc) ->
process_responses(Rest, Ref, Acc#{headers => Headers});
process_responses([{data, Ref, Chunk} | Rest], Ref, Acc) ->
Body = maps:get(body, Acc, <<>>),
process_responses(Rest, Ref, Acc#{body => <<Body/binary, Chunk/binary>>});
process_responses([{done, Ref} | _Rest], Ref, Acc) ->
{done, Acc}.HTTPS connections negotiate the protocol automatically via ALPN. If the server supports HTTP/2, you get HTTP/2. The API stays the same:
{ok, Conn} = gen_http:connect(https, "www.google.com", 443),
{ok, Conn2, Ref} = gen_http:request(Conn, <<"GET">>, <<"/">>, [], <<>>).To force a specific protocol:
%% HTTP/2 only
{ok, Conn} = gen_http:connect(https, "example.com", 443, #{protocols => [http2]}).
%% HTTP/1.1 only
{ok, Conn} = gen_http:connect(https, "example.com", 443, #{protocols => [http1]}).{ok, Conn} = gen_http:connect(https, "httpbin.org", 443),
Headers = [{<<"content-type">>, <<"application/json">>}],
Body = <<"{\"key\": \"value\"}">>,
{ok, Conn2, Ref} = gen_http:request(Conn, <<"POST">>, <<"/post">>, Headers, Body).Connections stay open. Send multiple requests on the same one:
{ok, Conn} = gen_http:connect(http, "httpbin.org", 80),
{ok, Conn2, Ref1} = gen_http:request(Conn, <<"GET">>, <<"/get">>, [], <<>>),
%% ... collect response for Ref1 ...
{ok, Conn3, Ref2} = gen_http:request(Conn2, <<"GET">>, <<"/headers">>, [], <<>>),
%% ... collect response for Ref2 ...
{ok, _} = gen_http:close(Conn3).If you prefer blocking reads over message-based I/O:
{ok, Conn} = gen_http:connect(http, "httpbin.org", 80, #{mode => passive}),
{ok, Conn2, Ref} = gen_http:request(Conn, <<"GET">>, <<"/get">>, [], <<>>),
{ok, Conn3, Responses} = gen_http:recv(Conn2, 0, 5000).See the Active and Passive Modes guide for when to pick which.
Errors come back as tagged tuples in three categories:
case gen_http:request(Conn, <<"GET">>, <<"/">>, [], <<>>) of
{ok, Conn2, Ref} ->
handle_success(Conn2, Ref);
{error, Conn2, Reason} ->
case gen_http:is_retriable_error(Reason) of
true -> retry_with_new_connection();
false -> {error, Reason}
end
endSee the Error Handling guide for the full breakdown.
Always close connections when done:
{ok, _Conn} = gen_http:close(Conn).- Architecture -- how
gen_httpworks under the hood - Active and Passive Modes -- choosing the right I/O mode
- Error Handling -- dealing with failures and retries
- SSL Certificates -- TLS configuration and certificate verification