Current section

Files

Jump to
hackney src hackney_connect hackney_connect.erl
Raw

src/hackney_connect/hackney_connect.erl

%%% -*- erlang -*-
%%%
%%% This file is part of hackney released under the Apache 2 license.
%%% See the NOTICE for more information.
%%%
%%% Copyright (c) 2012-2014 Benoît Chesneau <benoitc@e-engura.org>
%%%
-module(hackney_connect).
-export([connect/3, connect/4, connect/5,
create_connection/4, create_connection/5,
maybe_connect/1,
reconnect/4,
set_sockopts/2,
close/1,
is_pool/1]).
-include("hackney.hrl").
connect(Transport, Host, Port) ->
connect(Transport, Host, Port, []).
connect(Transport, Host, Port, Options) ->
connect(Transport, Host, Port, Options, false).
connect(Transport, Host, Port, Options, Dynamic) when is_binary(Host) ->
connect(Transport, binary_to_list(Host), Port, Options, Dynamic);
connect(Transport, Host, Port, Options, Dynamic) ->
case create_connection(Transport, hackney_idna:to_ascii(Host), Port,
Options, Dynamic) of
{ok, #client{request_ref=Ref}} ->
{ok, Ref};
Error ->
Error
end.
%% @doc create a connection and return a client state
create_connection(Transport, Host, Port, Options) ->
create_connection(Transport, Host, Port, Options, true).
create_connection(Transport, Host, Port, Options, Dynamic)
when is_list(Options) ->
%% default timeout
Timeout = proplists:get_value(recv_timeout, Options, infinity),
%% initial state
InitialState = #client{dynamic=Dynamic,
recv_timeout=Timeout,
options=Options},
%% if we use a pool then checkout the connection from the pool, else
%% connect the socket to the remote
case is_pool(InitialState) of
false ->
%% the client won't use any pool
do_connect(Host, Port, Transport, InitialState);
true ->
socket_from_pool(Host, Port, Transport, InitialState)
end.
%% @doc connect a socket and create a client state.
%%
maybe_connect(#client{state=closed, redirect=nil}=Client) ->
%% the socket has been closed, reconnect it.
#client{transport=Transport,
host=Host,
port=Port} = Client,
reconnect(Host, Port, Transport, Client);
maybe_connect(#client{state=closed, redirect=Redirect}=Client) ->
%% connection closed after a redirection, reinit the options and
%% reconnect it.
{Transport, Host, Port, Options} = Redirect,
Client1 = Client#client{options=Options,
redirect=nil},
reconnect(Host, Port, Transport, Client1);
maybe_connect(#client{redirect=nil}=Client) ->
{ok, Client};
maybe_connect(#client{redirect=Redirect}=Client) ->
#client{socket=Socket, socket_ref=Ref, pool_handler=Handler}=Client,
%% the connection was redirected. If we are using a pool, checkin
%% the socket and create the newone, else close the current socket
%% and create a new one.
case is_pool(Client) of
false ->
close(Client);
true ->
Handler:checkin(Ref, Socket)
end,
%% reinit the options and reconnect the client
{Transport, Host, Port, Options} = Redirect,
Client1 = Client#client{options=Options,
redirect=nil},
reconnect(Host, Port, Transport, Client1).
%% @doc add set sockets options in the client
set_sockopts(#client{transport=Transport, socket=Skt}, Options) ->
Transport:setopts(Skt, Options).
%% @doc close the client
%%
%%
close(#client{socket=nil}=Client) ->
Client#client{state = closed};
close(#client{transport=Transport, socket=Skt}=Client) ->
Transport:close(Skt),
Client#client{state = closed, socket=nil};
close(Ref) when is_reference(Ref) ->
hackney_manager:close_request(Ref).
%% @doc get current pool pid or name used by a client if needed
is_pool(#client{options=Opts}) ->
UseDefaultPool = use_default_pool(),
case proplists:get_value(pool, Opts) of
false ->
false;
undefined when UseDefaultPool =:= true ->
true;
undefined ->
false;
_ ->
true
end.
reconnect(Host, Port, Transport, State) ->
%% if we use a pool then checkout the connection from the pool, else
%% connect the socket to the remote
case is_pool(State) of
false ->
%% the client won't use any pool
do_connect(Host, Port, Transport, State);
true ->
socket_from_pool(Host, Port, Transport, State)
end.
%%
%% internal functions
%%
socket_from_pool(Host, Port, Transport, #client{options=Opts,
request_ref=ReqRef0}=Client) ->
PoolHandler = hackney_app:get_app_env(pool_handler, hackney_pool),
case PoolHandler:checkout(Host, Port, Transport, Client) of
{ok, Ref, Skt} ->
FollowRedirect = proplists:get_value(follow_redirect,
Opts, false),
MaxRedirect = proplists:get_value(max_redirect, Opts, 5),
Async = proplists:get_value(async, Opts, false),
StreamTo = proplists:get_value(stream_to, Opts, false),
Client1 = Client#client{transport=Transport,
host=Host,
port=Port,
socket=Skt,
socket_ref=Ref,
pool_handler=PoolHandler,
state = connected,
follow_redirect=FollowRedirect,
max_redirect=MaxRedirect,
async=Async,
stream_to=StreamTo,
buffer = <<>>},
FinalClient = case is_reference(ReqRef0) of
true ->
ok = hackney_manager:take_control(ReqRef0, Client1),
Client1;
false ->
RequestRef = hackney_manager:new_request(Client1),
Client1#client{request_ref=RequestRef}
end,
{ok, FinalClient};
{error, no_socket, Ref} ->
do_connect(Host, Port, Transport, Client#client{socket_ref=Ref});
Error ->
Error
end.
do_connect(Host, Port, Transport, #client{options=Opts,
request_ref=Ref0}=Client) ->
ConnectOpts0 = proplists:get_value(connect_options, Opts, []),
ConnectTimeout = proplists:get_value(connect_timeout, Opts, 8000),
%% handle ipv6
ConnectOpts1 = case hackney_util:is_ipv6(Host) of
true ->
[inet6 | ConnectOpts0];
false ->
ConnectOpts0
end,
ConnectOpts = case {Transport, proplists:get_value(ssl_options, Opts)} of
{hackney_ssl_transport, undefined} ->
case proplists:get_value(insecure, Opts) of
true ->
ConnectOpts1 ++ [{verify, verify_none},
{reuse_sessions, true}];
_ ->
ConnectOpts1
end;
{hackney_ssl_transport, SslOpts} ->
ConnectOpts1 ++ SslOpts;
{_, _} ->
ConnectOpts1
end,
case Transport:connect(Host, Port, ConnectOpts, ConnectTimeout) of
{ok, Skt} ->
FollowRedirect = proplists:get_value(follow_redirect,
Opts, false),
MaxRedirect = proplists:get_value(max_redirect, Opts, 5),
ForceRedirect = proplists:get_value(force_redirect, Opts,
false),
Async = proplists:get_value(async, Opts, false),
StreamTo = proplists:get_value(stream_to, Opts, false),
Client1 = Client#client{transport=Transport,
host=Host,
port=Port,
socket=Skt,
state = connected,
follow_redirect=FollowRedirect,
max_redirect=MaxRedirect,
force_redirect=ForceRedirect,
async=Async,
stream_to=StreamTo,
buffer = <<>>},
FinalClient = case is_reference(Ref0) of
true ->
ok = hackney_manager:take_control(Ref0, Client1),
Client1;
false ->
Ref = hackney_manager:new_request(Client1),
Client1#client{request_ref=Ref}
end,
{ok, FinalClient};
{error, timeout} ->
{error, connect_timeout};
Error ->
Error
end.
use_default_pool() ->
case application:get_env(hackney, use_default_pool) of
{ok, Val} ->
Val;
_ ->
true
end.