mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Greatly expand on the Req object
Cut in four different chapters: request, request body, response and cookies.
This commit is contained in:
parent
eb4843a46b
commit
2b2829f585
5 changed files with 778 additions and 169 deletions
140
guide/cookies.md
Normal file
140
guide/cookies.md
Normal file
|
@ -0,0 +1,140 @@
|
|||
Using cookies
|
||||
=============
|
||||
|
||||
Cookies are a mechanism allowing applications to maintain
|
||||
state on top of the stateless HTTP protocol.
|
||||
|
||||
Cowboy provides facilities for handling cookies. It is highly
|
||||
recommended to use them instead of writing your own, as the
|
||||
implementation of cookies can vary greatly between clients.
|
||||
|
||||
Cookies are stored client-side and sent with every subsequent
|
||||
request that matches the domain and path for which they were
|
||||
stored, including requests for static files. For this reason
|
||||
they can incur a cost which must be taken in consideration.
|
||||
|
||||
Also consider that, regardless of the options used, cookies
|
||||
are not to be trusted. They may be read and modified by any
|
||||
program on the user's computer, but also by proxies. You
|
||||
should always validate cookie values before using them. Do
|
||||
not store any sensitive information in cookies either.
|
||||
|
||||
When explicitly setting the domain, the cookie will be sent
|
||||
for the domain and all subdomains from that domain. Otherwise
|
||||
the current domain will be used. The same is true for the
|
||||
path.
|
||||
|
||||
When the server sets cookies, they will only be available
|
||||
for requests that are sent after the client receives the
|
||||
response.
|
||||
|
||||
Cookies are sent in HTTP headers, therefore they must have
|
||||
text values. It is your responsibility to encode any other
|
||||
data type. Also note that cookie names are de facto case
|
||||
sensitive.
|
||||
|
||||
Cookies can be set for the client session (which generally
|
||||
means until the browser is closed), or it can be set for
|
||||
a number of seconds. Once it expires, or when the server
|
||||
says the cookie must exist for up to 0 seconds, the cookie
|
||||
is deleted by the client. To avoid this while the user
|
||||
is browsing your site, you should set the cookie for
|
||||
every request, essentially resetting the expiration time.
|
||||
|
||||
Cookies can be restricted to secure channels. This typically
|
||||
means that such a cookie will only be sent over HTTPS,
|
||||
and that it will only be available by client-side scripts
|
||||
that run from HTTPS webpages.
|
||||
|
||||
Finally, cookies can be restricted to HTTP and HTTPS requests,
|
||||
essentially disabling their access from client-side scripts.
|
||||
|
||||
Setting cookies
|
||||
---------------
|
||||
|
||||
By default, cookies you set are defined for the session.
|
||||
|
||||
``` erlang
|
||||
SessionID = generate_session_id(),
|
||||
Req2 = cowboy_req:set_resp_cookie(<<"sessionid">>, SessionID, [], Req).
|
||||
```
|
||||
|
||||
You can also make them expire at a specific point in the
|
||||
future.
|
||||
|
||||
``` erlang
|
||||
SessionID = generate_session_id(),
|
||||
Req2 = cowboy_req:set_resp_cookie(<<"sessionid">>, SessionID, [
|
||||
{max_age, 3600}
|
||||
], Req).
|
||||
```
|
||||
|
||||
You can delete cookies that have already been set. The value
|
||||
is ignored.
|
||||
|
||||
``` erlang
|
||||
Req2 = cowboy_req:set_resp_cookie(<<"sessionid">>, <<>>, [
|
||||
{max_age, 0}
|
||||
], Req).
|
||||
```
|
||||
|
||||
You can restrict them to a specific domain and path.
|
||||
For example, the following cookie will be set for the domain
|
||||
`my.example.org` and all its subdomains, but only on the path
|
||||
`/account` and all its subdirectories.
|
||||
|
||||
``` erlang
|
||||
Req2 = cowboy_req:set_resp_cookie(<<"inaccount">>, <<"1">>, [
|
||||
{domain, "my.example.org"},
|
||||
{path, "/account"}
|
||||
], Req).
|
||||
```
|
||||
|
||||
You can restrict the cookie to secure channels, typically HTTPS.
|
||||
|
||||
``` erlang
|
||||
SessionID = generate_session_id(),
|
||||
Req2 = cowboy_req:set_resp_cookie(<<"sessionid">>, SessionID, [
|
||||
{secure, true}
|
||||
], Req).
|
||||
```
|
||||
|
||||
You can restrict the cookie to client-server communication
|
||||
only. Such a cookie will not be available to client-side scripts.
|
||||
|
||||
``` erlang
|
||||
SessionID = generate_session_id(),
|
||||
Req2 = cowboy_req:set_resp_cookie(<<"sessionid">>, SessionID, [
|
||||
{http_only, true}
|
||||
], Req).
|
||||
```
|
||||
|
||||
Cookies may also be set client-side, for example using
|
||||
Javascript.
|
||||
|
||||
Reading cookies
|
||||
---------------
|
||||
|
||||
As we said, the client sends cookies with every request.
|
||||
But unlike the server, the client only sends the cookie
|
||||
name and value.
|
||||
|
||||
You can read the value of a cookie.
|
||||
|
||||
``` erlang
|
||||
{CookieVal, Req2} = cowboy_req:cookie(<<"lang">>, Req).
|
||||
```
|
||||
|
||||
You can also get a default value returned when the cookie
|
||||
isn't set.
|
||||
|
||||
``` erlang
|
||||
{CookieVal, Req2} = cowboy_req:cookie(<<"lang">>, Req, <<"fr">>).
|
||||
```
|
||||
|
||||
And you can obtain all cookies at once as a list of
|
||||
key/value tuples.
|
||||
|
||||
``` erlang
|
||||
{AllCookies, Req2} = cowboy_req:cookies(Req).
|
||||
```
|
430
guide/req.md
430
guide/req.md
|
@ -1,204 +1,298 @@
|
|||
Request object
|
||||
The Req object
|
||||
==============
|
||||
|
||||
Purpose
|
||||
-------
|
||||
The Req object is this variable that you will use to obtain
|
||||
information about a request, read the body of the request
|
||||
and send a response.
|
||||
|
||||
The request object is a special variable that can be used
|
||||
to interact with a request, extracting information from it
|
||||
or modifying it, and sending a response.
|
||||
A special variable
|
||||
------------------
|
||||
|
||||
It's a special variable because it contains both immutable
|
||||
and mutable state. This means that some operations performed
|
||||
on the request object will always return the same result,
|
||||
while others will not. For example, obtaining request headers
|
||||
can be repeated safely. Obtaining the request body can only
|
||||
be done once, as it is read directly from the socket.
|
||||
While we call it an "object", it is not an object in the
|
||||
OOP sense of the term. In fact it is completely opaque
|
||||
to you and the only way you can perform operations using
|
||||
it is by calling the functions from the `cowboy_req`
|
||||
module.
|
||||
|
||||
With few exceptions, all calls to the `cowboy_req` module
|
||||
will return an updated request object. You MUST use the new
|
||||
request object instead of the old one for all subsequent
|
||||
operations.
|
||||
Almost all the calls to the `cowboy_req` module will
|
||||
return an updated request object. Just like you would
|
||||
keep the updated `State` variable in a gen_server,
|
||||
you MUST keep the updated `Req` variable in a Cowboy
|
||||
handler. Cowboy will use this object to know whether
|
||||
a response has been sent when the handler has finished
|
||||
executing.
|
||||
|
||||
The Req object allows accessing both immutable and
|
||||
mutable state. This means that calling some of the
|
||||
functions twice will not produce the same result.
|
||||
For example, when streaming the request body, the
|
||||
function will return the body by chunks, one at a
|
||||
time, until there is none left.
|
||||
|
||||
It also caches the result of operations performed
|
||||
on the immutable state. That means that some calls
|
||||
will give a result much faster when called many times.
|
||||
|
||||
Overview of the cowboy_req interface
|
||||
------------------------------------
|
||||
|
||||
The `cowboy_req` interface is divided in four groups
|
||||
of functions, each having a well defined return type
|
||||
signature common to the entire group.
|
||||
|
||||
The first group, access functions, will always return
|
||||
`{Value, Req}`. The group includes all the following
|
||||
functions: `binding/{2,3}`, `bindings/1`, `body_length/1`,
|
||||
`cookie/{2,3}`, `cookies/1`, `header/{2,3}`, `headers/1`,
|
||||
`host/1`, `host_info/1`, `host_url/1`, `meta/{2,3}`,
|
||||
`method/1`, `path/1`, `path_info/1`, `peer/1`, `port/1`,
|
||||
`qs/1`, `qs_val/{2,3}`, `qs_vals/1`, `url/1`, `version/1`.
|
||||
|
||||
The second group, question functions, will always return
|
||||
a `boolean()`. The group includes the following three
|
||||
functions: `has_body/1`, `has_resp_body/1`, `has_resp_header/2`.
|
||||
|
||||
The third group contains the functions that manipulate
|
||||
the socket or perform operations that may legitimately fail.
|
||||
They may return `{Result, Req}`, `{Result, Value, Req}`
|
||||
or `{error, atom()}`. This includes the following functions:
|
||||
`body/{1,2}`, `body_qs/{1,2}`, `chunked_reply/{2,3}`,
|
||||
`init_stream/4`, `parse_header/{2,3}`, `reply/{2,3,4}`,
|
||||
`skip_body/1`, `stream_body/{1,2}`. Finally, the group
|
||||
also includes the `chunk/2` function which always returns
|
||||
`ok`.
|
||||
|
||||
The final group modifies the Req object, so it always return
|
||||
a new `Req`. It includes the following functions: `compact/1`,
|
||||
`delete_resp_header/2`, `set_meta/3`, `set_resp_body/2`,
|
||||
`set_resp_body_fun/{2,3}`, `set_resp_cookie/4`, `set_resp_header/3`.
|
||||
|
||||
This chapter covers most of the first group, plus a few other
|
||||
functions. The next few chapters cover cookies handling, reading
|
||||
the request body and sending a response.
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
Cowboy allows you to retrieve a lot of information about
|
||||
the request. All these calls return a `{Value, Req}` tuple,
|
||||
with `Value` the requested value and `Req` the updated
|
||||
request object.
|
||||
When a client performs a request, it first sends a few required
|
||||
values. They are sent differently depending on the protocol
|
||||
being used, but the intent is the same. They indicate to the
|
||||
server the type of action it wants to do and how to locate
|
||||
the resource to perform it on.
|
||||
|
||||
The following access functions are defined in `cowboy_req`:
|
||||
The method identifies the action. Standard methods include
|
||||
GET, HEAD, OPTIONS, PATCH, POST, PUT, DELETE. Method names
|
||||
are case sensitive.
|
||||
|
||||
* `method/1`: the request method (`<<"GET">>`, `<<"POST">>`...)
|
||||
* `version/1`: the HTTP version (`'HTTP/1.0'` or `'HTTP/1.1'`)
|
||||
* `peer/1`: the peer address and port number
|
||||
* `host/1`: the hostname requested
|
||||
* `host_info/1`: the result of the `[...]` match on the host
|
||||
* `port/1`: the port number used for the connection
|
||||
* `path/1`: the path requested
|
||||
* `path_info/1`: the result of the `[...]` match on the path
|
||||
* `qs/1`: the entire query string unmodified
|
||||
* `qs_val/{2,3}`: the value for the requested query string key
|
||||
* `qs_vals/1`: all key/values found in the query string
|
||||
* `host_url/1`: the requested URL without the path and query string
|
||||
* `url/1`: the requested URL
|
||||
* `binding/{2,3}`: the value for the requested binding found during routing
|
||||
* `bindings/1`: all key/values found during routing
|
||||
* `header/{2,3}`: the value for the requested header name
|
||||
* `headers/1`: all headers name/value
|
||||
* `cookie/{2,3}`: the value for the requested cookie name
|
||||
* `cookies/1`: all cookies name/value
|
||||
* `meta/{2,3}`: the meta information for the requested key
|
||||
``` erlang
|
||||
{Method, Req2} = cowboy_req:method(Req).
|
||||
```
|
||||
|
||||
All the functions above that can take two or three arguments
|
||||
take an optional third argument for the default value if
|
||||
none is found. Otherwise it will return `undefined`.
|
||||
The host, port and path parts of the URL identify the resource
|
||||
being accessed. The host and port information may not be
|
||||
available if the client uses HTTP/1.0.
|
||||
|
||||
In addition, Cowboy allows you to parse headers using the
|
||||
`parse_header/{2,3}` function, which takes a header name
|
||||
as lowercase binary, the request object, and an optional
|
||||
default value. It returns `{ok, ParsedValue, Req}` if it
|
||||
could be parsed, `{undefined, RawValue, Req}` if Cowboy
|
||||
doesn't know this header, and `{error, badarg}` if Cowboy
|
||||
encountered an error while trying to parse it.
|
||||
``` erlang
|
||||
{Host, Req2} = cowboy_req:host(Req),
|
||||
{Port, Req3} = cowboy_req:port(Req2),
|
||||
{Path, Req4} = cowboy_req:path(Req3).
|
||||
```
|
||||
|
||||
Finally, Cowboy allows you to set request meta information
|
||||
using the `set_meta/3` function, which takes a name, a value
|
||||
and the request object and returns the latter modified.
|
||||
The version used by the client can of course also be obtained.
|
||||
|
||||
Request body
|
||||
------------
|
||||
``` erlang
|
||||
{Version, Req2} = cowboy_req:version(Req).
|
||||
```
|
||||
|
||||
Cowboy will not read the request body until you ask it to.
|
||||
If you don't, then Cowboy will simply discard it. It will
|
||||
not take extra memory space until you start reading it.
|
||||
Do note however that clients claiming to implement one version
|
||||
of the protocol does not mean they implement it fully, or even
|
||||
properly.
|
||||
|
||||
Cowboy has a few utility functions for dealing with the
|
||||
request body.
|
||||
|
||||
The function `has_body/1` will return whether the request
|
||||
contains a body. Note that some clients may not send the
|
||||
right headers while still sending a body, but as Cowboy has
|
||||
no way of detecting it this function will return `false`.
|
||||
|
||||
The function `body_length/1` retrieves the size of the
|
||||
request body. If the body is compressed, the value returned
|
||||
here is the compressed size. If a `Transfer-Encoding` header
|
||||
was passed in the request, then Cowboy will return a size
|
||||
of `undefined`, as it has no way of knowing it.
|
||||
|
||||
If you know the request contains a body, and that it is
|
||||
within 8MB (for `body/1`) or 16KB (for `body_qs/1`) bytes,
|
||||
then you can read it directly with either `body/1` or `body_qs/1`.
|
||||
If you want to override the default size limits of `body/1`
|
||||
or `body_qs/1`, you can pass the maximum body length byte
|
||||
size as first parameter to `body/2` and `body_qs/2` or pass
|
||||
atom `infinity` to ignore size limits.
|
||||
|
||||
If the request contains bigger body than allowed default sizes
|
||||
or supplied maximum body length, `body/1`, `body/2`, `body_qs/1`
|
||||
and `body_qs/2` will return `{error, badlength}`. If the request
|
||||
contains chunked body, `body/1`, `body/2`, `body_qs/1`
|
||||
and `body_qs/2` will return `{error, chunked}`.
|
||||
If you get either of the above two errors, you will want to
|
||||
handle the body of the request using `stream_body/1`,
|
||||
`stream_body/2` and `skip_body/1`, with the streaming process
|
||||
optionally initialized using `init_stream/4`.
|
||||
|
||||
Multipart request body
|
||||
----------------------
|
||||
|
||||
Cowboy provides facilities for dealing with multipart bodies.
|
||||
They are typically used for uploading files. You can use two
|
||||
functions to process these bodies, `multipart_data/1` and
|
||||
`multipart_skip/1`.
|
||||
|
||||
Response
|
||||
Bindings
|
||||
--------
|
||||
|
||||
You can send a response by calling the `reply/{2,3,4}` function.
|
||||
It takes the status code for the response (usually `200`),
|
||||
an optional list of headers, an optional body and the request
|
||||
object.
|
||||
After routing the request, bindings are available. Bindings
|
||||
are these parts of the host or path that you chose to extract
|
||||
when defining the routes of your application.
|
||||
|
||||
The following snippet sends a simple response with no headers
|
||||
specified but with a body.
|
||||
You can fetch a single binding. The value will be `undefined`
|
||||
if the binding doesn't exist.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:reply(200, [], "Hello world!", Req).
|
||||
{Binding, Req2} = cowboy_req:binding(my_binding, Req).
|
||||
```
|
||||
|
||||
If this is the only line in your handler then make sure to return
|
||||
the `Req2` variable to Cowboy so it can know you replied.
|
||||
|
||||
If you want to send HTML you'll need to specify the `Content-Type`
|
||||
header so the client can properly interpret it.
|
||||
If you need a different value when the binding doesn't exist,
|
||||
you can change the default.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:reply(200,
|
||||
[{<<"content-type">>, <<"text/html">>}],
|
||||
"<html><head>Hello world!</head><body><p>Hats off!</p></body></html>",
|
||||
Req).
|
||||
{Binding, Req2} = cowboy_req:binding(my_binding, Req, 42).
|
||||
```
|
||||
|
||||
You only need to make sure to follow conventions and to use a
|
||||
lowercase header name.
|
||||
|
||||
Chunked response
|
||||
----------------
|
||||
|
||||
You can also send chunked responses using `chunked_reply/{2,3}`.
|
||||
Chunked responses allow you to send the body in chunks of various
|
||||
sizes. It is the recommended way of performing streaming if the
|
||||
client supports it.
|
||||
|
||||
You must first initiate the response by calling the aforementioned
|
||||
function, then you can call `chunk/2` as many times as needed.
|
||||
The following snippet sends a body in three chunks.
|
||||
You can also obtain all bindings in one call. They will be
|
||||
returned as a list of key/value tuples.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:chunked_reply(200, Req),
|
||||
ok = cowboy_req:chunk("Hello...", Req2),
|
||||
ok = cowboy_req:chunk("chunked...", Req2),
|
||||
ok = cowboy_req:chunk("world!!", Req2).
|
||||
{AllBindings, Req2} = cowboy_req:bindings(Req).
|
||||
```
|
||||
|
||||
As you can see the call to `chunk/2` does not return a modified
|
||||
request object. It may return an error, however, so you should
|
||||
make sure that you match the return value on `ok`.
|
||||
|
||||
Response preconfiguration
|
||||
-------------------------
|
||||
|
||||
Cowboy allows you to set response cookies, headers or body
|
||||
in advance without having to send the response at the same time.
|
||||
Then, when you decide to send it, all these informations will be
|
||||
built into the resulting response.
|
||||
|
||||
Some of the functions available for this purpose also give you
|
||||
additional functionality, like `set_resp_cookie/4` which will build
|
||||
the appropriate `Set-Cookie` header, or `set_resp_body_fun/{2,3}`
|
||||
which allows you to stream the response body.
|
||||
|
||||
Note that any value given directly to `reply/{2,3,4}` will
|
||||
override all preset values. This means for example that you
|
||||
can set a default body and then override it when you decide
|
||||
to send a reply.
|
||||
|
||||
Closing the connection
|
||||
----------------------
|
||||
|
||||
HTTP/1.1 keep-alive allows clients to send more than one request
|
||||
on the same connection. This can be useful for speeding up the
|
||||
loading of webpages, but is not required. You can tell Cowboy
|
||||
explicitly that you want to close the connection by setting the
|
||||
`Connection` header to `close`.
|
||||
If you used `...` at the beginning of the route's pattern
|
||||
for the host, you can retrieve the matched part of the host.
|
||||
The value will be `undefined` otherwise.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:reply(200,
|
||||
[{<<"connection">>, <<"close">>}],
|
||||
Req).
|
||||
{HostInfo, Req2} = cowboy_req:host_info(Req).
|
||||
```
|
||||
|
||||
Similarly, if you used `...` at the end of the route's
|
||||
pattern for the path, you can retrieve the matched part,
|
||||
or get `undefined` otherwise.
|
||||
|
||||
``` erlang
|
||||
{PathInfo, Req2} = cowboy_req:path_info(Req).
|
||||
```
|
||||
|
||||
Query string
|
||||
------------
|
||||
|
||||
The query string can be obtained directly.
|
||||
|
||||
``` erlang
|
||||
{Qs, Req2} = cowboy_req:qs(Req).
|
||||
```
|
||||
|
||||
You can also requests only one value.
|
||||
|
||||
``` erlang
|
||||
{QsVal, Req2} = cowboy_req:qs_val(<<"lang">>, Req).
|
||||
```
|
||||
|
||||
If that value is optional, you can define a default to simplify
|
||||
your task.
|
||||
|
||||
``` erlang
|
||||
{QsVal, Req2} = cowboy_req:qs_val(<<"lang">>, Req, <<"en">>).
|
||||
```
|
||||
|
||||
Finally, you can obtain all query string values.
|
||||
|
||||
``` erlang
|
||||
{AllValues, Req2} = cowboy_req:qs_vals(Req).
|
||||
```
|
||||
|
||||
Request URL
|
||||
-----------
|
||||
|
||||
You can reconstruct the full URL of the resource.
|
||||
|
||||
``` erlang
|
||||
{URL, Req2} = cowboy_req:url(Req).
|
||||
```
|
||||
|
||||
You can also obtain only the base of the URL, excluding the
|
||||
path and query string.
|
||||
|
||||
``` erlang
|
||||
{BaseURL, Req2} = cowboy_req:host_url(Req).
|
||||
```
|
||||
|
||||
Headers
|
||||
-------
|
||||
|
||||
Cowboy allows you to obtain the header values as string,
|
||||
or parsed into a more meaningful representation.
|
||||
|
||||
This will get the string value of a header.
|
||||
|
||||
``` erlang
|
||||
{HeaderVal, Req2} = cowboy_req:header(<<"content-type">>, Req).
|
||||
```
|
||||
|
||||
You can of course set a default in case the header is missing.
|
||||
|
||||
``` erlang
|
||||
{HeaderVal, Req2}
|
||||
= cowboy_req:header(<<"content-type">>, Req, <<"text/plain">>).
|
||||
```
|
||||
|
||||
And also obtain all headers.
|
||||
|
||||
``` erlang
|
||||
{AllHeaders, Req2} = cowboy_req:headers(Req).
|
||||
```
|
||||
|
||||
To parse the previous header, simply call `parse_header/{2,3}`
|
||||
where you would call `header/{2,3}` otherwise. Note that the
|
||||
return value changes and includes the result of the operation
|
||||
as the first element of the returned tuple. A successful parse
|
||||
returns `ok`.
|
||||
|
||||
``` erlang
|
||||
{ok, ParsedVal, Req2} = cowboy_req:parse_header(<<"content-type">>, Req).
|
||||
```
|
||||
|
||||
When Cowboy doesn't know how to parse the given header, the
|
||||
result of the operation will be `undefined` and the string value
|
||||
will be returned instead.
|
||||
|
||||
``` erlang
|
||||
{undefined, HeaderVal, Req2}
|
||||
= cowboy_req:parse_header(<<"unicorn-header">>, Req).
|
||||
```
|
||||
|
||||
When parsing fails, `{error, Reason}` is returned instead.
|
||||
|
||||
You can of course define a default value. Note that the default
|
||||
value you specify here is the parsed value you'd like to get
|
||||
by default.
|
||||
|
||||
``` erlang
|
||||
{ok, ParsedVal, Req2}
|
||||
= cowboy_req:parse_header(<<"content-type">>, Req,
|
||||
{<<"text">>, <<"plain">>, []}).
|
||||
```
|
||||
|
||||
The list of known headers and default values is defined in the
|
||||
manual. Also note that the result of parsing is cached, so
|
||||
calling this function multiple times for the same values will
|
||||
not have a significant performance impact.
|
||||
|
||||
Meta
|
||||
----
|
||||
|
||||
Cowboy will sometimes associate some meta information with
|
||||
the request. Built-in meta values are listed in the manual
|
||||
for their respective modules.
|
||||
|
||||
This will get a meta value. The returned value will be `undefined`
|
||||
if it isn't defined.
|
||||
|
||||
``` erlang
|
||||
{MetaVal, Req2} = cowboy_req:meta(websocket_version, Req).
|
||||
```
|
||||
|
||||
You can change the default value if needed.
|
||||
|
||||
``` erlang
|
||||
{MetaVal, Req2} = cowboy_req:meta(websocket_version, Req, 13).
|
||||
```
|
||||
|
||||
You can also define your own meta values. The name must be
|
||||
an `atom()`.
|
||||
|
||||
``` erlang
|
||||
Req2 = cowboy_req:set_meta(the_answer, 42, Req).
|
||||
```
|
||||
|
||||
Peer
|
||||
----
|
||||
|
||||
You can obtain the peer address and port number. This is
|
||||
not necessarily the actual IP and port of the client, but
|
||||
rather the one of the machine that connected to the server.
|
||||
|
||||
``` erlang
|
||||
{{IP, Port}, Req2} = cowboy_req:peer(Req).
|
||||
```
|
||||
|
||||
Reducing the memory footprint
|
||||
|
@ -213,3 +307,5 @@ free memory.
|
|||
``` erlang
|
||||
Req2 = cowboy_req:compact(Req).
|
||||
```
|
||||
|
||||
You will still be able to send a reply if needed.
|
||||
|
|
169
guide/req_body.md
Normal file
169
guide/req_body.md
Normal file
|
@ -0,0 +1,169 @@
|
|||
Reading the request body
|
||||
========================
|
||||
|
||||
The Req object also allows you to read the request body.
|
||||
|
||||
Because the request body can be of any size, all body
|
||||
reading operations will only work once, as Cowboy will
|
||||
not cache the result of these operations.
|
||||
|
||||
Cowboy will not attempt to read the body until you do.
|
||||
If handler execution ends without reading it, Cowboy
|
||||
will simply skip it.
|
||||
|
||||
Check for request body
|
||||
----------------------
|
||||
|
||||
You can check whether a body was sent with the request.
|
||||
|
||||
``` erlang
|
||||
cowboy_req:has_body(Req).
|
||||
```
|
||||
|
||||
It will return `true` if there is a request body, and
|
||||
`false` otherwise.
|
||||
|
||||
Note that it is generally safe to assume that a body is
|
||||
sent for `POST`, `PUT` and `PATCH` requests, without
|
||||
having to explicitly check for it.
|
||||
|
||||
Request body length
|
||||
-------------------
|
||||
|
||||
You can obtain the body length if it was sent with the
|
||||
request.
|
||||
|
||||
``` erlang
|
||||
{Length, Req2} = cowboy_req:body_length(Req).
|
||||
```
|
||||
|
||||
The value returned will be `undefined` if the length
|
||||
couldn't be figured out from the request headers. If
|
||||
there's a body but no length is given, this means that
|
||||
the chunked transfer-encoding was used. You can read
|
||||
chunked bodies by using the stream functions.
|
||||
|
||||
Reading the body
|
||||
----------------
|
||||
|
||||
If a content-length header was sent with the request,
|
||||
you can read the whole body directly.
|
||||
|
||||
``` erlang
|
||||
{ok, Body, Req2} = cowboy_req:body(Req).
|
||||
```
|
||||
|
||||
If no content-length header is available, Cowboy will
|
||||
return the `{error, chunked}` tuple. You will need to
|
||||
stream the request body instead.
|
||||
|
||||
By default, Cowboy will reject all body sizes above 8MB,
|
||||
to prevent an attacker from needlessly filling up memory.
|
||||
You can override this limit however.
|
||||
|
||||
``` erlang
|
||||
{ok, Body, Req2} = cowboy_req:body(100000000, Req).
|
||||
```
|
||||
|
||||
You can also disable it.
|
||||
|
||||
``` erlang
|
||||
{ok, Body, Req2} = cowboy_req:body(infinity, Req).
|
||||
```
|
||||
|
||||
It is recommended that you do not disable it for public
|
||||
facing websites.
|
||||
|
||||
Reading a body sent from an HTML form
|
||||
-------------------------------------
|
||||
|
||||
You can directly obtain a list of key/value pairs if the
|
||||
body was sent using the application/x-www-form-urlencoded
|
||||
content-type.
|
||||
|
||||
``` erlang
|
||||
{ok, KeyValues, Req2} = cowboy_req:body_qs(Req).
|
||||
```
|
||||
|
||||
You can then retrieve an individual value from that list.
|
||||
|
||||
``` erlang
|
||||
{_, Lang} = lists:keyfind(lang, 1, KeyValues).
|
||||
```
|
||||
|
||||
You should not attempt to match on the list as the order
|
||||
of the values is undefined.
|
||||
|
||||
By default Cowboy will reject bodies with a size above
|
||||
16KB when using this function. You can override this limit.
|
||||
|
||||
``` erlang
|
||||
{ok, KeyValues, Req2} = cowboy_req:body_qs(500000, Req).
|
||||
```
|
||||
|
||||
You can also disable it by passing the atom `infinity`,
|
||||
although it is not recommended.
|
||||
|
||||
Streaming the body
|
||||
------------------
|
||||
|
||||
You can stream the request body by chunks.
|
||||
|
||||
``` erlang
|
||||
{ok, Chunk, Req2} = cowboy_req:stream_body(Req).
|
||||
```
|
||||
|
||||
By default, Cowboy will attempt to read chunks of up to
|
||||
1MB in size. The chunks returned by this function will
|
||||
often be smaller, however. You can also change this limit.
|
||||
|
||||
``` erlang
|
||||
{ok, Chunk, Req2} = cowboy_req:stream_body(500000, Req).
|
||||
```
|
||||
|
||||
When Cowboy finishes reading the body, any subsequent call
|
||||
will return `{done, Req2}`. You can thus write a recursive
|
||||
function to read the whole body and perform an action on
|
||||
all chunks, for example printing them to the console.
|
||||
|
||||
``` erlang
|
||||
body_to_console(Req) ->
|
||||
case cowboy_req:stream_body(Req) of
|
||||
{ok, Chunk, Req2} ->
|
||||
io:format("~s", [Chunk]),
|
||||
body_to_console(Req2);
|
||||
{done, Req2} ->
|
||||
Req2
|
||||
end.
|
||||
```
|
||||
|
||||
Advanced streaming
|
||||
------------------
|
||||
|
||||
Cowboy will by default decode the chunked transfer-encoding
|
||||
if any. It will not decode any content-encoding by default.
|
||||
|
||||
Before starting to stream, you can configure the functions
|
||||
that will be used for decoding both transfer-encoding and
|
||||
content-encoding.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:init_stream(fun transfer_decode/2,
|
||||
TransferStartState, fun content_decode/1, Req).
|
||||
```
|
||||
|
||||
Note that you do not need to call this function generally,
|
||||
as Cowboy will happily initialize the stream on its own.
|
||||
|
||||
Skipping the body
|
||||
-----------------
|
||||
|
||||
If you do not need the body, or if you started streaming
|
||||
the body but do not need the rest of it, you can skip it.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:skip_body(Req).
|
||||
```
|
||||
|
||||
You do not have to call this function though, as Cowboy will
|
||||
do it automatically when handler execution ends.
|
203
guide/resp.md
Normal file
203
guide/resp.md
Normal file
|
@ -0,0 +1,203 @@
|
|||
Sending a response
|
||||
==================
|
||||
|
||||
The Req object also allows you to send a response.
|
||||
|
||||
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.
|
||||
|
||||
You can also set headers or the response body in advance
|
||||
and Cowboy will use them when you finally do reply.
|
||||
|
||||
Reply
|
||||
-----
|
||||
|
||||
You can send a reply with no particular headers or body.
|
||||
Cowboy will make sure to send the mandatory headers with
|
||||
the response.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:reply(200, Req).
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:reply(303, [
|
||||
{<<"location">>, <<"http://ninenines.eu">>}
|
||||
], Req).
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:reply(200, [
|
||||
{<<"server">>, <<"yaws">>}
|
||||
], Req).
|
||||
```
|
||||
|
||||
We also saw earlier how to force close the connection by
|
||||
overriding the connection header.
|
||||
|
||||
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.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:reply(200, [
|
||||
{<<"content-type">>, <<"text/plain">>
|
||||
], "Hello world!", Req).
|
||||
```
|
||||
|
||||
Here is the same example but sending HTML this time.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:reply(200, [
|
||||
{<<"content-type">>, <<"text/html">>}
|
||||
], "<html><head>Hello world!</head><body><p>Hats off!</p></body></html>", Req).
|
||||
```
|
||||
|
||||
Note that the reply is sent immediately.
|
||||
|
||||
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.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:chunked_reply(200, Req),
|
||||
ok = cowboy_req:chunk("Hello...", Req2),
|
||||
ok = cowboy_req:chunk("chunked...", Req2),
|
||||
ok = 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.
|
||||
|
||||
``` erlang
|
||||
{ok, Req2} = cowboy_req:chunked_reply(200, [
|
||||
{<<"content-type">>, <<"text/html">>}
|
||||
], Req),
|
||||
ok = cowboy_req:chunk("<html><head>Hello world!</head>", Req2),
|
||||
ok = cowboy_req:chunk("<body><p>Hats off!</p></body></html>", Req2).
|
||||
```
|
||||
|
||||
Note that the reply and each chunk following it are sent
|
||||
immediately.
|
||||
|
||||
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.
|
||||
|
||||
``` erlang
|
||||
Req2 = cowboy_req:set_resp_header(<<"allow">>, "GET", Req).
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
``` erlang
|
||||
cowboy_req:has_resp_header(<<"allow">>, Req).
|
||||
```
|
||||
|
||||
It will return `true` if the header is defined, and `false`
|
||||
otherwise.
|
||||
|
||||
Finally, you can also delete a preset response header if
|
||||
needed. If you do, it will not be sent.
|
||||
|
||||
``` erlang
|
||||
Req2 = cowboy_req:delete_resp_header(<<"allow">>, Req).
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
``` erlang
|
||||
Req2 = cowboy_req:set_resp_body("Hello world!", Req).
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
``` erlang
|
||||
F = fun (Socket, Transport) ->
|
||||
Transport:send(Socket, "Hello world!")
|
||||
end,
|
||||
Req2 = cowboy_req:set_resp_body_fun(12, F, Req).
|
||||
```
|
||||
|
||||
If you do not know the length of the body, you should use
|
||||
a chunked response body fun instead.
|
||||
|
||||
``` erlang
|
||||
F = fun (SendChunk) ->
|
||||
Body = lists:duplicate(random:uniform(1024, $a)),
|
||||
SendChunk(Body)
|
||||
end,
|
||||
Req2 = cowboy_req:set_resp_body_fun(chunked, F, Req).
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
``` 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).
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Again, it is recommended to set the size of the file if it
|
||||
can be known in advance.
|
||||
|
||||
``` erlang
|
||||
F = fun (Socket, Transport) ->
|
||||
Transport:sendfile(Socket, "priv/styles.css")
|
||||
end,
|
||||
Req2 = cowboy_req:set_resp_body_fun(FileSize, F, Req).
|
||||
```
|
||||
|
||||
Please see the Ranch guide for more information about
|
||||
sending files.
|
|
@ -20,8 +20,9 @@ HTTP
|
|||
* [Routing](routing.md)
|
||||
* [Handling plain HTTP requests](http_handlers.md)
|
||||
* [The Req object](req.md)
|
||||
* Reading the request body
|
||||
* Sending a response
|
||||
* [Reading the request body](req_body.md)
|
||||
* [Sending a response](resp.md)
|
||||
* [Using cookies](cookies.md)
|
||||
|
||||
Static files
|
||||
------------
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue