aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_http.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cowboy_http.erl')
-rw-r--r--src/cowboy_http.erl72
1 files changed, 43 insertions, 29 deletions
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl
index 0231def..5ec7d0e 100644
--- a/src/cowboy_http.erl
+++ b/src/cowboy_http.erl
@@ -111,8 +111,7 @@
%% The connection will be closed after this stream.
last_streamid = undefined :: pos_integer(),
- %% Currently active HTTP/1.1 streams. Streams may be initiated either
- %% by the client or by the server through PUSH_PROMISE frames.
+ %% Currently active HTTP/1.1 streams.
streams = [] :: [stream()],
%% Children which are in the process of shutting down.
@@ -202,7 +201,7 @@ loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
loop(State, Buffer);
%% Unknown messages.
Msg ->
- error_logger:error_msg("Received stray message ~p.", [Msg]),
+ error_logger:error_msg("Received stray message ~p.~n", [Msg]),
loop(State, Buffer)
%% @todo Configurable timeout. This should be a global inactivity timeout
%% that triggers when really nothing happens (ie something went really wrong).
@@ -271,6 +270,7 @@ after_parse({request, Req=#{streamid := StreamID, headers := Headers, version :=
error_logger:error_msg("Exception occurred in ~s:init(~p, ~p, ~p) "
"with reason ~p:~p.",
[Handler, StreamID, Req, Opts, Class, Reason]),
+ %% @todo Bad value returned here. Crashes.
ok
%% @todo Status code.
% stream_reset(State, StreamID, {internal_error, {Class, Reason},
@@ -288,6 +288,7 @@ after_parse({data, StreamID, IsFin, Data, State=#state{handler=Handler,
catch Class:Reason ->
error_logger:error_msg("Exception occurred in ~s:data(~p, ~p, ~p, ~p) with reason ~p:~p.",
[Handler, StreamID, IsFin, Data, StreamState0, Class, Reason]),
+ %% @todo Bad value returned here. Crashes.
ok
%% @todo
% stream_reset(State, StreamID, {internal_error, {Class, Reason},
@@ -502,6 +503,8 @@ parse_hd_value(<< $\r, $\n, Rest/bits >>, S, Headers0, Name, SoFar) ->
Value = clean_value_ws_end(SoFar, byte_size(SoFar) - 1),
Headers = case maps:get(Name, Headers0, undefined) of
undefined -> Headers0#{Name => Value};
+ %% The cookie header does not use proper HTTP header lists.
+ Value0 when Name =:= <<"cookie">> -> Headers0#{Name => << Value0/binary, "; ", Value/binary >>};
Value0 -> Headers0#{Name => << Value0/binary, ", ", Value/binary >>}
end,
parse_header(Rest, S, Headers);
@@ -575,7 +578,7 @@ parse_host(<< $[, Rest/bits >>, false, <<>>) ->
parse_host(<<>>, false, Acc) ->
{Acc, undefined};
parse_host(<< $:, Rest/bits >>, false, Acc) ->
- {Acc, list_to_integer(binary_to_list(Rest))};
+ {Acc, binary_to_integer(Rest)};
parse_host(<< $], Rest/bits >>, true, Acc) ->
parse_host(Rest, false, << Acc/binary, $] >>);
parse_host(<< C, Rest/bits >>, E, Acc) ->
@@ -617,6 +620,9 @@ request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, in_stream
scheme => Scheme,
host => Host,
port => Port,
+
+%% @todo So the path component needs to be normalized.
+
path => Path,
qs => Qs,
version => Version,
@@ -734,6 +740,11 @@ parse_body(Buffer, State=#state{in_streamid=StreamID, in_state=
%% Message handling.
+%% @todo There is a difference in behavior between HTTP/1.1 and HTTP/2
+%% when an error or crash occurs after sending a 500 response. In HTTP/2
+%% the error will be printed, in HTTP/1.1 the error will be ignored.
+%% This is due to HTTP/1.1 disabling streams differently after both
+%% requests and responses have been sent.
down(State=#state{children=Children0}, Pid, Msg) ->
case lists:keytake(Pid, 1, Children0) of
{value, {_, undefined, _}, Children} ->
@@ -741,7 +752,7 @@ down(State=#state{children=Children0}, Pid, Msg) ->
{value, {_, StreamID, _}, Children} ->
info(State#state{children=Children}, StreamID, Msg);
false ->
- error_logger:error_msg("Received EXIT signal ~p for unknown process ~p.", [Msg, Pid]),
+ error_logger:error_msg("Received EXIT signal ~p for unknown process ~p.~n", [Msg, Pid]),
State
end.
@@ -762,16 +773,10 @@ info(State=#state{handler=Handler, streams=Streams0}, StreamID, Msg) ->
% 'Exception occurred in StreamHandler:info/3 call.'})
end;
false ->
- error_logger:error_msg("Received message ~p for unknown stream ~p.", [Msg, StreamID]),
+ error_logger:error_msg("Received message ~p for unknown stream ~p.~n", [Msg, StreamID]),
State
end.
-%% @todo commands/3
-%% @todo stream_reset
-
-
-
-
%% Commands.
commands(State, _, []) ->
@@ -800,20 +805,24 @@ commands(State, StreamID, [{flow, _Length}|Tail]) ->
%% @todo Set the body reading length to min(Length, BodyLength)
commands(State, StreamID, Tail);
-%% @todo Probably a good idea to have an atomic response send (single send call for resp+body).
+%% Error responses are sent only if a response wasn't sent already.
+commands(State=#state{out_state=wait}, StreamID, [{error_response, StatusCode, Headers, Body}|Tail]) ->
+ commands(State, StreamID, [{response, StatusCode, Headers, Body}|Tail]);
+commands(State, StreamID, [{error_response, _, _, _}|Tail]) ->
+ commands(State, StreamID, Tail);
%% Send a full response.
%%
%% @todo Kill the stream if it sent a response when one has already been sent.
%% @todo Keep IsFin in the state.
%% @todo Same two things above apply to DATA, possibly promise too.
-commands(State0=#state{socket=Socket, transport=Transport, streams=Streams}, StreamID,
+commands(State0=#state{socket=Socket, transport=Transport, out_state=wait, streams=Streams}, StreamID,
[{response, StatusCode, Headers0, Body}|Tail]) ->
%% @todo I'm pretty sure the last stream in the list is the one we want
%% considering all others are queued.
#stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams),
{State, Headers} = connection(State0, Headers0, StreamID, Version),
%% @todo Ensure content-length is set.
- Response = cow_http:response(StatusCode, 'HTTP/1.1', maps:to_list(Headers)),
+ Response = cow_http:response(StatusCode, 'HTTP/1.1', headers_to_list(Headers)),
case Body of
{sendfile, O, B, P} ->
Transport:send(Socket, Response),
@@ -838,13 +847,17 @@ commands(State0=#state{socket=Socket, transport=Transport, streams=Streams}, Str
{State0#state{last_streamid=StreamID}, Headers0}
end,
{State, Headers} = connection(State1, Headers1, StreamID, Version),
- Transport:send(Socket, cow_http:response(StatusCode, 'HTTP/1.1', maps:to_list(Headers))),
+ Transport:send(Socket, cow_http:response(StatusCode, 'HTTP/1.1', headers_to_list(Headers))),
commands(State#state{out_state=chunked}, StreamID, Tail);
%% Send a response body chunk.
%%
%% @todo WINDOW_UPDATE stuff require us to buffer some data.
+%% @todo We probably want to allow Data to be the {sendfile, ...} tuple also.
commands(State=#state{socket=Socket, transport=Transport, streams=Streams}, StreamID,
[{data, IsFin, Data}|Tail]) ->
+
+ %% @todo We need to kill the stream if it tries to send data before headers.
+
%% @todo Same as above.
case lists:keyfind(StreamID, #stream.id, Streams) of
#stream{version='HTTP/1.1'} ->
@@ -885,7 +898,20 @@ commands(State0=#state{ref=Ref, parent=Parent, socket=Socket, transport=Transpor
commands(State, StreamID, [stop|Tail]) ->
%% @todo Do we want to run the commands after a stop?
% commands(stream_terminate(State, StreamID, stop), StreamID, Tail).
- maybe_terminate(State, StreamID, Tail, fin).
+
+ %% @todo I think that's where we need to terminate streams.
+
+ maybe_terminate(State, StreamID, Tail, fin);
+%% HTTP/1.1 does not support push; ignore.
+commands(State, StreamID, [{push, _, _, _, _, _, _, _}|Tail]) ->
+ commands(State, StreamID, Tail).
+
+%% The set-cookie header is special; we can only send one cookie per header.
+headers_to_list(Headers0=#{<<"set-cookie">> := SetCookies}) ->
+ Headers1 = maps:to_list(maps:remove(<<"set-cookie">>, Headers0)),
+ Headers1 ++ [{<<"set-cookie">>, Value} || Value <- SetCookies];
+headers_to_list(Headers) ->
+ maps:to_list(Headers).
flush() ->
receive _ -> flush() after 0 -> ok end.
@@ -1013,18 +1039,6 @@ error_terminate(StatusCode, State=#state{socket=Socket, transport=Transport}, Re
terminate(_State, _Reason) ->
exit(normal). %% @todo
-
-
-
-
-
-
-
-
-
-
-
-
%% System callbacks.
-spec system_continue(_, _, #state{}) -> ok.