Packages
hackney
0.14.1
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_connect/hackney_pool.erl
%%% -*- erlang -*-
%%%
%%% This file is part of hackney released under the Apache 2 license.
%%% See the NOTICE for more information.
%%%
%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
%%% Copyright (c) 2012-2014, Benoît Chesneau <benoitc@e-engura.org>
%% @doc pool of sockets connections
%%
-module(hackney_pool).
-behaviour(gen_server).
-behaviour(hackney_pool_handler).
%% PUBLIC API
-export([start/0,
checkout/4,
checkin/2]).
-export([start_pool/2,
stop_pool/1]).
-export([count/1, count/2,
max_connections/1,
set_max_connections/2,
timeout/1,
set_timeout/2,
child_spec/2]).
-export([start_link/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
code_change/3, terminate/2]).
-include("hackney.hrl").
-record(state, {
max_connections,
timeout,
connections = dict:new(),
sockets = dict:new()}).
start() ->
%% NB this is first called from hackney_sup:start_link
%% BEFORE the hackney_pool ETS table exists
ok.
%% @doc fetch a socket from the pool
checkout(Host0, Port, Transport, #client{options=Opts}) ->
Host = string:to_lower(Host0),
Pid = self(),
Name = proplists:get_value(pool, Opts, default),
Pool = find_pool(Name, Opts),
case gen_server:call(Pool, {checkout, {Host, Port, Transport},
Pid}) of
{ok, Socket, Owner} ->
CheckinReference = {Host, Port, Transport},
{ok, {Name, CheckinReference, Owner, Transport}, Socket};
{error, no_socket, Owner} ->
CheckinReference = {Host, Port, Transport},
{error, no_socket, {Name, CheckinReference, Owner,
Transport}};
{error, Reason} ->
{error, Reason}
end.
%% @doc release a socket in the pool
checkin({_Name, Key, Owner, Transport}, Socket) ->
case Transport:controlling_process(Socket, Owner) of
ok ->
gen_server:call(Owner, {checkin, Key, Socket, Transport});
_Error ->
Transport:close(Socket)
end.
%% @doc start a pool
start_pool(Name, Options) ->
case find_pool(Name, Options) of
Pid when is_pid(Pid) ->
ok;
Error ->
Error
end.
%% @doc stop a pool
stop_pool(Name) ->
case find_pool(Name) of
undefined ->
ok;
_Pid ->
case supervisor:terminate_child(hackney_sup, Name) of
ok ->
supervisor:delete_child(hackney_sup, Name),
ets:delete(hackney_pool, Name),
ok;
Error ->
Error
end
end.
%%
%% util functions for this pool
%%
%% @doc return a child spec suitable for embeding your pool in the
%% supervisor
child_spec(Name, Options0) ->
Options = [{name, Name} | Options0],
{Name, {hackney_pool, start_link, [Name, Options]},
permanent, 10000, worker, [Name]}.
%% @doc get the number of connections in the pool
count(Name) ->
gen_server:call(find_pool(Name), count).
%% @doc get the number of connections in the pool for `{Host0, Port, Transport}'
count(Name, {Host0, Port, Transport}) ->
Host = string:to_lower(Host0),
gen_server:call(find_pool(Name), {count, {Host, Port, Transport}}).
%% @doc get max pool size
max_connections(Name) ->
gen_server:call(find_pool(Name), max_connections).
%% @doc change the pool size
set_max_connections(Name, NewSize) ->
gen_server:cast(find_pool(Name), {set_maxconn, NewSize}).
%% @doc get timeout
timeout(Name) ->
gen_server:call(find_pool(Name), timeout).
%% @doc change the connection timeout
%%
set_timeout(Name, NewTimeout) ->
gen_server:cast(find_pool(Name), {set_timeout, NewTimeout}).
%% @private
%%
%%
do_start_pool(Name, Options) ->
Spec = child_spec(Name, Options),
case supervisor:start_child(hackney_sup, Spec) of
{ok, Pid} ->
Pid;
{error, {already_started, _}} ->
find_pool(Name, Options)
end.
find_pool(Name) ->
case ets:lookup(?MODULE, Name) of
[] ->
undefined;
[{_, Pid}] ->
Pid
end.
find_pool(Name, Options) ->
case ets:lookup(?MODULE, Name) of
[] ->
do_start_pool(Name, Options);
[{_, Pid}] ->
Pid
end.
start_link(Name, Options0) ->
Options = hackney_util:maybe_apply_defaults([max_connections, timeout],
Options0),
gen_server:start_link(?MODULE, [Name, Options], []).
init([Name, Options]) ->
MaxConn = case proplists:get_value(pool_size, Options) of
undefined ->
proplists:get_value(max_connections, Options);
Size ->
Size
end,
Timeout = proplists:get_value(timeout, Options),
%% register the module
ets:insert(?MODULE, {Name, self()}),
{ok, #state{max_connections=MaxConn, timeout=Timeout}}.
handle_call(timeout, _From, #state{timeout=Timeout}=State) ->
{reply, Timeout, State};
handle_call(max_connections, _From, #state{max_connections=MaxConn}=State) ->
{reply, MaxConn, State};
handle_call({checkout, Key, Pid}, _From, State) ->
{Reply, NewState} = find_connection(Key, Pid, State),
{reply, Reply, NewState};
handle_call({checkin, Key, Socket, Transport}, From,
#state{sockets=Sockets, max_connections=MaxConn}=State) ->
gen_server:reply(From, ok),
PoolSize = dict:size(Sockets),
NewState = if PoolSize =< MaxConn ->
store_connection(Key, Socket, State);
true ->
%% don't store more than MaxConn
catch Transport:close(Socket),
State
end,
{noreply, NewState};
handle_call(count, _From, #state{sockets=Sockets}=State) ->
{reply, dict:size(Sockets), State};
handle_call({count, Key}, _From, #state{connections=Conns}=State) ->
Size = case dict:find(Key, Conns) of
{ok, Sockets} ->
length(Sockets);
error ->
0
end,
{reply, Size, State}.
handle_cast({set_maxconn, MaxConn}, State) ->
{noreply, State#state{max_connections=MaxConn}};
handle_cast({set_timeout, NewTimeout}, State) ->
{noreply, State#state{timeout=NewTimeout}};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({timeout, Socket}, State) ->
{noreply, remove_socket(Socket, State)};
handle_info({tcp_closed, Socket}, State) ->
{noreply, remove_socket(Socket, State)};
handle_info({ssl_closed, Socket}, State) ->
{noreply, remove_socket(Socket, State)};
handle_info({tcp_error, Socket, _}, State) ->
{noreply, remove_socket(Socket, State)};
handle_info({ssl_error, Socket, _}, State) ->
{noreply, remove_socket(Socket, State)};
handle_info({tcp, Socket, _}, State) ->
{noreply, remove_socket(Socket, State)};
handle_info({ssl, Socket, _}, State) ->
{noreply, remove_socket(Socket, State)};
handle_info(_, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, #state{sockets=Sockets}) ->
lists:foreach(fun({Socket, {{Transport, _, _}, Timer}}) ->
cancel_timer(Socket, Timer),
Transport:close(Socket)
end, dict:to_list(Sockets)),
ok.
%% internals
find_connection({_Host, _Port, Transport}=Key, Pid,
#state{connections=Conns, sockets=Sockets}=State) ->
case dict:find(Key, Conns) of
{ok, [S | Rest]} ->
Transport:setopts(S, [{active, false}]),
case Transport:controlling_process(S, Pid) of
ok ->
{_, Timer} = dict:fetch(S, Sockets),
cancel_timer(S, Timer),
NewConns = update_connections(Rest, Key, Conns),
NewSockets = dict:erase(S, Sockets),
NewState = State#state{connections=NewConns,
sockets=NewSockets},
{{ok, S, self()}, NewState};
{error, badarg} ->
% Pid has timed out, reuse for someone else
Transport:setopts(S, [{active, once}]),
{{error, no_socket, self()}, State};
_Else ->
find_connection(Key, Pid, remove_socket(S, State))
end;
_ ->
{{error, no_socket, self()}, State}
end.
remove_socket(Socket, #state{connections=Conns, sockets=Sockets}=State) ->
case dict:find(Socket, Sockets) of
{ok, {{_Host, _Port, Transport}=Key, Timer}} ->
cancel_timer(Socket, Timer),
Transport:close(Socket),
ConnSockets = lists:delete(Socket, dict:fetch(Key, Conns)),
NewConns = update_connections(ConnSockets, Key, Conns),
NewSockets = dict:erase(Socket, Sockets),
State#state{connections=NewConns, sockets=NewSockets};
error ->
State
end.
store_connection({_Host, _Port, Transport} = Key, Socket,
#state{timeout=Timeout, connections=Conns,
sockets=Sockets}=State) ->
Timer = erlang:send_after(Timeout, self(), {timeout, Socket}),
Transport:setopts(Socket, [{active, once}]),
ConnSockets = case dict:find(Key, Conns) of
{ok, OldSockets} ->
[Socket | OldSockets];
error -> [Socket]
end,
State#state{connections = dict:store(Key, ConnSockets, Conns),
sockets = dict:store(Socket, {Key, Timer}, Sockets)}.
update_connections([], Key, Connections) ->
dict:erase(Key, Connections);
update_connections(Sockets, Key, Connections) ->
dict:store(Key, Sockets, Connections).
cancel_timer(Socket, Timer) ->
case erlang:cancel_timer(Timer) of
false ->
receive
{timeout, Socket} -> ok
after
0 -> ok
end;
_ -> ok
end.