Skip to content

Instantly share code, notes, and snippets.

@essen
Created December 12, 2018 16:38
Show Gist options
  • Save essen/0f5bc9cb0b45d79423582d94ac90b318 to your computer and use it in GitHub Desktop.
Save essen/0f5bc9cb0b45d79423582d94ac90b318 to your computer and use it in GitHub Desktop.
-module(gun_proxied_tls).
-behaviour(gen_server).
%% Gun-specific interface.
-export([ssl_connect/2]).
-export([ssl_connect/3]).
-export([proxy_received/2]).
%% Transport callback.
-export([connect/4]).
-export([controlling_process/2]).
-export([send/2]).
-export([setopts/2]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-record(state, {
%% The pid of the Gun process.
gun_pid :: pid(),
%% The pid of the TLS connection.
controlling_pid :: pid(),
%% Metadata about the connection.
meta :: map(),
%% Active mode state.
active = false :: false | true | pos_integer(),
%% Buffer for incoming data.
buffer = <<>> :: binary()
}).
%% Gun-specific interface.
ssl_connect(Host, Port) ->
spawn_link(?MODULE, ssl_connect, [self(), Host, Port]).
ssl_connect(Parent, Host, Port) ->
{ok, Socket} = ssl:connect(Host, Port, [
{cb_info, {gun_proxied_tls, gun_data, gun_closed, gun_error}},
{?MODULE, Parent}
]),
ssl:controlling_process(Socket, Parent),
Parent ! {?MODULE, {ssl_connect, Socket}},
ok.
proxy_received(Pid, Data) ->
gen_server:cast(Pid, {?FUNCTION_NAME, Data}).
%% Transport callback.
%% The connect/4 function is called by the process
%% that calls ssl:connect/2,3,4.
connect(Address, Port, Opts, _Timeout) ->
gen_server:start_link(?MODULE, {self(), Opts, #{
address => Address,
port => Port
}}, []).
%% Nothing to do, we're just a callback module.
controlling_process(Pid, ControllingPid) ->
gen_server:cast(Pid, {?FUNCTION_NAME, ControllingPid}).
send(Pid, Data) ->
gen_server:cast(Pid, {?FUNCTION_NAME, Data}).
setopts(Pid, Opts) ->
gen_server:cast(Pid, {?FUNCTION_NAME, Opts}).
%% gen_server.
-spec init({pid(), [inet:socket_setopt()], map()}) -> {ok, passive, #state{}}.
init({ControllingPid, Opts, Meta}) ->
{_, GunPid} = lists:keyfind(?MODULE, 1, Opts),
GunPid ! {?MODULE, ControllingPid, {proxied_pid, self()}},
{ok, handle_setopts(Opts, #state{
gun_pid=GunPid,
controlling_pid=ControllingPid,
meta=Meta
})}.
handle_call(_, _, State) ->
{reply, {error, bad_call}, State}.
handle_cast({proxy_received, Data}, State=#state{buffer=Buffer}) ->
{noreply, active(State#state{buffer= <<Buffer/binary, Data/binary>>})};
handle_cast({controlling_process, ControllingPid}, State) ->
{noreply, State#state{controlling_pid=ControllingPid}};
handle_cast(Event={send, _}, State=#state{gun_pid=GunPid}) ->
GunPid ! {?MODULE, self(), Event},
{noreply, State};
handle_cast({setopts, Opts}, State0) ->
{noreply, active(handle_setopts(Opts, State0))};
handle_cast(_, State) ->
{noreply, State}.
handle_info(_, State) ->
{noreply, State}.
handle_setopts(Opts, State0) ->
case [A || {active, A} <- Opts] of
[] -> State0;
[false] -> State0#state{active=false};
[0] -> State0#state{active=false};
[once] -> State0#state{active=1};
[Active] -> active(State0#state{active=Active})
end.
active(State=#state{buffer= <<>>}) ->
State;
active(State=#state{active=false}) ->
State;
active(State=#state{controlling_pid=Pid, active=Active0, buffer=Buffer}) ->
Pid ! {gun_data, self(), Buffer},
Active = case Active0 of
true -> true;
1 -> false;
N -> N - 1
end,
State#state{active=Active, buffer= <<>>}.
-ifdef(TEST).
my_test() ->
ssl:start(),
dbg:tracer(),
dbg:tpl(?MODULE, []),
dbg:p(all, c),
Self = self(),
ConnectPid = ssl_connect("google.com", 443),
{ok, Socket} = gen_tcp:connect("google.com", 443, [binary, {active, true}]),
ProxyPid = my_receive_loop(Socket, ConnectPid),
io:format(user, "~p~n", [erlang:process_info(Self, messages)]),
io:format(user, "~p~n", [erlang:process_info(ProxyPid, messages)]),
ok.
my_receive_loop(Socket, Pid) ->
receive
{?MODULE, {ssl_connect, SSL}} ->
io:format(user, "~p~n", [SSL]),
ssl:send(SSL, <<"GET / HTTP/1.1\r\nHost: google.com\r\n\r\n">>),
ssl:setopts(SSL, [{active, true}]),
my_receive_loop(Socket, Pid);
{?MODULE, Pid, {proxied_pid, ProxiedPid}} ->
my_receive_loop(Socket, ProxiedPid);
{?MODULE, Pid, {send, Data}} ->
gen_tcp:send(Socket, Data),
my_receive_loop(Socket, Pid);
{tcp, Socket, Data} ->
proxy_received(Pid, Data),
my_receive_loop(Socket, Pid)
after 1000 ->
Pid
end.
-endif.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment