Packages
hackney
1.22.0
4.7.2
4.7.1
4.7.0
4.6.1
4.6.0
4.5.2
4.5.1
4.5.0
4.4.5
4.4.3
4.4.2
4.4.1
4.4.0
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.3
4.0.2
4.0.1
4.0.0
3.2.1
3.2.0
3.1.2
3.1.1
3.1.0
3.0.3
3.0.2
3.0.1
3.0.0
retired
2.0.1
2.0.0
2.0.0-beta.1
1.25.0
1.24.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.2
1.18.1
1.18.0
1.17.4
1.17.3
1.17.2
1.17.1
1.17.0
1.16.0
1.15.2
1.15.1
1.15.0
1.14.3
1.14.2
1.14.0
1.13.0
1.12.1
1.12.0
1.11.0
1.10.1
1.10.0
1.9.0
1.8.6
1.8.5
1.8.4
1.8.3
1.8.2
1.8.0
1.7.1
1.7.0
1.6.6
retired
1.6.5
1.6.4
retired
1.6.3
1.6.2
1.6.1
1.6.0
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.10
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.0
1.0.6
1.0.5
1.0.2
1.0.1
0.15.2
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
Simple HTTP client with HTTP/1.1, HTTP/2, and HTTP/3 support
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
src/hackney_happy.erl
-module(hackney_happy).
-export([connect/3, connect/4]).
-include("hackney_internal.hrl").
-include_lib("kernel/include/inet.hrl").
-define(TIMEOUT, 250).
-define(CONNECT_TIMEOUT, 5000).
connect(Hostname, Port, Opts) ->
connect(Hostname, Port, Opts, ?CONNECT_TIMEOUT).
connect(Hostname, Port, Opts, Timeout) ->
do_connect(parse_address(Hostname), Port, Opts, Timeout).
do_connect(Hostname, Port, Opts, Timeout) when is_tuple(Hostname) ->
case hackney_cidr:is_ipv6(Hostname) of
true ->
?report_debug("connect using IPv6", [{hostname, Hostname}, {port, Port}]),
gen_tcp:connect(Hostname, Port, [inet6 | Opts], Timeout);
false ->
case hackney_cidr:is_ipv4(Hostname) of
true ->
?report_debug("connect using IPv4", [{hostname, Hostname}, {port, Port}]),
gen_tcp:connect(Hostname, Port, [inet | Opts], Timeout);
false ->
{error, nxdomain}
end
end;
do_connect(Hostname, Port, Opts, Timeout) ->
?report_debug("happy eyeballs, try to connect using IPv6", [{hostname, Hostname}, {port, Port}]),
Self = self(),
{Ipv6Addrs, IPv4Addrs} = getaddrs(Hostname),
{Pid6, MRef6} = spawn_monitor(fun() -> try_connect(Ipv6Addrs, Port, Opts, Self) end),
TRef = erlang:start_timer(?TIMEOUT, self(), try_ipv4),
receive
{'DOWN', MRef6, _Type, _Pid, {happy_connect, OK}} ->
_ = erlang:cancel_timer(TRef, []),
OK;
{'DOWN', MRef6, _Type, _Pid, _Info} ->
_ = erlang:cancel_timer(TRef, []),
{Pid4, MRef4} = spawn_monitor(fun() -> try_connect(IPv4Addrs, Port, Opts, Self) end),
do_connect_2(Pid4, MRef4, Timeout);
{timeout, TRef, try_ipv4} ->
PidRef4 = spawn_monitor(fun() -> try_connect(IPv4Addrs, Port, Opts, Self) end),
do_connect_1(PidRef4, {Pid6, MRef6}, Timeout)
after Timeout ->
_ = erlang:cancel_timer(TRef, []),
erlang:demonitor(MRef6, [flush]),
{error, connect_timeout}
end.
do_connect_1({Pid4, MRef4}, {Pid6, MRef6}, Timeout) ->
receive
{'DOWN', MRef4, _Type, _Pid, {happy_connect, OK}} ->
?report_trace("happy_connect ~p", [OK]),
connect_gc(Pid6, MRef6),
OK;
{'DOWN', MRef4, _Type, _Pid, _Info} ->
do_connect_2(Pid6, MRef6, Timeout);
{'DOWN', MRef6, _Type, _Pid, {happy_connect, OK}} ->
?report_trace("happy_connect ~p", [OK]),
connect_gc(Pid4, MRef4),
OK;
{'DOWN', MRef6, _Type, _Pid, _Info} ->
do_connect_2(Pid4, MRef4, Timeout)
after Timeout ->
connect_gc(Pid4, MRef4),
connect_gc(Pid6, MRef6),
{error, connect_timeout}
end.
do_connect_2(Pid, MRef, Timeout) ->
receive
{'DOWN', MRef, _Type, _Pid, {happy_connect, OK}} ->
?report_trace("happy_connect ~p", [OK]),
OK;
{'DOWN', MRef, _Type, _Pid, Info} ->
{connect_error, Info}
after Timeout ->
connect_gc(Pid, MRef),
{error, connect_timeout}
end.
connect_gc(Pid, MRef) ->
catch exit(Pid, normal),
erlang:demonitor(MRef, [flush]).
-spec parse_address(inet:ip_address() | binary() | string()) -> inet:ip_address() | string().
parse_address(IPTuple) when is_tuple(IPTuple) -> IPTuple;
parse_address(IPBin) when is_binary(IPBin) ->
parse_address(binary_to_list(IPBin));
%% IPv6 string with brackets
parse_address("[" ++ IPString) ->
parse_address(lists:sublist(IPString, length(IPString) - 1));
parse_address(IPString) ->
case inet:parse_address(IPString) of
{ok, IP} -> IP;
{error, _} -> IPString
end.
-spec getaddrs(string()) -> {[{inet:ip_address(), 'inet6' | 'inet'}], [{inet:ip_address(), 'inet6' | 'inet'}]}.
getaddrs("localhost") ->
{[{{0,0,0,0,0,0,0,1}, 'inet6'}], [{{127,0,0,1}, 'inet'}]};
getaddrs(Name) ->
IP6Addrs = [{Addr, 'inet6'} || Addr <- getbyname(Name, 'aaaa')],
IP4Addrs = [{Addr, 'inet'} || Addr <- getbyname(Name, 'a')],
{IP6Addrs, IP4Addrs}.
getbyname(Hostname, Type) ->
case (catch inet_res:getbyname(Hostname, Type)) of
{'ok', #hostent{h_addr_list=AddrList}} -> lists:usort(AddrList);
{error, _Reason} -> [];
Else ->
%% ERLANG 22 has an issue when g matching somee DNS server messages
?report_debug("DNS error", [{hostname, Hostname}
,{type, Type}
,{error, Else}]),
[]
end.
try_connect(Ipv6Addrs, Port, Opts, Self) ->
try_connect(Ipv6Addrs, Port, Opts, Self, {error, nxdomain}).
try_connect([], _Port, _Opts, _ServerPid, LastError) ->
?report_trace("happy eyeball: failed to connect", [{error, LastError}]),
exit(LastError);
try_connect([{IP, Type} | Rest], Port, Opts, ServerPid, _LastError) ->
?report_trace("try to connect", [{ip, IP}, {type, Type}]),
case gen_tcp:connect(IP, Port, [Type | Opts], ?TIMEOUT) of
{ok, Socket} = OK ->
?report_trace("success to connect", [{ip, IP}, {type, Type}]),
ok = gen_tcp:controlling_process(Socket, ServerPid),
exit({happy_connect, OK});
Error ->
try_connect(Rest, Port, Opts, ServerPid, Error)
end.