0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 20:30:23 +00:00

Add cowboy_req:inform/2,3

User code can now send as many 1xx responses as necessary.
This commit is contained in:
Loïc Hoguin 2017-10-29 19:52:27 +00:00
parent f4331f7c16
commit f3d6b05b86
No known key found for this signature in database
GPG key ID: 8A9DF795F6FED764
11 changed files with 206 additions and 1 deletions

View file

@ -262,6 +262,29 @@ Req = cowboy_req:reply(200, #{
// example would be automatic concatenation of CSS or JS
// files.
=== Informational responses
Cowboy allows you to send informational responses.
Informational responses are responses that have a status
code between 100 and 199. Any number can be sent before
the proper response. Sending an informational response
does not change the behavior of the proper response, and
clients are expected to ignore any informational response
they do not understand.
The following snippet sends a 103 informational response
with some headers that are expected to be in the final
response.
[source,erlang]
----
Req = cowboy_req:inform(103, #{
<<"link">> => <<"</style.css>; rel=preload; as=style">>,
<<"link">> => <<"</script.js>; rel=preload; as=script">>
}, Req0).
----
=== Push
The HTTP/2 protocol introduced the ability to push resources

View file

@ -80,6 +80,7 @@ Response:
* link:man:cowboy_req:delete_resp_header(3)[cowboy_req:delete_resp_header(3)] - Delete a response header
* link:man:cowboy_req:set_resp_body(3)[cowboy_req:set_resp_body(3)] - Set the response body
* link:man:cowboy_req:has_resp_body(3)[cowboy_req:has_resp_body(3)] - Is there a response body?
* link:man:cowboy_req:inform(3)[cowboy_req:inform(3)] - Send an informational response
* link:man:cowboy_req:reply(3)[cowboy_req:reply(3)] - Send the response
* link:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)] - Send the response headers
* link:man:cowboy_req:stream_body(3)[cowboy_req:stream_body(3)] - Stream the response body

View file

@ -0,0 +1,83 @@
= cowboy_req:inform(3)
== Name
cowboy_req:inform - Send an informational response
== Description
[source,erlang]
----
inform(Status, Req :: cowboy_req:req())
-> inform(StatusCode, #{}, Req)
inform(Status, Headers, Req :: cowboy_req:req())
-> ok
Status :: cowboy:http_status()
Headers :: cowboy:http_headers()
----
Send an informational response.
Informational responses use a status code between 100 and 199.
They cannot include a body. This function will not use any
of the previously set headers. All headers to be sent must
be given directly.
Any number of informational responses can be sent as long as
they are sent before the proper response. Attempting to use
this function after sending a normal response will result
in an error.
The header names must be given as lowercase binary strings.
While header names are case insensitive, Cowboy requires them
to be given as lowercase to function properly.
== Arguments
Status::
The status code for the response.
Headers::
The response headers.
Header names must be given as lowercase binary strings.
Req::
The Req object.
== Return value
The atom `ok` is always returned. It can be safely ignored.
== Changelog
* *2.0*: Function introduced.
== Examples
.Send an informational response
[source,erlang]
----
Req = cowboy_req:inform(102, Req0).
----
.Send an informational response with headers
[source,erlang]
----
Req = cowboy_req:inform(103, #{
<<"link">> => <<"</style.css>; rel=preload; as=style">>,
<<"link">> => <<"</script.js>; rel=preload; as=script">>
}, Req0).
----
== See also
link:man:cowboy_req(3)[cowboy_req(3)],
link:man:cowboy_req:reply(3)[cowboy_req:reply(3)],
link:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)],
link:man:cowboy_req:push(3)[cowboy_req:push(3)]

View file

@ -94,5 +94,6 @@ cowboy_req:push("/static/style.css", #{
== See also
link:man:cowboy_req(3)[cowboy_req(3)],
link:man:cowboy_req:inform(3)[cowboy_req:inform(3)],
link:man:cowboy_req:reply(3)[cowboy_req:reply(3)],
link:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)]

View file

@ -113,5 +113,6 @@ link:man:cowboy_req:set_resp_cookie(3)[cowboy_req:set_resp_cookie(3)],
link:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)],
link:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)],
link:man:cowboy_req:set_resp_body(3)[cowboy_req:set_resp_body(3)],
link:man:cowboy_req:inform(3)[cowboy_req:inform(3)],
link:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)],
link:man:cowboy_req:push(3)[cowboy_req:push(3)]

View file

@ -103,6 +103,7 @@ link:man:cowboy_req(3)[cowboy_req(3)],
link:man:cowboy_req:set_resp_cookie(3)[cowboy_req:set_resp_cookie(3)],
link:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)],
link:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)],
link:man:cowboy_req:inform(3)[cowboy_req:inform(3)],
link:man:cowboy_req:reply(3)[cowboy_req:reply(3)],
link:man:cowboy_req:stream_body(3)[cowboy_req:stream_body(3)],
link:man:cowboy_req:push(3)[cowboy_req:push(3)]

View file

@ -495,6 +495,13 @@ commands(State, Stream=#stream{local=idle}, [{error_response, StatusCode, Header
commands(State, Stream, [{response, StatusCode, Headers, Body}|Tail]);
commands(State, Stream, [{error_response, _, _, _}|Tail]) ->
commands(State, Stream, Tail);
%% Send an informational response.
commands(State=#state{socket=Socket, transport=Transport, encode_state=EncodeState0},
Stream=#stream{id=StreamID, local=idle}, [{inform, StatusCode, Headers0}|Tail]) ->
Headers = Headers0#{<<":status">> => status(StatusCode)},
{HeaderBlock, EncodeState} = headers_encode(Headers, EncodeState0),
Transport:send(Socket, cow_http2:headers(StreamID, fin, HeaderBlock)),
commands(State#state{encode_state=EncodeState}, Stream, Tail);
%% Send response headers.
%%
%% @todo Kill the stream if it sent a response when one has already been sent.

View file

@ -71,6 +71,8 @@
-export([set_resp_body/2]).
%% @todo set_resp_body/3 with a ContentType or even Headers argument, to set content headers.
-export([has_resp_body/1]).
-export([inform/2]).
-export([inform/3]).
-export([reply/2]).
-export([reply/3]).
-export([reply/4]).
@ -685,6 +687,18 @@ has_resp_body(_) ->
delete_resp_header(Name, Req=#{resp_headers := RespHeaders}) ->
Req#{resp_headers => maps:remove(Name, RespHeaders)}.
-spec inform(cowboy:http_status(), req()) -> ok.
inform(Status, Req) ->
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.
-spec reply(cowboy:http_status(), Req) -> Req when Req::req().
reply(Status, Req) ->
reply(Status, #{}, Req).
@ -699,7 +713,7 @@ reply(Status, Headers, Req) ->
-spec reply(cowboy:http_status(), cowboy:http_headers(), resp_body(), Req)
-> Req when Req::req().
reply(_, _, _, #{has_sent_resp := _}) ->
error(function_clause);
error(function_clause); %% @todo Better error message.
reply(Status, Headers, {sendfile, _, 0, _}, Req)
when is_integer(Status); is_binary(Status) ->
do_reply(Status, Headers#{

View file

@ -119,6 +119,8 @@ info(_StreamID, {read_body_timeout, Ref}, State=#state{pid=Pid, read_body_ref=Re
info(_StreamID, {read_body_timeout, _}, State) ->
{[], State};
%% Response.
info(_StreamID, Inform = {inform, _, _}, State) ->
{[Inform], State};
info(_StreamID, Response = {response, _, _, _}, State) ->
{[Response], State};
info(_StreamID, Headers = {headers, _, _}, State) ->

View file

@ -100,6 +100,37 @@ do(<<"delete_resp_header">>, Req0, Opts) ->
Req = cowboy_req:delete_resp_header(<<"content-type">>, Req1),
false = cowboy_req:has_resp_header(<<"content-type">>, Req),
{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"inform2">>, Req0, Opts) ->
case cowboy_req:binding(arg, Req0) of
<<"binary">> ->
cowboy_req:inform(<<"102 On my way">>, Req0);
<<"error">> ->
ct_helper:ignore(cowboy_req, inform, 3),
cowboy_req:inform(ok, Req0);
<<"twice">> ->
cowboy_req:inform(102, Req0),
cowboy_req:inform(102, Req0);
Status ->
cowboy_req:inform(binary_to_integer(Status), Req0)
end,
Req = cowboy_req:reply(200, Req0),
{ok, Req, Opts};
do(<<"inform3">>, Req0, Opts) ->
Headers = #{<<"ext-header">> => <<"ext-value">>},
case cowboy_req:binding(arg, Req0) of
<<"binary">> ->
cowboy_req:inform(<<"102 On my way">>, Headers, Req0);
<<"error">> ->
ct_helper:ignore(cowboy_req, inform, 3),
cowboy_req:inform(ok, Headers, Req0);
<<"twice">> ->
cowboy_req:inform(102, Headers, Req0),
cowboy_req:inform(102, Headers, Req0);
Status ->
cowboy_req:inform(binary_to_integer(Status), Headers, Req0)
end,
Req = cowboy_req:reply(200, Req0),
{ok, Req, Opts};
do(<<"reply2">>, Req0, Opts) ->
Req = case cowboy_req:binding(arg, Req0) of
<<"binary">> ->

View file

@ -114,6 +114,30 @@ do_get_body(Path, Config) ->
do_get_body(Path, Headers, Config) ->
do_body("GET", Path, Headers, Config).
do_get_inform(Path, Config) ->
ConnPid = gun_open(Config),
Ref = gun:get(ConnPid, Path, [{<<"accept-encoding">>, <<"gzip">>}]),
case gun:await(ConnPid, Ref) of
{response, _, RespStatus, RespHeaders} ->
%% We don't care about the body.
gun:close(ConnPid),
{RespStatus, RespHeaders};
{inform, InfoStatus, InfoHeaders} ->
{response, IsFin, RespStatus, RespHeaders}
= case gun:await(ConnPid, Ref) of
{inform, InfoStatus, InfoHeaders} ->
gun:await(ConnPid, Ref);
Response ->
Response
end,
{ok, RespBody} = case IsFin of
nofin -> gun:await_body(ConnPid, Ref);
fin -> {ok, <<>>}
end,
gun:close(ConnPid),
{InfoStatus, InfoHeaders, RespStatus, RespHeaders, do_decode(RespHeaders, RespBody)}
end.
do_decode(Headers, Body) ->
case lists:keyfind(<<"content-encoding">>, 1, Headers) of
{_, <<"gzip">>} -> zlib:gunzip(Body);
@ -703,6 +727,23 @@ delete_resp_header(Config) ->
false = lists:keymember(<<"content-type">>, 1, Headers),
ok.
inform2(Config) ->
doc("Informational response(s) without headers, followed by the real response."),
{102, [], 200, _, _} = do_get_inform("/resp/inform2/102", Config),
{102, [], 200, _, _} = do_get_inform("/resp/inform2/binary", Config),
{500, _} = do_get_inform("/resp/inform2/error", Config),
{102, [], 200, _, _} = do_get_inform("/resp/inform2/twice", Config),
ok.
inform3(Config) ->
doc("Informational response(s) with headers, followed by the real response."),
Headers = [{<<"ext-header">>, <<"ext-value">>}],
{102, Headers, 200, _, _} = do_get_inform("/resp/inform3/102", Config),
{102, Headers, 200, _, _} = do_get_inform("/resp/inform3/binary", Config),
{500, _} = do_get_inform("/resp/inform3/error", Config),
{102, Headers, 200, _, _} = do_get_inform("/resp/inform3/twice", Config),
ok.
reply2(Config) ->
doc("Response with default headers and no body."),
{200, _, _} = do_get("/resp/reply2/200", Config),