mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 20:30:23 +00:00
Update the guide chapter for responses
This commit is contained in:
parent
bae10829ba
commit
aa617f2330
1 changed files with 246 additions and 129 deletions
|
@ -1,201 +1,318 @@
|
|||
[[resp]]
|
||||
== Sending a response
|
||||
|
||||
The Req object also allows you to send a response.
|
||||
The response must be sent using the Req object.
|
||||
|
||||
You can only send one response. Any other attempt will
|
||||
trigger a crash. The response may be sent in one go or
|
||||
with its body streamed by chunks of arbitrary size.
|
||||
Cowboy provides two different ways of sending responses:
|
||||
either directly or by streaming the body. Response headers
|
||||
and body may be set in advance. The response is sent as
|
||||
soon as one of the reply or stream reply function is
|
||||
called.
|
||||
|
||||
You can also set headers or the response body in advance
|
||||
and Cowboy will use them when you finally do reply.
|
||||
Cowboy also provides a simplified interface for sending
|
||||
files. It can also send only specific parts of a file.
|
||||
|
||||
While only one response is allowed for every request,
|
||||
HTTP/2 introduced a mechanism that allows the server
|
||||
to push additional resources related to the response.
|
||||
This chapter also describes how this feature works in
|
||||
Cowboy.
|
||||
|
||||
=== Reply
|
||||
|
||||
You can send a reply with no particular headers or body.
|
||||
Cowboy will make sure to send the mandatory headers with
|
||||
the response.
|
||||
Cowboy provides three functions for sending the entire reply,
|
||||
depending on whether you need to set headers and body. In all
|
||||
cases, Cowboy will add any headers required by the protocol
|
||||
(for example the date header will always be sent).
|
||||
|
||||
When you need to set only the status code,
|
||||
use `cowboy_req:reply/2`:
|
||||
|
||||
[source,erlang]
|
||||
Req2 = cowboy_req:reply(200, Req).
|
||||
Req = cowboy_req:reply(200, Req0).
|
||||
|
||||
You can define headers to be sent with the response. Note
|
||||
that header names must be lowercase. Again, Cowboy will
|
||||
make sure to send the mandatory headers with the response.
|
||||
When you need to set response headers at the same time,
|
||||
use `cowboy_req:reply/3`:
|
||||
|
||||
[source,erlang]
|
||||
----
|
||||
Req2 = cowboy_req:reply(303, [
|
||||
{<<"location">>, <<"http://ninenines.eu">>}
|
||||
], Req).
|
||||
Req = cowboy_req:reply(303, #{
|
||||
<<"location">> => <<"http://ninenines.eu">>
|
||||
}, Req0).
|
||||
----
|
||||
|
||||
You can override headers that Cowboy would send otherwise.
|
||||
Any header set by the user will be used over the ones set
|
||||
by Cowboy. For example, you can advertise yourself as a
|
||||
different server.
|
||||
Note that the header name must always be a lowercase
|
||||
binary.
|
||||
|
||||
When you also need to set the response body,
|
||||
use `cowboy_req:reply/4`:
|
||||
|
||||
[source,erlang]
|
||||
----
|
||||
Req2 = cowboy_req:reply(200, [
|
||||
{<<"server">>, <<"yaws">>}
|
||||
], Req).
|
||||
Req = cowboy_req:reply(200, #{
|
||||
<<"content-type">> => <<"text/plain">>
|
||||
}, "Hello world!", Req0).
|
||||
----
|
||||
|
||||
We also saw earlier how to force close the connection by
|
||||
overriding the connection header.
|
||||
You should always set the content-type header when the
|
||||
response has a body. There is however no need to set
|
||||
the content-length header; Cowboy does it automatically.
|
||||
|
||||
Finally, you can also send a body with the response. Cowboy
|
||||
will automatically set the content-length header if you do.
|
||||
We recommend that you set the content-type header so the
|
||||
client may know how to read the body.
|
||||
The response body and the header values must be either
|
||||
a binary or an iolist. An iolist is a list containing
|
||||
binaries, characters, strings or other iolists. This
|
||||
allows you to build a response from different parts
|
||||
without having to do any concatenation:
|
||||
|
||||
[source,erlang]
|
||||
----
|
||||
Req2 = cowboy_req:reply(200, [
|
||||
{<<"content-type">>, <<"text/plain">>}
|
||||
], "Hello world!", Req).
|
||||
Title = "Hello world!",
|
||||
Body = <<"Hats off!">>,
|
||||
Req = cowboy_req:reply(200, #{
|
||||
<<"content-type">> => <<"text/html">>
|
||||
}, ["<html><head><title>", Title, "</title></head>",
|
||||
"<body><p>", Body, "</p></body></html>"], Req0).
|
||||
----
|
||||
|
||||
Here is the same example but sending HTML this time.
|
||||
This method of building responses is more efficient than
|
||||
concatenating. Behind the scenes, each element of the list
|
||||
is simply a pointer, and those pointers are used directly
|
||||
when writing to the socket.
|
||||
|
||||
=== Stream reply
|
||||
|
||||
Cowboy provides two functions for initiating a response,
|
||||
and an additional function for streaming the response body.
|
||||
Cowboy will add any required headers to the response.
|
||||
|
||||
// @todo For HTTP/1.1 Cowboy should probably not use chunked transfer-encoding if the content-length is set.
|
||||
|
||||
When you need to set only the status code,
|
||||
use `cowboy_req:stream_reply/2`:
|
||||
|
||||
[source,erlang]
|
||||
----
|
||||
Req2 = cowboy_req:reply(200, [
|
||||
{<<"content-type">>, <<"text/html">>}
|
||||
], "<html><head>Hello world!</head><body><p>Hats off!</p></body></html>", Req).
|
||||
Req = cowboy_req:stream_reply(200, Req0),
|
||||
|
||||
cowboy_req:stream_body("Hello...", nofin, Req),
|
||||
cowboy_req:stream_body("chunked...", nofin, Req),
|
||||
cowboy_req:stream_body("world!!", fin, Req).
|
||||
----
|
||||
|
||||
Note that the reply is sent immediately.
|
||||
The second argument to `cowboy_req:stream_body/3` indicates
|
||||
whether this data terminates the body. Use `fin` for the
|
||||
final flag, and `nofin` otherwise.
|
||||
|
||||
=== Chunked reply
|
||||
|
||||
You can also stream the response body. First, you need to
|
||||
initiate the reply by sending the response status code.
|
||||
Then you can send the body in chunks of arbitrary size.
|
||||
|
||||
[source,erlang]
|
||||
Req2 = cowboy_req:chunked_reply(200, Req),
|
||||
cowboy_req:chunk("Hello...", Req2),
|
||||
cowboy_req:chunk("chunked...", Req2),
|
||||
cowboy_req:chunk("world!!", Req2).
|
||||
|
||||
You should make sure to match on `ok` as an error may be
|
||||
returned.
|
||||
|
||||
While it is possible to send a chunked response without
|
||||
a content-type header, it is still recommended. You can
|
||||
set this header or any other just like for normal replies.
|
||||
This snippet does not set a content-type header. This is
|
||||
not recommended. All responses with a body should have
|
||||
a content-type. The header can be set beforehand, or
|
||||
using the `cowboy_req:stream_reply/3`:
|
||||
|
||||
[source,erlang]
|
||||
----
|
||||
Req2 = cowboy_req:chunked_reply(200, [
|
||||
{<<"content-type">>, <<"text/html">>}
|
||||
], Req),
|
||||
cowboy_req:chunk("<html><head>Hello world!</head>", Req2),
|
||||
cowboy_req:chunk("<body><p>Hats off!</p></body></html>", Req2).
|
||||
Req = cowboy_req:stream_reply(200, #{
|
||||
<<"content-type">> => <<"text/html">>
|
||||
}, Req0),
|
||||
|
||||
cowboy_req:stream_body("<html><head>Hello world!</head>", nofin, Req),
|
||||
cowboy_req:stream_body("<body><p>Hats off!</p></body></html>", fin, Req).
|
||||
----
|
||||
|
||||
Note that the reply and each chunk following it are sent
|
||||
immediately.
|
||||
HTTP provides a few different ways to stream response bodies.
|
||||
Cowboy will select the most appropriate one based on the HTTP
|
||||
version and the request and response headers.
|
||||
|
||||
While not required by any means, it is recommended that you
|
||||
set the content-length header in the response if you know it
|
||||
in advance. This will ensure that the best response method
|
||||
is selected and help clients understand when the response
|
||||
is fully received.
|
||||
|
||||
// @todo Document trailers here.
|
||||
|
||||
=== Preset response headers
|
||||
|
||||
You can define response headers in advance. They will be
|
||||
merged into the headers given in the reply call. Headers
|
||||
in the reply call override preset response headers which
|
||||
override the default Cowboy headers.
|
||||
Cowboy provides functions to set response headers without
|
||||
immediately sending them. They are stored in the Req object
|
||||
and sent as part of the response when a reply function is
|
||||
called.
|
||||
|
||||
To set response headers:
|
||||
|
||||
[source,erlang]
|
||||
Req2 = cowboy_req:set_resp_header(<<"allow">>, "GET", Req).
|
||||
Req = cowboy_req:set_resp_header(<<"allow">>, "GET", Req0).
|
||||
|
||||
You can check if a response header has already been set.
|
||||
This will only check the response headers that you set,
|
||||
and not the ones Cowboy will add when actually sending
|
||||
the reply.
|
||||
Header names must be a lowercase binary.
|
||||
|
||||
Do not use this function for setting cookies. Refer to
|
||||
the xref:cookies[Cookies] chapter for more information.
|
||||
|
||||
To check if a response header has already been set:
|
||||
|
||||
[source,erlang]
|
||||
cowboy_req:has_resp_header(<<"allow">>, Req).
|
||||
|
||||
It will return `true` if the header is defined, and `false`
|
||||
otherwise.
|
||||
It returns `true` if the header was set, `false` otherwise.
|
||||
|
||||
Finally, you can also delete a preset response header if
|
||||
needed. If you do, it will not be sent.
|
||||
To delete a response header that was set previously:
|
||||
|
||||
[source,erlang]
|
||||
Req2 = cowboy_req:delete_resp_header(<<"allow">>, Req).
|
||||
Req = cowboy_req:delete_resp_header(<<"allow">>, Req0).
|
||||
|
||||
=== Overriding headers
|
||||
|
||||
As Cowboy provides different ways of setting response
|
||||
headers and body, clashes may occur, so it's important
|
||||
to understand what happens when a header is set twice.
|
||||
|
||||
Headers come from five different origins:
|
||||
|
||||
* Protocol-specific headers (for example HTTP/1.1's connection header)
|
||||
* Other required headers (for example the date header)
|
||||
* Preset headers
|
||||
* Headers given to the reply function
|
||||
* Set-cookie headers
|
||||
|
||||
Cowboy does not allow overriding protocol-specific headers.
|
||||
|
||||
Set-cookie headers will always be appended at the end of
|
||||
the list of headers before sending the response.
|
||||
|
||||
Headers given to the reply function will always override
|
||||
preset headers and required headers. If a header is found
|
||||
in two or three of these, then the one in the reply function
|
||||
is picked and the others are dropped.
|
||||
|
||||
Similarly, preset headers will always override required
|
||||
headers.
|
||||
|
||||
To illustrate, look at the following snippet. Cowboy by
|
||||
default sends the server header with the value "Cowboy".
|
||||
We can override it:
|
||||
|
||||
[source,erlang]
|
||||
----
|
||||
Req = cowboy_req:reply(200, #{
|
||||
<<"server">> => <<"yaws">>
|
||||
}, Req0).
|
||||
----
|
||||
|
||||
=== Preset response body
|
||||
|
||||
You can set the response body in advance. Note that this
|
||||
body will be ignored if you then choose to send a chunked
|
||||
reply, or if you send a reply with an explicit body.
|
||||
Cowboy provides functions to set the response body without
|
||||
immediately sending it. It is stored in the Req object and
|
||||
sent when the reply function is called.
|
||||
|
||||
To set the response body:
|
||||
|
||||
[source,erlang]
|
||||
Req2 = cowboy_req:set_resp_body("Hello world!", Req).
|
||||
Req = cowboy_req:set_resp_body("Hello world!", Req0).
|
||||
|
||||
You can also set a fun that will be called when it is time
|
||||
to send the body. There are three different ways of doing
|
||||
that.
|
||||
// @todo Yeah we probably should add that function that
|
||||
// also sets the content-type at the same time...
|
||||
|
||||
If you know the length of the body that needs to be sent,
|
||||
you should specify it, as it will help clients determine
|
||||
the remaining download time and allow them to inform the
|
||||
user.
|
||||
To check if a response body has already been set:
|
||||
|
||||
[source,erlang]
|
||||
----
|
||||
F = fun (Socket, Transport) ->
|
||||
Transport:send(Socket, "Hello world!")
|
||||
end,
|
||||
Req2 = cowboy_req:set_resp_body_fun(12, F, Req).
|
||||
----
|
||||
cowboy_req:has_resp_body(Req).
|
||||
|
||||
If you do not know the length of the body, you should use
|
||||
a chunked response body fun instead.
|
||||
It returns `true` if the body was set and is non-empty,
|
||||
`false` otherwise.
|
||||
|
||||
[source,erlang]
|
||||
----
|
||||
F = fun (SendChunk) ->
|
||||
Body = lists:duplicate(random:uniform(1024, $a)),
|
||||
SendChunk(Body)
|
||||
end,
|
||||
Req2 = cowboy_req:set_resp_body_fun(chunked, F, Req).
|
||||
----
|
||||
// @todo We probably should also have a function that
|
||||
// properly removes the response body, including any
|
||||
// content-* headers.
|
||||
|
||||
Finally, you can also send data on the socket directly,
|
||||
without knowing the length in advance. Cowboy may be
|
||||
forced to close the connection at the end of the response
|
||||
though depending on the protocol capabilities.
|
||||
|
||||
[source,erlang]
|
||||
----
|
||||
F = fun (Socket, Transport) ->
|
||||
Body = lists:duplicate(random:uniform(1024, $a)),
|
||||
Transport:send(Socket, Body)
|
||||
end,
|
||||
Req2 = cowboy_req:set_resp_body_fun(F, Req).
|
||||
----
|
||||
The preset response body is only sent if the reply function
|
||||
used is `cowboy_req:reply/2` or `cowboy_req:reply/3`.
|
||||
|
||||
=== Sending files
|
||||
|
||||
You can send files directly from disk without having to
|
||||
read them. Cowboy will use the `sendfile` syscall when
|
||||
possible, which means that the file is sent to the socket
|
||||
directly from the kernel, which is a lot more performant
|
||||
than doing it from userland.
|
||||
Cowboy provides a shortcut for sending files. When
|
||||
using `cowboy_req:reply/4`, or when presetting the
|
||||
response header, you can give a `sendfile` tuple to
|
||||
Cowboy:
|
||||
|
||||
Again, it is recommended to set the size of the file if it
|
||||
can be known in advance.
|
||||
[source,erlang]
|
||||
{sendfile, Offset, Length, Filename}
|
||||
|
||||
Depending on the values for `Offset` or `Length`, the
|
||||
entire file may be sent, or just a part of it.
|
||||
|
||||
The length is required even for sending the entire file.
|
||||
Cowboy sends it in the content-length header.
|
||||
|
||||
To send a file while replying:
|
||||
|
||||
[source,erlang]
|
||||
----
|
||||
F = fun (Socket, Transport) ->
|
||||
Transport:sendfile(Socket, "priv/styles.css")
|
||||
end,
|
||||
Req2 = cowboy_req:set_resp_body_fun(FileSize, F, Req).
|
||||
Req = cowboy_req:reply(200, #{
|
||||
<<"content-type">> => "image/png"
|
||||
}, {sendfile, 0, 12345, "path/to/logo.png"}, Req0).
|
||||
----
|
||||
|
||||
Please see the Ranch guide for more information about
|
||||
sending files.
|
||||
// @todo An example of presetting a file would be useful,
|
||||
// but let's wait for the function that can set the
|
||||
// content-type at the same time.
|
||||
|
||||
// @todo What about streaming many files? For example
|
||||
// it should be possible to build a tar file on the fly
|
||||
// while still using sendfile. Another example could be
|
||||
// proper support for multipart byte ranges. Yet another
|
||||
// example would be automatic concatenation of CSS or JS
|
||||
// files.
|
||||
|
||||
=== Push
|
||||
|
||||
The HTTP/2 protocol introduced the ability to push resources
|
||||
related to the one sent in the response. Cowboy provides two
|
||||
functions for that purpose: `cowboy_req:push/3,4`.
|
||||
|
||||
Push is only available for HTTP/2. Cowboy will automatically
|
||||
ignore push requests if the protocol doesn't support it.
|
||||
|
||||
The push function must be called before any of the reply
|
||||
functions. Doing otherwise will result in a crash.
|
||||
|
||||
To push a resource, you need to provide the same information
|
||||
as a client performing a request would. This includes the
|
||||
HTTP method, the URI and any necessary request headers.
|
||||
|
||||
Cowboy by default only requires you to give the path to
|
||||
the resource and the request headers. The rest of the URI
|
||||
is taken from the current request (excluding the query
|
||||
string, set to empty) and the method is GET by default.
|
||||
|
||||
The following snippet pushes a CSS file that is linked to
|
||||
in the response:
|
||||
|
||||
[source,erlang]
|
||||
----
|
||||
cowboy_req:push("/static/style.css", #{
|
||||
<<"accept">> => <<"text/css">>
|
||||
}, Req0),
|
||||
Req = cowboy_req:reply(200, #{
|
||||
<<"content-type">> => <<"text/html">>
|
||||
}, ["<html><head><title>My web page</title>",
|
||||
"<link rel='stylesheet' type='text/css' href='/static/style.css'>",
|
||||
"<body><p>Welcome to Erlang!</p></body></html>"], Req0).
|
||||
----
|
||||
|
||||
To override the method, scheme, host, port or query string,
|
||||
simply pass in a fourth argument. The following snippet
|
||||
uses a different host name:
|
||||
|
||||
[source,erlang]
|
||||
----
|
||||
cowboy_req:push("/static/style.css", #{
|
||||
<<"accept">> => <<"text/css">>
|
||||
}, #{host => <<"cdn.example.org">>}, Req),
|
||||
----
|
||||
|
||||
Pushed resources don't have to be files. As long as the push
|
||||
request is cacheable, safe and does not include a body, the
|
||||
resource can be pushed.
|
||||
|
||||
Under the hood, Cowboy handles pushed requests the same as
|
||||
normal requests: a different process is created which will
|
||||
ultimately send a response to the client.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue