From 2e8fcb9a9ef9ef9beff25ed4d48cf0d90609a69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 7 Oct 2019 13:25:49 +0200 Subject: Add cowboy_req:cast/2 Better than sending messages manually. --- doc/src/manual/cowboy_req.asciidoc | 4 +++ doc/src/manual/cowboy_req.cast.asciidoc | 64 +++++++++++++++++++++++++++++++++ doc/src/manual/cowboy_stream.asciidoc | 8 ++--- src/cowboy_req.erl | 46 +++++++++++++----------- test/handlers/compress_h.erl | 12 ++----- test/handlers/set_options_h.erl | 20 +++-------- 6 files changed, 105 insertions(+), 49 deletions(-) create mode 100644 doc/src/manual/cowboy_req.cast.asciidoc diff --git a/doc/src/manual/cowboy_req.asciidoc b/doc/src/manual/cowboy_req.asciidoc index f6f4127..0a1ca1b 100644 --- a/doc/src/manual/cowboy_req.asciidoc +++ b/doc/src/manual/cowboy_req.asciidoc @@ -90,6 +90,10 @@ Response: * link:man:cowboy_req:stream_trailers(3)[cowboy_req:stream_trailers(3)] - Send the response trailers * link:man:cowboy_req:push(3)[cowboy_req:push(3)] - Push a resource to the client +Stream handlers: + +* link:man:cowboy_req:cast(3)[cowboy_req:cast(3)] - Cast a stream handler event + == Types === push_opts() diff --git a/doc/src/manual/cowboy_req.cast.asciidoc b/doc/src/manual/cowboy_req.cast.asciidoc new file mode 100644 index 0000000..d6e018f --- /dev/null +++ b/doc/src/manual/cowboy_req.cast.asciidoc @@ -0,0 +1,64 @@ += cowboy_req:cast(3) + +== Name + +cowboy_req:cast - Cast a stream handler event + +== Description + +[source,erlang] +---- +cast(Event :: any(), Req :: cowboy_req:req()) -> ok +---- + +Cast a stream handler event. + +The event will be passed to stream handlers through the +`info/3` callback. + +== Arguments + +Event:: + +The event to be sent to stream handlers. + +Req:: + +The Req object. + +== Return value + +The atom `ok` is always returned. It can be safely ignored. + +== Changelog + +* *2.7*: Function introduced. + +== Examples + +.Increase the HTTP/1.1 idle timeout +[source,erlang] +---- +cowboy_req:cast({set_options, #{ + idle_timeout => 3600000 +}}, Req). +---- + +.Add user data to metrics +---- +cowboy_req:cast({set_options, #{ + metrics_user_data => #{handler => ?MODULE} +}}, Req). +---- + +.Enable compression buffering +---- +cowboy_req:cast({set_options, #{ + compress_buffering => true +}}, Req). +---- + +== See also + +link:man:cowboy_req(3)[cowboy_req(3)], +link:man:cowboy_stream(3)[cowboy_stream(3)] diff --git a/doc/src/manual/cowboy_stream.asciidoc b/doc/src/manual/cowboy_stream.asciidoc index b1cab3f..868deac 100644 --- a/doc/src/manual/cowboy_stream.asciidoc +++ b/doc/src/manual/cowboy_stream.asciidoc @@ -277,9 +277,8 @@ relevant option's documentation for details. Cowboy will forward all messages sent to the stream to the `info/3` callback. To send a message to a stream, -send a message to the connection process with the form -`{{Pid, StreamID}, Msg}`. The connection process will -then forward `Msg` to the stream handlers. +the function link:man:cowboy_req:cast(3)[cowboy_req:cast(3)] +can be used. Cowboy will also forward the exit signals for the processes that the stream spawned. @@ -403,4 +402,5 @@ tuple. link:man:cowboy(7)[cowboy(7)], link:man:cowboy_http(3)[cowboy_http(3)], -link:man:cowboy_http2(3)[cowboy_http2(3)] +link:man:cowboy_http2(3)[cowboy_http2(3)], +link:man:cowboy_req:cast(3)[cowboy_req:cast(3)] diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 8ae090e..240d95b 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -92,6 +92,9 @@ -export([push/3]). -export([push/4]). +%% Stream handlers. +-export([cast/2]). + %% Internal. -export([response_headers/2]). @@ -516,12 +519,12 @@ read_body(Req=#{has_body := false}, _) -> {ok, <<>>, Req}; read_body(Req=#{has_read_body := true}, _) -> {ok, <<>>, Req}; -read_body(Req=#{pid := Pid, streamid := StreamID}, Opts) -> +read_body(Req, Opts) -> Length = maps:get(length, Opts, 8000000), Period = maps:get(period, Opts, 15000), Timeout = maps:get(timeout, Opts, Period + 1000), Ref = make_ref(), - Pid ! {{Pid, StreamID}, {read_body, self(), Ref, Length, Period}}, + cast({read_body, self(), Ref, Length, Period}, Req), receive {request_body, Ref, nofin, Body} -> {more, Body, Req}; @@ -775,10 +778,8 @@ inform(Status, Req) -> -spec inform(cowboy:http_status(), cowboy:http_headers(), req()) -> ok. inform(_, _, #{has_sent_resp := _}) -> error(function_clause); %% @todo Better error message. -inform(Status, Headers, #{pid := Pid, streamid := StreamID}) - when is_integer(Status); is_binary(Status) -> - Pid ! {{Pid, StreamID}, {inform, Status, Headers}}, - ok. +inform(Status, Headers, Req) when is_integer(Status); is_binary(Status) -> + cast({inform, Status, Headers}, Req). -spec reply(cowboy:http_status(), Req) -> Req when Req::req(). reply(Status, Req) -> @@ -823,11 +824,11 @@ reply(Status, Headers, Body, Req) %% Don't send any body for HEAD responses. While the protocol code is %% supposed to enforce this rule, we prefer to avoid copying too much %% data around if we can avoid it. -do_reply(Status, Headers, _, Req=#{pid := Pid, streamid := StreamID, method := <<"HEAD">>}) -> - Pid ! {{Pid, StreamID}, {response, Status, response_headers(Headers, Req), <<>>}}, +do_reply(Status, Headers, _, Req=#{method := <<"HEAD">>}) -> + cast({response, Status, response_headers(Headers, Req), <<>>}, Req), done_replying(Req, true); -do_reply(Status, Headers, Body, Req=#{pid := Pid, streamid := StreamID}) -> - Pid ! {{Pid, StreamID}, {response, Status, response_headers(Headers, Req), Body}}, +do_reply(Status, Headers, Body, Req) -> + cast({response, Status, response_headers(Headers, Req), Body}, Req), done_replying(Req, true). done_replying(Req, HasSentResp) -> @@ -841,9 +842,8 @@ stream_reply(Status, Req) -> -> Req when Req::req(). stream_reply(_, _, #{has_sent_resp := _}) -> error(function_clause); -stream_reply(Status, Headers=#{}, Req=#{pid := Pid, streamid := StreamID}) - when is_integer(Status); is_binary(Status) -> - Pid ! {{Pid, StreamID}, {headers, Status, response_headers(Headers, Req)}}, +stream_reply(Status, Headers=#{}, Req) when is_integer(Status); is_binary(Status) -> + cast({headers, Status, response_headers(Headers, Req)}, Req), done_replying(Req, headers). -spec stream_body(resp_body(), fin | nofin, req()) -> ok. @@ -872,8 +872,8 @@ stream_body(Data, IsFin, Req=#{has_sent_resp := headers}) stream_body({data, self(), IsFin, Data}, Req). %% @todo Do we need a timeout? -stream_body(Msg, #{pid := Pid, streamid := StreamID}) -> - Pid ! {{Pid, StreamID}, Msg}, +stream_body(Msg, Req=#{pid := Pid}) -> + cast(Msg, Req), receive {data_ack, Pid} -> ok end. -spec stream_events(cow_sse:event() | [cow_sse:event()], fin | nofin, req()) -> ok. @@ -883,9 +883,8 @@ stream_events(Events, IsFin, Req=#{has_sent_resp := headers}) -> stream_body({data, self(), IsFin, cow_sse:events(Events)}, Req). -spec stream_trailers(cowboy:http_headers(), req()) -> ok. -stream_trailers(Trailers, #{pid := Pid, streamid := StreamID, has_sent_resp := headers}) -> - Pid ! {{Pid, StreamID}, {trailers, Trailers}}, - ok. +stream_trailers(Trailers, Req=#{has_sent_resp := headers}) -> + cast({trailers, Trailers}, Req). -spec push(iodata(), cowboy:http_headers(), req()) -> ok. push(Path, Headers, Req) -> @@ -895,14 +894,19 @@ push(Path, Headers, Req) -> %% @todo Path, Headers, Opts, everything should be in proper binary, %% or normalized when creating the Req object. -spec push(iodata(), cowboy:http_headers(), req(), push_opts()) -> ok. -push(Path, Headers, #{pid := Pid, streamid := StreamID, - scheme := Scheme0, host := Host0, port := Port0}, Opts) -> +push(Path, Headers, Req=#{scheme := Scheme0, host := Host0, port := Port0}, Opts) -> Method = maps:get(method, Opts, <<"GET">>), Scheme = maps:get(scheme, Opts, Scheme0), Host = maps:get(host, Opts, Host0), Port = maps:get(port, Opts, Port0), Qs = maps:get(qs, Opts, <<>>), - Pid ! {{Pid, StreamID}, {push, Method, Scheme, Host, Port, Path, Qs, Headers}}, + cast({push, Method, Scheme, Host, Port, Path, Qs, Headers}, Req). + +%% Stream handlers. + +-spec cast(any(), req()) -> ok. +cast(Msg, #{pid := Pid, streamid := StreamID}) -> + Pid ! {{Pid, StreamID}, Msg}, ok. %% Internal. diff --git a/test/handlers/compress_h.erl b/test/handlers/compress_h.erl index 76c2db1..27edbd3 100644 --- a/test/handlers/compress_h.erl +++ b/test/handlers/compress_h.erl @@ -24,9 +24,7 @@ init(Req0, State=reply) -> Size = filelib:file_size(AppFile), cowboy_req:reply(200, #{}, {sendfile, 0, Size, AppFile}, Req0); <<"set_options_threshold0">> -> - %% @todo This should be replaced by a cowboy_req:cast/cowboy_stream:cast. - #{pid := Pid, streamid := StreamID} = Req0, - Pid ! {{Pid, StreamID}, {set_options, #{compress_threshold => 0}}}, + cowboy_req:cast({set_options, #{compress_threshold => 0}}, Req0), cowboy_req:reply(200, #{}, lists:duplicate(100, $a), Req0) end, {ok, Req, State}; @@ -62,14 +60,10 @@ init(Req0, State=stream_reply) -> <<"delayed">> -> stream_delayed(Req0); <<"set_options_buffering_false">> -> - %% @todo This should be replaced by a cowboy_req:cast/cowboy_stream:cast. - #{pid := Pid, streamid := StreamID} = Req0, - Pid ! {{Pid, StreamID}, {set_options, #{compress_buffering => false}}}, + cowboy_req:cast({set_options, #{compress_buffering => false}}, Req0), stream_delayed(Req0); <<"set_options_buffering_true">> -> - %% @todo This should be replaced by a cowboy_req:cast/cowboy_stream:cast. - #{pid := Pid, streamid := StreamID} = Req0, - Pid ! {{Pid, StreamID}, {set_options, #{compress_buffering => true}}}, + cowboy_req:cast({set_options, #{compress_buffering => true}}, Req0), stream_delayed(Req0) end, {ok, Req, State}. diff --git a/test/handlers/set_options_h.erl b/test/handlers/set_options_h.erl index ef88a6f..420b3f9 100644 --- a/test/handlers/set_options_h.erl +++ b/test/handlers/set_options_h.erl @@ -9,32 +9,22 @@ init(Req, State) -> set_options(cowboy_req:binding(key, Req), Req, State). set_options(<<"chunked_false">>, Req0, State) -> - %% @todo This should be replaced by a cowboy_req:cast/cowboy_stream:cast. - #{pid := Pid, streamid := StreamID} = Req0, - Pid ! {{Pid, StreamID}, {set_options, #{chunked => false}}}, + cowboy_req:cast({set_options, #{chunked => false}}, Req0), Req = cowboy_req:stream_reply(200, Req0), cowboy_req:stream_body(<<0:8000000>>, fin, Req), {ok, Req, State}; set_options(<<"chunked_false_ignored">>, Req0, State) -> - %% @todo This should be replaced by a cowboy_req:cast/cowboy_stream:cast. - #{pid := Pid, streamid := StreamID} = Req0, - Pid ! {{Pid, StreamID}, {set_options, #{chunked => false}}}, + cowboy_req:cast({set_options, #{chunked => false}}, Req0), Req = cowboy_req:reply(200, #{}, <<"Hello world!">>, Req0), {ok, Req, State}; set_options(<<"idle_timeout_short">>, Req0, State) -> - %% @todo This should be replaced by a cowboy_req:cast/cowboy_stream:cast. - #{pid := Pid, streamid := StreamID} = Req0, - Pid ! {{Pid, StreamID}, {set_options, #{idle_timeout => 500}}}, + cowboy_req:cast({set_options, #{idle_timeout => 500}}, Req0), {_, Body, Req} = cowboy_req:read_body(Req0), {ok, cowboy_req:reply(200, #{}, Body, Req), State}; set_options(<<"idle_timeout_long">>, Req0, State) -> - %% @todo This should be replaced by a cowboy_req:cast/cowboy_stream:cast. - #{pid := Pid, streamid := StreamID} = Req0, - Pid ! {{Pid, StreamID}, {set_options, #{idle_timeout => 60000}}}, + cowboy_req:cast({set_options, #{idle_timeout => 60000}}, Req0), {_, Body, Req} = cowboy_req:read_body(Req0), {ok, cowboy_req:reply(200, #{}, Body, Req), State}; set_options(<<"metrics_user_data">>, Req, State) -> - %% @todo This should be replaced by a cowboy_req:cast/cowboy_stream:cast. - #{pid := Pid, streamid := StreamID} = Req, - Pid ! {{Pid, StreamID}, {set_options, #{metrics_user_data => #{handler => ?MODULE}}}}, + cowboy_req:cast({set_options, #{metrics_user_data => #{handler => ?MODULE}}}, Req), {ok, cowboy_req:reply(200, #{}, <<"Hello world!">>, Req), State}. -- cgit v1.2.3