Current section

Files

Jump to
pe4kin src pe4kin_http.erl
Raw

src/pe4kin_http.erl

%% @doc abstract API for HTTP client
-module(pe4kin_http).
-export([start_pool/0, stop_pool/0]).
-export([open/0, open/3, get/1, post/3]).
-type response() :: {non_neg_integer(), [{binary(), binary()}], iodata()}.
-type path() :: iodata().
-type req_headers() :: [{binary(), iodata()}].
-type disposition() ::
{Disposition :: binary(), Params :: [{binary(), iodata()}]}.
-type multipart() ::
{file, file:name_all(), disposition(), req_headers()} |
{Name :: binary(), Payload :: binary(), disposition(), req_headers()} |
{Name :: binary(), Payload :: binary()}.
-type req_body() :: binary() |
iodata() |
{form, #{binary() => binary()}} |
{json, pe4kin:json_value()} |
{multipart, multipart()}.
open() ->
{ok, Endpoint} = pe4kin:get_env(api_server_endpoint),
{Transport, Host, Port} = parse_endpoint(Endpoint),
open(Transport, Host, Port).
open(Transport, Host, Port) ->
{ok, Pid} = gun:open(Host, Port, #{transport => Transport}),
_Protocol = gun:await_up(Pid),
{ok, Pid}.
-spec get(iodata()) -> response() | {error, any()}.
get(Path) ->
{Opts, Host} = http_req_opts(),
await(gun_pool:get(Path, #{<<"host">> => Host}, Opts)).
-spec post(path(), req_headers(), req_body()) -> response() | {error, any()}.
post(Path, Headers, Body) when is_binary(Body);
is_list(Body) ->
{Opts, Host} = http_req_opts(),
await(gun_pool:post(Path, [{<<"host">>, Host} | Headers], Body, Opts));
post(Path, Headers, {form, KV}) ->
post(Path, Headers, cow_qs:qs(maps:to_list(KV)));
post(Path, Headers, {json, Struct}) ->
post(Path, Headers, jiffy:encode(Struct));
post(Path, Headers0, {multipart, Multipart}) ->
Boundary = cow_multipart:boundary(),
{value, {_, <<"multipart/form-data">>}, Headers1} =
lists:keytake(<<"content-type">>, 1, Headers0),
Headers = [{<<"content-type">>,
[<<"multipart/form-data;boundary=">>, Boundary]}
| Headers1],
{Opts, Host} = http_req_opts(),
{async, Ref} = gun_pool:post(Path, [{<<"host">>, Host} | Headers], Opts),
multipart_stream(Ref, Boundary, Multipart),
await(Ref).
http_req_opts() ->
{ok, PoolOpts} = pe4kin:get_env(keepalive_pool),
Opts = #{reply_to => self(),
scope => ?MODULE,
checkout_retry => maps:get(checkout_retry, PoolOpts, [])},
{ok, Endpoint} = pe4kin:get_env(api_server_endpoint),
{_Transport, Host, _Port} = parse_endpoint(Endpoint),
{Opts, Host}.
await({async, Ref}) ->
await(Ref);
await(Ref) ->
case gun_pool:await(Ref, 30_000) of
{response, fin, Status, Headers} ->
{Status, Headers, []};
{response, nofin, Status, Headers} ->
{ok, Body} = gun_pool:await_body(Ref),
{Status, Headers, Body};
{error, _} = Err ->
Err
end.
multipart_stream(Ref, Boundary, Multipart) ->
ok = lists:foreach(
fun({file, Path, Disposition, Hdrs0}) ->
{ok, Bin} = file:read_file(Path),
Hdrs = [{<<"content-disposition">>, encode_disposition(Disposition)}
| Hdrs0],
Chunk = cow_multipart:part(Boundary, Hdrs),
ok = gun_pool:data(Ref, nofin, [Chunk, Bin]);
({_Name, Payload, Disposition, Hdrs0}) ->
Hdrs = [{<<"content-disposition">>, encode_disposition(Disposition)}
| Hdrs0],
Chunk = cow_multipart:part(Boundary, Hdrs),
ok = gun_pool:data(Ref, nofin, [Chunk, Payload]);
({Name, Value}) ->
Hdrs = [{<<"content-disposition">>,
encode_disposition({<<"form-data">>,
[{<<"name">>, Name}]})}],
Chunk = cow_multipart:part(Boundary, Hdrs),
ok = gun_pool:data(Ref, nofin, [Chunk, Value])
end, Multipart),
Closing = cow_multipart:close(Boundary),
ok = gun_pool:data(Ref, fin, Closing).
encode_disposition({Disposition, Params}) ->
[Disposition
| [[";", K, "=\"", V, "\""] || {K, V} <- Params]].
%% Pool
start_pool() ->
{ok, Opts} = pe4kin:get_env(keepalive_pool),
{ok, Endpoint} = pe4kin:get_env(api_server_endpoint),
{Transport, Host, Port} = parse_endpoint(Endpoint),
{ok, ManagerPid} = gun_pool:start_pool(Host, Port, #{
conn_opts => #{protocols => [http2],
transport => Transport},
scope => ?MODULE,
size => maps:get(max_count, Opts, 10)
}),
gun_pool:await_up(ManagerPid).
stop_pool() ->
{ok, Endpoint} = pe4kin:get_env(api_server_endpoint),
{_Transport, Host, Port} = parse_endpoint(Endpoint),
ok = gun_pool:stop_pool(Host, Port).
parse_endpoint(Uri) ->
Parts = case Uri of
<<"https://", Rest/binary>> ->
[tls | binary:split(Rest, <<":">>)];
<<"http://", Rest/binary>> ->
[tcp | binary:split(Rest, <<":">>)]
end,
case Parts of
[tls, Host] ->
{tls, binary_to_list(Host), 443};
[tcp, Host] ->
{tcp, binary_to_list(Host), 80};
[Transport, Host, Port] ->
{Transport, binary_to_list(Host), binary_to_integer(Port)}
end.