mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Make sure we can only reply to an HTTP request inside Handler:handle.
Of course since requests are a record the response state can be explicitly overriden, but standard use prevents errors by making sure only one reply is sent.
This commit is contained in:
parent
d69d0adfa7
commit
71b31cee92
3 changed files with 14 additions and 5 deletions
|
@ -13,9 +13,12 @@
|
||||||
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
-record(http_req, {
|
-record(http_req, {
|
||||||
|
%% Transport.
|
||||||
socket = undefined :: undefined | socket(),
|
socket = undefined :: undefined | socket(),
|
||||||
transport = undefined :: undefined | module(),
|
transport = undefined :: undefined | module(),
|
||||||
connection = keepalive :: keepalive | close,
|
connection = keepalive :: keepalive | close,
|
||||||
|
|
||||||
|
%% Request.
|
||||||
method = 'GET' :: http_method(),
|
method = 'GET' :: http_method(),
|
||||||
version = {1, 1} :: http_version(),
|
version = {1, 1} :: http_version(),
|
||||||
peer = undefined :: undefined | {Address::ip_address(), Port::port_number()},
|
peer = undefined :: undefined | {Address::ip_address(), Port::port_number()},
|
||||||
|
@ -26,6 +29,9 @@
|
||||||
qs_vals = undefined :: undefined | bindings(),
|
qs_vals = undefined :: undefined | bindings(),
|
||||||
raw_qs = undefined :: undefined | string(),
|
raw_qs = undefined :: undefined | string(),
|
||||||
bindings = undefined :: undefined | bindings(),
|
bindings = undefined :: undefined | bindings(),
|
||||||
headers = [] :: http_headers()
|
headers = [] :: http_headers(),
|
||||||
%% cookies = undefined :: undefined | http_cookies() %% @todo
|
%% cookies = undefined :: undefined | http_cookies() %% @todo
|
||||||
|
|
||||||
|
%% Response.
|
||||||
|
resp_state = locked :: locked | waiting | done
|
||||||
}).
|
}).
|
||||||
|
|
|
@ -143,7 +143,8 @@ handler_init(Req, State=#state{handler={Handler, Opts}}) ->
|
||||||
-spec handler_loop(HandlerState::term(), Req::#http_req{},
|
-spec handler_loop(HandlerState::term(), Req::#http_req{},
|
||||||
State::#state{}) -> ok.
|
State::#state{}) -> ok.
|
||||||
handler_loop(HandlerState, Req, State=#state{handler={Handler, _Opts}}) ->
|
handler_loop(HandlerState, Req, State=#state{handler={Handler, _Opts}}) ->
|
||||||
case catch Handler:handle(Req, HandlerState) of
|
case catch Handler:handle(Req#http_req{resp_state=waiting},
|
||||||
|
HandlerState) of
|
||||||
{ok, Req2, HandlerState2} ->
|
{ok, Req2, HandlerState2} ->
|
||||||
handler_terminate(HandlerState2, Req2, State);
|
handler_terminate(HandlerState2, Req2, State);
|
||||||
{'EXIT', _Reason} ->
|
{'EXIT', _Reason} ->
|
||||||
|
@ -153,7 +154,8 @@ handler_loop(HandlerState, Req, State=#state{handler={Handler, _Opts}}) ->
|
||||||
-spec handler_terminate(HandlerState::term(), Req::#http_req{},
|
-spec handler_terminate(HandlerState::term(), Req::#http_req{},
|
||||||
State::#state{}) -> ok.
|
State::#state{}) -> ok.
|
||||||
handler_terminate(HandlerState, Req, State=#state{handler={Handler, _Opts}}) ->
|
handler_terminate(HandlerState, Req, State=#state{handler={Handler, _Opts}}) ->
|
||||||
Res = (catch Handler:terminate(Req, HandlerState)),
|
Res = (catch Handler:terminate(
|
||||||
|
Req#http_req{resp_state=locked}, HandlerState)),
|
||||||
%% @todo We need to check if the Req has been replied to.
|
%% @todo We need to check if the Req has been replied to.
|
||||||
%% All requests must have a reply, at worst an error.
|
%% All requests must have a reply, at worst an error.
|
||||||
%% If a request started but wasn't completed, complete it.
|
%% If a request started but wasn't completed, complete it.
|
||||||
|
|
|
@ -135,13 +135,14 @@ headers(Req) ->
|
||||||
Body::iolist(), Req::#http_req{}) -> ok.
|
Body::iolist(), Req::#http_req{}) -> ok.
|
||||||
%% @todo Don't be naive about the headers!
|
%% @todo Don't be naive about the headers!
|
||||||
reply(Code, Headers, Body, Req=#http_req{socket=Socket,
|
reply(Code, Headers, Body, Req=#http_req{socket=Socket,
|
||||||
transport=Transport, connection=Connection}) ->
|
transport=Transport, connection=Connection,
|
||||||
|
resp_state=waiting}) ->
|
||||||
StatusLine = ["HTTP/1.1 ", status(Code), "\r\n"],
|
StatusLine = ["HTTP/1.1 ", status(Code), "\r\n"],
|
||||||
BaseHeaders = ["Connection: ", atom_to_connection(Connection),
|
BaseHeaders = ["Connection: ", atom_to_connection(Connection),
|
||||||
"\r\nContent-Length: ", integer_to_list(iolist_size(Body)), "\r\n"],
|
"\r\nContent-Length: ", integer_to_list(iolist_size(Body)), "\r\n"],
|
||||||
Transport:send(Socket,
|
Transport:send(Socket,
|
||||||
[StatusLine, BaseHeaders, Headers, "\r\n", Body]),
|
[StatusLine, BaseHeaders, Headers, "\r\n", Body]),
|
||||||
{ok, Req}.
|
{ok, Req#http_req{resp_state=done}}.
|
||||||
|
|
||||||
%% Internal.
|
%% Internal.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue