Skip to content

Commit

Permalink
Introduce transport abstraction. Fixes #63 (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
tank-bohr committed Nov 21, 2019
1 parent 2cbe753 commit f540c06
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 162 deletions.
7 changes: 0 additions & 7 deletions elvis.config
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,6 @@
{elvis_style, function_naming_convention, #{
ignore => [bookish_spork_request],
regex => "^([a-z][a-z0-9]*_?)*$"
}},
{elvis_style, invalid_dynamic_call, #{
ignore => [
bookish_spork_server,
bookish_spork_acceptor,
bookish_spork_handler
]
}}
],
ruleset => erl_files
Expand Down
48 changes: 18 additions & 30 deletions src/bookish_spork_acceptor.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

-export([
child_spec/1,
start_link/3
start_link/2
]).

-behaviour(gen_server).
Expand All @@ -15,10 +15,9 @@
]).

-record(state, {
server :: pid(),
transport :: gen_tcp | bookish_spork_ssl,
socket :: gen_tcp:socket() | ssl:sslsocket(),
sup :: pid()
sup :: pid(),
server :: pid(),
listen_socket :: bookish_spork_transport:listen()
}).

child_spec(Args) ->
Expand All @@ -31,27 +30,26 @@ child_spec(Args) ->
modules => [?MODULE]
}.

start_link(Server, Transport, ListenSocket) ->
gen_server:start_link(?MODULE, {Server, Transport, ListenSocket}, []).
start_link(Server, ListenSocket) ->
gen_server:start_link(?MODULE, {Server, ListenSocket}, []).

%% @private
init({Server, Transport, ListenSocket}) ->
{ok, Sup} = bookish_spork_sup:start_handler_sup(Server, Transport),
init({Server, ListenSocket}) ->
{ok, Sup} = bookish_spork_sup:start_handler_sup(Server),
accept(),
{ok, #state{
sup = Sup,
server = Server,
transport = Transport,
socket = ListenSocket,
sup = Sup
listen_socket = ListenSocket
}}.

%% @private
handle_call(_Request, _From, State) ->
{reply, {error, unknown_call}, State}.

%% @private
handle_cast(accept, #state{transport = Transport, socket = Socket, sup = Sup} = State) ->
accept(Transport, Socket, Sup),
handle_cast(accept, #state{listen_socket = ListenSocket, sup = Sup} = State) ->
accept(ListenSocket, Sup),
{noreply, State};
handle_cast(_Msg, State) ->
{noreply, State}.
Expand All @@ -67,20 +65,10 @@ terminate(_Reason, #state{sup = Sup}) ->
accept() ->
gen_server:cast(self(), accept).

accept(Transport, ListenSocket, Sup) ->
{Socket, TlsExt} = case Transport:accept(ListenSocket, 5000) of
{ok, Sock, Ext} ->
{Sock, Ext};
{ok, Sock} ->
{Sock, undefined}
end,
ConnectionId = generate_id(),
bookish_spork_sup:start_handler(Sup, Socket, TlsExt, ConnectionId),
-spec accept(ListenSocket, Sup) -> ok when
ListenSocket :: bookish_spork_transport:listen(),
Sup :: pid().
accept(ListenSocket, Sup) ->
Transport = bookish_spork_transport:accept(ListenSocket),
bookish_spork_sup:start_handler(Sup, Transport),
accept().

-spec generate_id() -> Id :: binary().
%% @doc generates unique id to be a connection id
generate_id() ->
Bytes = crypto:strong_rand_bytes(7),
Base64 = base64:encode(Bytes),
string:trim(Base64, trailing, "=").
115 changes: 42 additions & 73 deletions src/bookish_spork_handler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

-export([
child_spec/1,
start_link/5
start_link/2
]).

-behaviour(gen_server).
Expand All @@ -13,17 +13,9 @@
handle_info/2
]).

-type transport() :: gen_tcp | bookish_spork_ssl.
-type socket() :: gen_tcp:socket() | ssl:sslsocket().
-type tls_ext() :: undefined | ssl:protocol_extensions().
-type connection_id() :: binary().

-record(state, {
server :: pid(),
transport :: transport(),
connection_id :: connection_id(),
socket :: socket(),
tls_ext :: tls_ext()
server :: pid(),
transport :: bookish_spork_transport:t()
}).

-type state() :: #state{}.
Expand All @@ -38,32 +30,23 @@ child_spec(Args) ->
modules => [?MODULE]
}.

-spec start_link(Server, Transport, Socket, TlsExt, ConnectionId) -> {ok, pid()} when
-spec start_link(Server, Transport) -> {ok, pid()} when
Server :: pid(),
Transport :: transport(),
Socket :: socket(),
TlsExt :: tls_ext(),
ConnectionId :: connection_id().
start_link(Server, Transport, Socket, TlsExt, ConnectionId) ->
Args = {Server, Transport, Socket, TlsExt, ConnectionId},
Transport :: bookish_spork_transport:t().
start_link(Server, Transport) ->
Args = {Server, Transport},
gen_server:start_link(?MODULE, Args, []).

-spec init({Server, Transport, Socket, TlsExt, ConnectionId}) -> {ok, State} when
State :: state(),
Server :: pid(),
Transport :: transport(),
Socket :: socket(),
TlsExt :: tls_ext(),
ConnectionId :: connection_id().
-spec init({Server, Transport}) -> {ok, State} when
State :: state(),
Server :: pid(),
Transport :: bookish_spork_transport:t().
%% @private
init({Server, Transport, Socket, TlsExt, ConnectionId}) ->
init({Server, Transport}) ->
handle_connection(),
{ok, #state{
server = Server,
transport = Transport,
socket = Socket,
tls_ext = TlsExt,
connection_id = ConnectionId
server = Server,
transport = Transport
}}.

%% @private
Expand All @@ -87,16 +70,16 @@ handle_connection() ->

-spec handle_connection(state()) -> {noreply, state()} | {stop, normal, state()}.
%% @private
handle_connection(#state{transport = Transport, socket = Socket, server = Server} = State) ->
handle_connection(#state{transport = Transport, server = Server} = State) ->
case receive_request(State) of
{ok, Request} ->
ok = bookish_spork_server:store_request(Server, Request),
case bookish_spork_server:response(Server) of
{ok, Response} ->
reply(Transport, Socket, Response, Request),
reply(Transport, Response, Request),
complete_connection(State, Request);
{error, no_response} ->
Transport:close(Socket),
bookish_spork_transport:close(Transport),
{stop, normal, State}

end;
Expand All @@ -108,80 +91,66 @@ handle_connection(#state{transport = Transport, socket = Socket, server = Server
State :: state(),
Request :: bookish_spork_request:t().
%% @private
complete_connection(#state{transport = Transport, socket = Socket} = State, Request) ->
complete_connection(#state{transport = Transport} = State, Request) ->
case bookish_spork_request:is_keepalive(Request) of
true ->
handle_connection(),
{noreply, State};
false ->
Transport:shutdown(Socket, read_write),
bookish_spork_transport:shutdown(Transport),
{stop, normal, State}
end.

-spec receive_request(State :: state()) -> Result when
Result :: {ok, Request} | socket_closed,
Request :: bookish_spork_request:t().
%% @private
receive_request(State) ->
#state{
transport = Transport,
connection_id = ConnectionId,
socket = Socket,
tls_ext = TlsExt
} = State,
Request = bookish_spork_request:new(ConnectionId, Socket, TlsExt),
read_from_socket(Transport, Socket, Request).

-spec read_from_socket(Transport, Socket, RequestIn) -> Result when
Transport :: transport(),
Socket :: socket(),
receive_request(#state{transport = Transport}) ->
Request = bookish_spork_request:from_transport(Transport),
read_from_socket(Transport, Request).

-spec read_from_socket(Transport, RequestIn) -> Result when
Transport :: bookish_spork_transport:t(),
RequestIn :: bookish_spork_request:t(),
Result :: {ok, RequestOut} | socket_closed,
RequestOut :: bookish_spork_request:t().
%% @private
read_from_socket(Transport, Socket, RequestIn) ->
case Transport:recv(Socket, 0) of
read_from_socket(Transport, RequestIn) ->
case bookish_spork_transport:recv(Transport) of
{ok, {http_request, Method, {abs_path, Uri}, Version}} ->
RequestOut = bookish_spork_request:request_line(RequestIn, Method, Uri, Version),
read_from_socket(Transport, Socket, RequestOut);
read_from_socket(Transport, RequestOut);
{ok, {http_header, _, Header, _, Value}} ->
RequestOut = bookish_spork_request:add_header(RequestIn, Header, Value),
read_from_socket(Transport, Socket, RequestOut);
read_from_socket(Transport, RequestOut);
{ok, http_eoh} ->
Body = read_body(Transport, Socket, bookish_spork_request:content_length(RequestIn)),
Body = read_body(Transport, bookish_spork_request:content_length(RequestIn)),
RequestOut = bookish_spork_request:body(RequestIn, Body),
{ok, RequestOut};
{ok, {http_error, HttpError}} ->
erlang:error({http_error, HttpError}, [Transport, Socket, RequestIn]);
erlang:error({http_error, HttpError}, [Transport, RequestIn]);
{error, closed} ->
socket_closed;
{error, enotconn} ->
socket_closed
end.

-spec read_body(Transport, Socket, ContentLength) -> Body when
Transport :: transport(),
Socket :: socket(),
-spec read_body(Transport, ContentLength) -> Body when
Transport :: bookish_spork_transport:t(),
ContentLength :: non_neg_integer(),
Body :: binary().
%% @private
read_body(_Transport, _Socket, 0) ->
<<>>;
read_body(Transport, Socket, ContentLength) ->
inet:setopts(Socket, [{packet, raw}]),
{ok, Body} = Transport:recv(Socket, ContentLength),
inet:setopts(Socket, [{packet, http}]),
Body.

-spec reply(Transport, Socket, Response, Request) -> ok when
Transport :: transport(),
Socket :: socket(),
read_body(Transport, ContentLength) ->
bookish_spork_transport:read_raw(Transport, ContentLength).

-spec reply(Transport, Response, Request) -> ok when
Transport :: bookish_spork_transport:t(),
Response :: bookish_spork:stub_request_fun() | bookish_spork_response:t(),
Request :: bookish_spork_request:t().
%% @private
reply(Transport, Socket, ResponseFun, Request) when is_function(ResponseFun) ->
reply(Transport, ResponseFun, Request) when is_function(ResponseFun) ->
Response = ResponseFun(Request),
reply(Transport, Socket, bookish_spork_response:new(Response), Request);
reply(Transport, Socket, Response, _Request) ->
reply(Transport, bookish_spork_response:new(Response), Request);
reply(Transport, Response, _Request) ->
String = bookish_spork_response:write_str(Response, calendar:universal_time()),
Transport:send(Socket, [String]).
bookish_spork_transport:send(Transport, [String]).
Loading

0 comments on commit f540c06

Please sign in to comment.