mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Add cowboy_req:inform/2,3
User code can now send as many 1xx responses as necessary.
This commit is contained in:
parent
f4331f7c16
commit
f3d6b05b86
11 changed files with 206 additions and 1 deletions
|
@ -262,6 +262,29 @@ Req = cowboy_req:reply(200, #{
|
||||||
// example would be automatic concatenation of CSS or JS
|
// example would be automatic concatenation of CSS or JS
|
||||||
// files.
|
// 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
|
=== Push
|
||||||
|
|
||||||
The HTTP/2 protocol introduced the ability to push resources
|
The HTTP/2 protocol introduced the ability to push resources
|
||||||
|
|
|
@ -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: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: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: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: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_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
|
* link:man:cowboy_req:stream_body(3)[cowboy_req:stream_body(3)] - Stream the response body
|
||||||
|
|
83
doc/src/manual/cowboy_req.inform.asciidoc
Normal file
83
doc/src/manual/cowboy_req.inform.asciidoc
Normal 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)]
|
|
@ -94,5 +94,6 @@ cowboy_req:push("/static/style.css", #{
|
||||||
== See also
|
== See also
|
||||||
|
|
||||||
link:man:cowboy_req(3)[cowboy_req(3)],
|
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:reply(3)[cowboy_req:reply(3)],
|
||||||
link:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)]
|
link:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)]
|
||||||
|
|
|
@ -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_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_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: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:stream_reply(3)[cowboy_req:stream_reply(3)],
|
||||||
link:man:cowboy_req:push(3)[cowboy_req:push(3)]
|
link:man:cowboy_req:push(3)[cowboy_req:push(3)]
|
||||||
|
|
|
@ -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_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_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_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:reply(3)[cowboy_req:reply(3)],
|
||||||
link:man:cowboy_req:stream_body(3)[cowboy_req:stream_body(3)],
|
link:man:cowboy_req:stream_body(3)[cowboy_req:stream_body(3)],
|
||||||
link:man:cowboy_req:push(3)[cowboy_req:push(3)]
|
link:man:cowboy_req:push(3)[cowboy_req:push(3)]
|
||||||
|
|
|
@ -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, [{response, StatusCode, Headers, Body}|Tail]);
|
||||||
commands(State, Stream, [{error_response, _, _, _}|Tail]) ->
|
commands(State, Stream, [{error_response, _, _, _}|Tail]) ->
|
||||||
commands(State, Stream, 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.
|
%% Send response headers.
|
||||||
%%
|
%%
|
||||||
%% @todo Kill the stream if it sent a response when one has already been sent.
|
%% @todo Kill the stream if it sent a response when one has already been sent.
|
||||||
|
|
|
@ -71,6 +71,8 @@
|
||||||
-export([set_resp_body/2]).
|
-export([set_resp_body/2]).
|
||||||
%% @todo set_resp_body/3 with a ContentType or even Headers argument, to set content headers.
|
%% @todo set_resp_body/3 with a ContentType or even Headers argument, to set content headers.
|
||||||
-export([has_resp_body/1]).
|
-export([has_resp_body/1]).
|
||||||
|
-export([inform/2]).
|
||||||
|
-export([inform/3]).
|
||||||
-export([reply/2]).
|
-export([reply/2]).
|
||||||
-export([reply/3]).
|
-export([reply/3]).
|
||||||
-export([reply/4]).
|
-export([reply/4]).
|
||||||
|
@ -685,6 +687,18 @@ has_resp_body(_) ->
|
||||||
delete_resp_header(Name, Req=#{resp_headers := RespHeaders}) ->
|
delete_resp_header(Name, Req=#{resp_headers := RespHeaders}) ->
|
||||||
Req#{resp_headers => maps:remove(Name, 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().
|
-spec reply(cowboy:http_status(), Req) -> Req when Req::req().
|
||||||
reply(Status, Req) ->
|
reply(Status, 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)
|
-spec reply(cowboy:http_status(), cowboy:http_headers(), resp_body(), Req)
|
||||||
-> Req when Req::req().
|
-> Req when Req::req().
|
||||||
reply(_, _, _, #{has_sent_resp := _}) ->
|
reply(_, _, _, #{has_sent_resp := _}) ->
|
||||||
error(function_clause);
|
error(function_clause); %% @todo Better error message.
|
||||||
reply(Status, Headers, {sendfile, _, 0, _}, Req)
|
reply(Status, Headers, {sendfile, _, 0, _}, Req)
|
||||||
when is_integer(Status); is_binary(Status) ->
|
when is_integer(Status); is_binary(Status) ->
|
||||||
do_reply(Status, Headers#{
|
do_reply(Status, Headers#{
|
||||||
|
|
|
@ -119,6 +119,8 @@ info(_StreamID, {read_body_timeout, Ref}, State=#state{pid=Pid, read_body_ref=Re
|
||||||
info(_StreamID, {read_body_timeout, _}, State) ->
|
info(_StreamID, {read_body_timeout, _}, State) ->
|
||||||
{[], State};
|
{[], State};
|
||||||
%% Response.
|
%% Response.
|
||||||
|
info(_StreamID, Inform = {inform, _, _}, State) ->
|
||||||
|
{[Inform], State};
|
||||||
info(_StreamID, Response = {response, _, _, _}, State) ->
|
info(_StreamID, Response = {response, _, _, _}, State) ->
|
||||||
{[Response], State};
|
{[Response], State};
|
||||||
info(_StreamID, Headers = {headers, _, _}, State) ->
|
info(_StreamID, Headers = {headers, _, _}, State) ->
|
||||||
|
|
|
@ -100,6 +100,37 @@ do(<<"delete_resp_header">>, Req0, Opts) ->
|
||||||
Req = cowboy_req:delete_resp_header(<<"content-type">>, Req1),
|
Req = cowboy_req:delete_resp_header(<<"content-type">>, Req1),
|
||||||
false = cowboy_req:has_resp_header(<<"content-type">>, Req),
|
false = cowboy_req:has_resp_header(<<"content-type">>, Req),
|
||||||
{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
|
{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) ->
|
do(<<"reply2">>, Req0, Opts) ->
|
||||||
Req = case cowboy_req:binding(arg, Req0) of
|
Req = case cowboy_req:binding(arg, Req0) of
|
||||||
<<"binary">> ->
|
<<"binary">> ->
|
||||||
|
|
|
@ -114,6 +114,30 @@ do_get_body(Path, Config) ->
|
||||||
do_get_body(Path, Headers, Config) ->
|
do_get_body(Path, Headers, Config) ->
|
||||||
do_body("GET", 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) ->
|
do_decode(Headers, Body) ->
|
||||||
case lists:keyfind(<<"content-encoding">>, 1, Headers) of
|
case lists:keyfind(<<"content-encoding">>, 1, Headers) of
|
||||||
{_, <<"gzip">>} -> zlib:gunzip(Body);
|
{_, <<"gzip">>} -> zlib:gunzip(Body);
|
||||||
|
@ -703,6 +727,23 @@ delete_resp_header(Config) ->
|
||||||
false = lists:keymember(<<"content-type">>, 1, Headers),
|
false = lists:keymember(<<"content-type">>, 1, Headers),
|
||||||
ok.
|
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) ->
|
reply2(Config) ->
|
||||||
doc("Response with default headers and no body."),
|
doc("Response with default headers and no body."),
|
||||||
{200, _, _} = do_get("/resp/reply2/200", Config),
|
{200, _, _} = do_get("/resp/reply2/200", Config),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue