mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Breaking update of the cowboy_req interface
Simplify the interface for most cowboy_req functions. They all return a single value except the four body reading functions. The reply functions now only return a Req value. Access functions do not return a Req anymore. Functions that used to cache results do not have a cache anymore. The interface for accessing query string and cookies has therefore been changed. There are now three query string functions: qs/1 provides access to the raw query string value; parse_qs/1 returns the query string as a list of key/values; match_qs/2 returns a map containing the values requested in the second argument, after applying constraints and default value. Similarly, there are two cookie functions: parse_cookies/1 and match_cookies/2. More match functions will be added in future commits. None of the functions return an error tuple anymore. It either works or crashes. Cowboy will attempt to provide an appropriate status code in the response of crashed handlers. As a result, the content decode function has its return value changed to a simple binary, and the body reading functions only return on success.
This commit is contained in:
parent
b57f94661f
commit
f1c3b6d76f
61 changed files with 814 additions and 767 deletions
32
ROADMAP.md
32
ROADMAP.md
|
@ -20,38 +20,6 @@ A number of backward incompatible changes are planned. These
|
||||||
changes are individually small, but together should result
|
changes are individually small, but together should result
|
||||||
in a large improvement in usability.
|
in a large improvement in usability.
|
||||||
|
|
||||||
### cowboy_req
|
|
||||||
|
|
||||||
The interface of `cowboy_req` will be largely changed. The
|
|
||||||
number one complaint about Cowboy today is that you have
|
|
||||||
to keep track of the Req whenever you do anything. The new
|
|
||||||
interface will minimize that.
|
|
||||||
|
|
||||||
All functions will return a single term, excluding the body
|
|
||||||
reading functions `body/{1,2}`, `body_qs/{1,2}`, `part/{1,2}`,
|
|
||||||
`part_body/{1,2}`.
|
|
||||||
|
|
||||||
Of the functions returning a single term, some of them will
|
|
||||||
return a Req object. This includes the functions that already
|
|
||||||
return Req: `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`, and adds the `chunked_reply/{2,3}` and
|
|
||||||
`reply/{2,3,4}` functions to the list.
|
|
||||||
|
|
||||||
Of note is that this will allow chaining all the response
|
|
||||||
functions if that's what you fancy.
|
|
||||||
|
|
||||||
The `parse_header/{2,3}` function will now only return the
|
|
||||||
parsed header value, and crash on error. It will also not
|
|
||||||
cache the parsed value anymore, except for headers that Cowboy
|
|
||||||
requires, like the connection header.
|
|
||||||
|
|
||||||
It is unsure what will become of the `qs_val`, `qs_vals`,
|
|
||||||
`cookie` and `cookies` functions. The main idea at this point
|
|
||||||
is to replace them with a `parse_qs/2` and `parse_cookies/2`
|
|
||||||
that would return the parsed list, and let the user decide
|
|
||||||
how to access it.
|
|
||||||
|
|
||||||
### init/terminate unification
|
### init/terminate unification
|
||||||
|
|
||||||
The first argument of the `init/3` function is too rarely used.
|
The first argument of the `init/3` function is too rarely used.
|
||||||
|
|
|
@ -28,8 +28,7 @@ that will format the header names with the expected case.
|
||||||
capitalize_hook(Status, Headers, Body, Req) ->
|
capitalize_hook(Status, Headers, Body, Req) ->
|
||||||
Headers2 = [{cowboy_bstr:capitalize_token(N), V}
|
Headers2 = [{cowboy_bstr:capitalize_token(N), V}
|
||||||
|| {N, V} <- Headers],
|
|| {N, V} <- Headers],
|
||||||
{ok, Req2} = cowboy_req:reply(Status, Headers2, Body, Req),
|
cowboy_req:reply(Status, Headers2, Body, Req).
|
||||||
Req2.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that SPDY clients do not have that particular issue
|
Note that SPDY clients do not have that particular issue
|
||||||
|
|
51
doc/src/guide/constraints.ezdoc
Normal file
51
doc/src/guide/constraints.ezdoc
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
::: Constraints
|
||||||
|
|
||||||
|
Cowboy provides an optional constraints based validation feature
|
||||||
|
when interacting with user input.
|
||||||
|
|
||||||
|
Constraints are first used during routing. The router uses
|
||||||
|
constraints to more accurately match bound values, allowing
|
||||||
|
to create routes where a segment is an integer for example,
|
||||||
|
and rejecting the others.
|
||||||
|
|
||||||
|
Constraints are also used when performing a match operation
|
||||||
|
on input data, like the query string or cookies. There, a
|
||||||
|
default value can also be provided for optional values.
|
||||||
|
|
||||||
|
Finally, constraints can be used to not only validate input,
|
||||||
|
but also convert said input into proper Erlang terms, all in
|
||||||
|
one step.
|
||||||
|
|
||||||
|
:: Structure
|
||||||
|
|
||||||
|
Constraints are provided as a list of fields and for each
|
||||||
|
field a list of constraints for that field can be provided.
|
||||||
|
|
||||||
|
Fields are either the name of the field; the name and
|
||||||
|
one or more constraints; or the name, one or more constraints
|
||||||
|
and a default value.
|
||||||
|
|
||||||
|
When no default value is provided then the field is required.
|
||||||
|
Otherwise the default value is used.
|
||||||
|
|
||||||
|
All constraints for a field will be used to match its value
|
||||||
|
in the order they are given. If the value is modified by a
|
||||||
|
constraint, the next constraint receives the updated value.
|
||||||
|
|
||||||
|
:: Built-in constraints
|
||||||
|
|
||||||
|
|| Constraint Description
|
||||||
|
|
|
||||||
|
| int Convert binary value to integer
|
||||||
|
| nonempty Ensures the binary value is non-empty
|
||||||
|
|
||||||
|
:: Custom constraint
|
||||||
|
|
||||||
|
In addition to the predefined constraints, Cowboy will accept
|
||||||
|
a fun. This fun must accept one argument and return one of
|
||||||
|
`true`, `{true, NewValue}` or `false`. The result indicates
|
||||||
|
whether the value matches the constraint, and if it does it
|
||||||
|
can optionally be modified. This allows converting the value
|
||||||
|
to a more appropriate Erlang term.
|
||||||
|
|
||||||
|
Note that constraint functions SHOULD be pure and MUST NOT crash.
|
|
@ -116,22 +116,47 @@ As we said, the client sends cookies with every request.
|
||||||
But unlike the server, the client only sends the cookie
|
But unlike the server, the client only sends the cookie
|
||||||
name and value.
|
name and value.
|
||||||
|
|
||||||
You can read the value of a cookie.
|
Cowboy provides two different ways to read cookies. You
|
||||||
|
can either parse them as a list of key/value pairs, or
|
||||||
|
match them into a map, optionally applying constraints
|
||||||
|
to the values or providing a default if they are missing.
|
||||||
|
|
||||||
|
You can parse the cookies and then use standard library
|
||||||
|
functions to access individual values.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{CookieVal, Req2} = cowboy_req:cookie(<<"lang">>, Req).
|
Cookies = cowboy_req:parse_cookies(Req),
|
||||||
|
{_, Lang} = lists:keyfind(<<"lang">>, 1, Cookies).
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also get a default value returned when the cookie
|
You can match the cookies into a map.
|
||||||
isn't set.
|
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{CookieVal, Req2} = cowboy_req:cookie(<<"lang">>, Req, <<"fr">>).
|
#{id := ID, lang := Lang} = cowboy_req:match_cookies(Req, [id, lang]).
|
||||||
```
|
```
|
||||||
|
|
||||||
And you can obtain all cookies at once as a list of
|
You can use constraints to validate the values while matching
|
||||||
key/value tuples.
|
them. The following snippet will crash if the `id` cookie is
|
||||||
|
not an integer number or if the `lang` cookie is empty. Additionally
|
||||||
|
the `id` cookie value will be converted to an integer term, saving
|
||||||
|
you a conversion step.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{AllCookies, Req2} = cowboy_req:cookies(Req).
|
CookiesMap = cowboy_req:match_cookies(Req, [{id, int}, {lang, nonempty}]).
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that if two cookies share the same name, then the map value
|
||||||
|
will be a list of the two cookie values.
|
||||||
|
|
||||||
|
Read more about ^constraints^.
|
||||||
|
|
||||||
|
A default value can be provided. The default will be used
|
||||||
|
if the `lang` cookie is not found. It will not be used if
|
||||||
|
the cookie is found but has an empty value.
|
||||||
|
|
||||||
|
``` erlang
|
||||||
|
#{lang := Lang} = cowboy_req:match_cookies(Req, [{lang, [], <<"en-US">>}]).
|
||||||
|
```
|
||||||
|
|
||||||
|
If no default is provided and the value is missing, the
|
||||||
|
query string is deemed invalid and the process will crash.
|
||||||
|
|
|
@ -154,7 +154,7 @@ the `handle/2` function like this to send a reply.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
handle(Req, State=#state{}) ->
|
handle(Req, State=#state{}) ->
|
||||||
{ok, Req2} = cowboy_req:reply(200,
|
Req2 = cowboy_req:reply(200,
|
||||||
[{<<"content-type">>, <<"text/plain">>}],
|
[{<<"content-type">>, <<"text/plain">>}],
|
||||||
<<"Hello Erlang!">>,
|
<<"Hello Erlang!">>,
|
||||||
Req),
|
Req),
|
||||||
|
|
|
@ -73,8 +73,7 @@ custom_404_hook(404, Headers, <<>>, Req) ->
|
||||||
Body = <<"404 Not Found.">>,
|
Body = <<"404 Not Found.">>,
|
||||||
Headers2 = lists:keyreplace(<<"content-length">>, 1, Headers,
|
Headers2 = lists:keyreplace(<<"content-length">>, 1, Headers,
|
||||||
{<<"content-length">>, integer_to_list(byte_size(Body))}),
|
{<<"content-length">>, integer_to_list(byte_size(Body))}),
|
||||||
{ok, Req2} = cowboy_req:reply(404, Headers2, Body, Req),
|
cowboy_req:reply(404, Headers2, Body, Req);
|
||||||
Req2;
|
|
||||||
custom_404_hook(_, _, _, Req) ->
|
custom_404_hook(_, _, _, Req) ->
|
||||||
Req.
|
Req.
|
||||||
```
|
```
|
||||||
|
|
|
@ -61,7 +61,7 @@ continue with the handler code, so we use the
|
||||||
init(_Type, Req, Opts) ->
|
init(_Type, Req, Opts) ->
|
||||||
case lists:keyfind(lang, 1, Opts) of
|
case lists:keyfind(lang, 1, Opts) of
|
||||||
false ->
|
false ->
|
||||||
{ok, Req2} = cowboy_req:reply(500, [
|
Req2 = cowboy_req:reply(500, [
|
||||||
{<<"content-type">>, <<"text/plain">>}
|
{<<"content-type">>, <<"text/plain">>}
|
||||||
], "Missing option 'lang'.", Req),
|
], "Missing option 'lang'.", Req),
|
||||||
{shutdown, Req2, no_state};
|
{shutdown, Req2, no_state};
|
||||||
|
@ -110,7 +110,7 @@ The following handle function will send a fairly original response.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
{ok, Req2} = cowboy_req:reply(200, [
|
Req2 = cowboy_req:reply(200, [
|
||||||
{<<"content-type">>, <<"text/plain">>}
|
{<<"content-type">>, <<"text/plain">>}
|
||||||
], <<"Hello World!">>, Req),
|
], <<"Hello World!">>, Req),
|
||||||
{ok, Req2, State}.
|
{ok, Req2, State}.
|
||||||
|
|
|
@ -80,7 +80,7 @@ as the reply is sent.
|
||||||
This snippet will force Cowboy to close the connection.
|
This snippet will force Cowboy to close the connection.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, Req2} = cowboy_req:reply(200, [
|
Req2 = cowboy_req:reply(200, [
|
||||||
{<<"connection">>, <<"close">>},
|
{<<"connection">>, <<"close">>},
|
||||||
], <<"Closing the socket in 3.. 2.. 1..">>, Req).
|
], <<"Closing the socket in 3.. 2.. 1..">>, Req).
|
||||||
```
|
```
|
||||||
|
|
|
@ -15,6 +15,7 @@ best use of Cowboy for writing powerful web applications.
|
||||||
|
|
||||||
* ^"The life of a request^http_req_life
|
* ^"The life of a request^http_req_life
|
||||||
* ^"Routing^routing
|
* ^"Routing^routing
|
||||||
|
* ^"Constraints^constraints
|
||||||
* ^"Handling plain HTTP requests^http_handlers
|
* ^"Handling plain HTTP requests^http_handlers
|
||||||
* ^"The Req object^req
|
* ^"The Req object^req
|
||||||
* ^"Reading the request body^req_body
|
* ^"Reading the request body^req_body
|
||||||
|
|
|
@ -60,7 +60,7 @@ message otherwise.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
info({reply, Body}, Req, State) ->
|
info({reply, Body}, Req, State) ->
|
||||||
{ok, Req2} = cowboy_req:reply(200, [], Body, Req),
|
Req2 = cowboy_req:reply(200, [], Body, Req),
|
||||||
{ok, Req2, State};
|
{ok, Req2, State};
|
||||||
info(_Msg, Req, State) ->
|
info(_Msg, Req, State) ->
|
||||||
{loop, Req, State, hibernate}.
|
{loop, Req, State, hibernate}.
|
||||||
|
@ -95,13 +95,13 @@ and the loop is stopped by sending an `eof` message.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
init(_Type, Req, _Opts) ->
|
init(_Type, Req, _Opts) ->
|
||||||
{ok, Req2} = cowboy_req:chunked_reply(200, [], Req),
|
Req2 = cowboy_req:chunked_reply(200, [], Req),
|
||||||
{loop, Req2, undefined_state}.
|
{loop, Req2, undefined_state}.
|
||||||
|
|
||||||
info(eof, Req, State) ->
|
info(eof, Req, State) ->
|
||||||
{ok, Req, State};
|
{ok, Req, State};
|
||||||
info({chunk, Chunk}, Req, State) ->
|
info({chunk, Chunk}, Req, State) ->
|
||||||
ok = cowboy_req:chunk(Chunk, Req),
|
cowboy_req:chunk(Chunk, Req),
|
||||||
{loop, Req, State};
|
{loop, Req, State};
|
||||||
info(_Msg, Req, State) ->
|
info(_Msg, Req, State) ->
|
||||||
{loop, Req, State}.
|
{loop, Req, State}.
|
||||||
|
|
|
@ -17,7 +17,7 @@ You can quickly figure out if a multipart message
|
||||||
has been sent by parsing the `content-type` header.
|
has been sent by parsing the `content-type` header.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, {<<"multipart">>, <<"form-data">>, _}, Req2}
|
{<<"multipart">>, <<"form-data">>, _}
|
||||||
= cowboy_req:parse_header(<<"content-type">>, Req).
|
= cowboy_req:parse_header(<<"content-type">>, Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -27,47 +27,22 @@ For example, when streaming the request body, the
|
||||||
function will return the body by chunks, one at a
|
function will return the body by chunks, one at a
|
||||||
time, until there is none left.
|
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
|
:: Overview of the cowboy_req interface
|
||||||
|
|
||||||
The `cowboy_req` interface is divided in four groups
|
With the exception of functions manipulating the request
|
||||||
of functions, each having a well defined return type
|
body, all functions return a single value. Depending on
|
||||||
signature common to the entire group.
|
the function this can be the requested value (method,
|
||||||
|
host, path, ...), a boolean (has_body, has_resp_header...)
|
||||||
|
a new Req object (set_resp_body, set_resp_header...), or
|
||||||
|
simply the atom `ok` (chunk, continue, ...).
|
||||||
|
|
||||||
The first group, access functions, will always return
|
The request body reading functions may return `{Result, Req}`
|
||||||
`{Value, Req}`. The group includes all the following
|
or `{Result, Value, Req}`. The functions in this category
|
||||||
functions: `binding/{2,3}`, `bindings/1`, `body_length/1`,
|
are `body/{1,2}`, `body_qs/{1,2}`, `part/{1,2}`, `part_body/{1,2}`.
|
||||||
`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
|
This chapter covers the access functions mainly. Cookies,
|
||||||
a `boolean()`. The group includes the following three
|
request body and response functions are covered in their
|
||||||
functions: `has_body/1`, `has_resp_body/1`, `has_resp_header/2`.
|
own chapters.
|
||||||
|
|
||||||
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}`,
|
|
||||||
`parse_header/{2,3}`, `part/{1,2}`, `part_body/{1,2}`
|
|
||||||
and `reply/{2,3,4}`. Finally, the group also includes the
|
|
||||||
`chunk/2` and `continue/1` functions which always return `ok`.
|
|
||||||
|
|
||||||
The final group modifies the Req object state without
|
|
||||||
performing any immediate operations. As these functions
|
|
||||||
can't fail, they always return a new `Req` directly.
|
|
||||||
This 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
|
:: Request
|
||||||
|
|
||||||
|
@ -82,7 +57,7 @@ GET, HEAD, OPTIONS, PATCH, POST, PUT, DELETE. Method names
|
||||||
are case sensitive.
|
are case sensitive.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{Method, Req2} = cowboy_req:method(Req).
|
Method = cowboy_req:method(Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
The host, port and path parts of the URL identify the resource
|
The host, port and path parts of the URL identify the resource
|
||||||
|
@ -90,15 +65,15 @@ being accessed. The host and port information may not be
|
||||||
available if the client uses HTTP/1.0.
|
available if the client uses HTTP/1.0.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{Host, Req2} = cowboy_req:host(Req),
|
Host = cowboy_req:host(Req),
|
||||||
{Port, Req3} = cowboy_req:port(Req2),
|
Port = cowboy_req:port(Req),
|
||||||
{Path, Req4} = cowboy_req:path(Req3).
|
Path = cowboy_req:path(Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
The version used by the client can of course also be obtained.
|
The version used by the client can of course also be obtained.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{Version, Req2} = cowboy_req:version(Req).
|
Version = cowboy_req:version(Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
Do note however that clients claiming to implement one version
|
Do note however that clients claiming to implement one version
|
||||||
|
@ -115,21 +90,21 @@ You can fetch a single binding. The value will be `undefined`
|
||||||
if the binding doesn't exist.
|
if the binding doesn't exist.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{Binding, Req2} = cowboy_req:binding(my_binding, Req).
|
Binding = cowboy_req:binding(my_binding, Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
If you need a different value when the binding doesn't exist,
|
If you need a different value when the binding doesn't exist,
|
||||||
you can change the default.
|
you can change the default.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{Binding, Req2} = cowboy_req:binding(my_binding, Req, 42).
|
Binding = cowboy_req:binding(my_binding, Req, 42).
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also obtain all bindings in one call. They will be
|
You can also obtain all bindings in one call. They will be
|
||||||
returned as a list of key/value tuples.
|
returned as a list of key/value tuples.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{AllBindings, Req2} = cowboy_req:bindings(Req).
|
AllBindings = cowboy_req:bindings(Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
If you used `...` at the beginning of the route's pattern
|
If you used `...` at the beginning of the route's pattern
|
||||||
|
@ -137,7 +112,7 @@ for the host, you can retrieve the matched part of the host.
|
||||||
The value will be `undefined` otherwise.
|
The value will be `undefined` otherwise.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{HostInfo, Req2} = cowboy_req:host_info(Req).
|
HostInfo = cowboy_req:host_info(Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
Similarly, if you used `...` at the end of the route's
|
Similarly, if you used `...` at the end of the route's
|
||||||
|
@ -145,49 +120,70 @@ pattern for the path, you can retrieve the matched part,
|
||||||
or get `undefined` otherwise.
|
or get `undefined` otherwise.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{PathInfo, Req2} = cowboy_req:path_info(Req).
|
PathInfo = cowboy_req:path_info(Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
:: Query string
|
:: Query string
|
||||||
|
|
||||||
The query string can be obtained directly.
|
The raw query string can be obtained directly.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{Qs, Req2} = cowboy_req:qs(Req).
|
Qs = cowboy_req:qs(Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also requests only one value.
|
You can parse the query string and then use standard library
|
||||||
|
functions to access individual values.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{QsVal, Req2} = cowboy_req:qs_val(<<"lang">>, Req).
|
QsVals = cowboy_req:parse_qs(Req),
|
||||||
|
{_, Lang} = lists:keyfind(<<"lang">>, 1, QsVals).
|
||||||
```
|
```
|
||||||
|
|
||||||
If that value is optional, you can define a default to simplify
|
You can match the query string into a map.
|
||||||
your task.
|
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{QsVal, Req2} = cowboy_req:qs_val(<<"lang">>, Req, <<"en">>).
|
#{id := ID, lang := Lang} = cowboy_req:match_qs(Req, [id, lang]).
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, you can obtain all query string values.
|
You can use constraints to validate the values while matching
|
||||||
|
them. The following snippet will crash if the `id` value is
|
||||||
|
not an integer number or if the `lang` value is empty. Additionally
|
||||||
|
the `id` value will be converted to an integer term, saving
|
||||||
|
you a conversion step.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{AllValues, Req2} = cowboy_req:qs_vals(Req).
|
QsMap = cowboy_req:match_qs(Req, [{id, int}, {lang, nonempty}]).
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that in the case of duplicate query string keys, the map
|
||||||
|
value will become a list of the different values.
|
||||||
|
|
||||||
|
Read more about ^constraints^.
|
||||||
|
|
||||||
|
A default value can be provided. The default will be used
|
||||||
|
if the `lang` key is not found. It will not be used if
|
||||||
|
the key is found but has an empty value.
|
||||||
|
|
||||||
|
``` erlang
|
||||||
|
#{lang := Lang} = cowboy_req:match_qs(Req, [{lang, [], <<"en-US">>}]).
|
||||||
|
```
|
||||||
|
|
||||||
|
If no default is provided and the value is missing, the
|
||||||
|
query string is deemed invalid and the process will crash.
|
||||||
|
|
||||||
:: Request URL
|
:: Request URL
|
||||||
|
|
||||||
You can reconstruct the full URL of the resource.
|
You can reconstruct the full URL of the resource.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{URL, Req2} = cowboy_req:url(Req).
|
URL = cowboy_req:url(Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also obtain only the base of the URL, excluding the
|
You can also obtain only the base of the URL, excluding the
|
||||||
path and query string.
|
path and query string.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{BaseURL, Req2} = cowboy_req:host_url(Req).
|
BaseURL = cowboy_req:host_url(Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
:: Headers
|
:: Headers
|
||||||
|
@ -198,57 +194,43 @@ or parsed into a more meaningful representation.
|
||||||
This will get the string value of a header.
|
This will get the string value of a header.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{HeaderVal, Req2} = cowboy_req:header(<<"content-type">>, Req).
|
HeaderVal = cowboy_req:header(<<"content-type">>, Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
You can of course set a default in case the header is missing.
|
You can of course set a default in case the header is missing.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{HeaderVal, Req2}
|
HeaderVal
|
||||||
= cowboy_req:header(<<"content-type">>, Req, <<"text/plain">>).
|
= cowboy_req:header(<<"content-type">>, Req, <<"text/plain">>).
|
||||||
```
|
```
|
||||||
|
|
||||||
And also obtain all headers.
|
And also obtain all headers.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{AllHeaders, Req2} = cowboy_req:headers(Req).
|
AllHeaders = cowboy_req:headers(Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
To parse the previous header, simply call `parse_header/{2,3}`
|
To parse the previous header, simply call `parse_header/{2,3}`
|
||||||
where you would call `header/{2,3}` otherwise. Note that the
|
where you would call `header/{2,3}` otherwise.
|
||||||
return value changes and includes the result of the operation
|
|
||||||
as the first element of the returned tuple. A successful parse
|
|
||||||
returns `ok`.
|
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, ParsedVal, Req2} = cowboy_req:parse_header(<<"content-type">>, Req).
|
ParsedVal = cowboy_req:parse_header(<<"content-type">>, Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
When Cowboy doesn't know how to parse the given header, the
|
Cowboy will crash if it doesn't know how to parse the given
|
||||||
result of the operation will be `undefined` and the string value
|
header, or if the value is invalid.
|
||||||
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
|
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
|
value you specify here is the parsed value you'd like to get
|
||||||
by default.
|
by default.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, ParsedVal, Req2}
|
ParsedVal = cowboy_req:parse_header(<<"content-type">>, Req,
|
||||||
= cowboy_req:parse_header(<<"content-type">>, Req,
|
{<<"text">>, <<"plain">>, []}).
|
||||||
{<<"text">>, <<"plain">>, []}).
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The list of known headers and default values is defined in the
|
The list of known headers and default values is defined in the
|
||||||
manual. Also note that the result of parsing is cached, so
|
manual.
|
||||||
calling this function multiple times for the same values will
|
|
||||||
not have a significant performance impact.
|
|
||||||
|
|
||||||
:: Meta
|
:: Meta
|
||||||
|
|
||||||
|
@ -260,13 +242,13 @@ This will get a meta value. The returned value will be `undefined`
|
||||||
if it isn't defined.
|
if it isn't defined.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{MetaVal, Req2} = cowboy_req:meta(websocket_version, Req).
|
MetaVal = cowboy_req:meta(websocket_version, Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
You can change the default value if needed.
|
You can change the default value if needed.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{MetaVal, Req2} = cowboy_req:meta(websocket_version, Req, 13).
|
MetaVal = cowboy_req:meta(websocket_version, Req, 13).
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also define your own meta values. The name must be
|
You can also define your own meta values. The name must be
|
||||||
|
@ -283,7 +265,7 @@ not necessarily the actual IP and port of the client, but
|
||||||
rather the one of the machine that connected to the server.
|
rather the one of the machine that connected to the server.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{{IP, Port}, Req2} = cowboy_req:peer(Req).
|
{IP, Port} = cowboy_req:peer(Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
:: Reducing the memory footprint
|
:: Reducing the memory footprint
|
||||||
|
|
|
@ -37,7 +37,7 @@ You can obtain the body length if it was sent with the
|
||||||
request.
|
request.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{Length, Req2} = cowboy_req:body_length(Req).
|
Length = cowboy_req:body_length(Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
The value returned will be `undefined` if the length
|
The value returned will be `undefined` if the length
|
||||||
|
@ -123,7 +123,7 @@ ignored.
|
||||||
The following example shows how to set both options.
|
The following example shows how to set both options.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, Req2} = cowboy_req:body(Req, [
|
{ok, Data, Req2} = cowboy_req:body(Req, [
|
||||||
{transfer_decode, fun transfer_decode/2, TransferState},
|
{transfer_decode, fun transfer_decode/2, TransferState},
|
||||||
{content_decode, fun content_decode/1}
|
{content_decode, fun content_decode/1}
|
||||||
]).
|
]).
|
||||||
|
|
|
@ -16,7 +16,7 @@ Cowboy will make sure to send the mandatory headers with
|
||||||
the response.
|
the response.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, Req2} = cowboy_req:reply(200, Req).
|
Req2 = cowboy_req:reply(200, Req).
|
||||||
```
|
```
|
||||||
|
|
||||||
You can define headers to be sent with the response. Note
|
You can define headers to be sent with the response. Note
|
||||||
|
@ -24,7 +24,7 @@ that header names must be lowercase. Again, Cowboy will
|
||||||
make sure to send the mandatory headers with the response.
|
make sure to send the mandatory headers with the response.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, Req2} = cowboy_req:reply(303, [
|
Req2 = cowboy_req:reply(303, [
|
||||||
{<<"location">>, <<"http://ninenines.eu">>}
|
{<<"location">>, <<"http://ninenines.eu">>}
|
||||||
], Req).
|
], Req).
|
||||||
```
|
```
|
||||||
|
@ -35,7 +35,7 @@ by Cowboy. For example, you can advertise yourself as a
|
||||||
different server.
|
different server.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, Req2} = cowboy_req:reply(200, [
|
Req2 = cowboy_req:reply(200, [
|
||||||
{<<"server">>, <<"yaws">>}
|
{<<"server">>, <<"yaws">>}
|
||||||
], Req).
|
], Req).
|
||||||
```
|
```
|
||||||
|
@ -49,7 +49,7 @@ We recommend that you set the content-type header so the
|
||||||
client may know how to read the body.
|
client may know how to read the body.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, Req2} = cowboy_req:reply(200, [
|
Req2 = cowboy_req:reply(200, [
|
||||||
{<<"content-type">>, <<"text/plain">>}
|
{<<"content-type">>, <<"text/plain">>}
|
||||||
], "Hello world!", Req).
|
], "Hello world!", Req).
|
||||||
```
|
```
|
||||||
|
@ -57,7 +57,7 @@ client may know how to read the body.
|
||||||
Here is the same example but sending HTML this time.
|
Here is the same example but sending HTML this time.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, Req2} = cowboy_req:reply(200, [
|
Req2 = cowboy_req:reply(200, [
|
||||||
{<<"content-type">>, <<"text/html">>}
|
{<<"content-type">>, <<"text/html">>}
|
||||||
], "<html><head>Hello world!</head><body><p>Hats off!</p></body></html>", Req).
|
], "<html><head>Hello world!</head><body><p>Hats off!</p></body></html>", Req).
|
||||||
```
|
```
|
||||||
|
@ -71,10 +71,10 @@ initiate the reply by sending the response status code.
|
||||||
Then you can send the body in chunks of arbitrary size.
|
Then you can send the body in chunks of arbitrary size.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, Req2} = cowboy_req:chunked_reply(200, Req),
|
Req2 = cowboy_req:chunked_reply(200, Req),
|
||||||
ok = cowboy_req:chunk("Hello...", Req2),
|
cowboy_req:chunk("Hello...", Req2),
|
||||||
ok = cowboy_req:chunk("chunked...", Req2),
|
cowboy_req:chunk("chunked...", Req2),
|
||||||
ok = cowboy_req:chunk("world!!", Req2).
|
cowboy_req:chunk("world!!", Req2).
|
||||||
```
|
```
|
||||||
|
|
||||||
You should make sure to match on `ok` as an error may be
|
You should make sure to match on `ok` as an error may be
|
||||||
|
@ -85,11 +85,11 @@ a content-type header, it is still recommended. You can
|
||||||
set this header or any other just like for normal replies.
|
set this header or any other just like for normal replies.
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{ok, Req2} = cowboy_req:chunked_reply(200, [
|
Req2 = cowboy_req:chunked_reply(200, [
|
||||||
{<<"content-type">>, <<"text/html">>}
|
{<<"content-type">>, <<"text/html">>}
|
||||||
], Req),
|
], Req),
|
||||||
ok = cowboy_req:chunk("<html><head>Hello world!</head>", Req2),
|
cowboy_req:chunk("<html><head>Hello world!</head>", Req2),
|
||||||
ok = cowboy_req:chunk("<body><p>Hats off!</p></body></html>", Req2).
|
cowboy_req:chunk("<body><p>Hats off!</p></body></html>", Req2).
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the reply and each chunk following it are sent
|
Note that the reply and each chunk following it are sent
|
||||||
|
|
|
@ -68,16 +68,16 @@ the connection, assuming no correct subprotocol was found.
|
||||||
``` erlang
|
``` erlang
|
||||||
websocket_init(_Type, Req, _Opts) ->
|
websocket_init(_Type, Req, _Opts) ->
|
||||||
case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of
|
case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of
|
||||||
{ok, undefined, Req2} ->
|
undefined ->
|
||||||
{ok, Req, #state{}};
|
{ok, Req, #state{}};
|
||||||
{ok, Subprotocols, Req2} ->
|
Subprotocols ->
|
||||||
case lists:keymember(<<"mychat2">>, 1, Subprotocols) of
|
case lists:keymember(<<"mychat2">>, 1, Subprotocols) of
|
||||||
true ->
|
true ->
|
||||||
Req3 = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>,
|
Req2 = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>,
|
||||||
<<"mychat2">>, Req2),
|
<<"mychat2">>, Req),
|
||||||
{ok, Req3, #state{}};
|
{ok, Req2, #state{}};
|
||||||
false ->
|
false ->
|
||||||
{shutdown, Req2}
|
{shutdown, Req}
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
```
|
```
|
||||||
|
|
|
@ -5,6 +5,12 @@ manipulating Ranch listeners.
|
||||||
|
|
||||||
:: Types
|
:: Types
|
||||||
|
|
||||||
|
: fields() = [atom()
|
||||||
|
| {atom(), cowboy_constraints:constraint() | [cowboy_constraints:constraint()]}
|
||||||
|
| {atom(), cowboy_constraints:constraint() | [cowboy_constraints:constraint()], any()}]
|
||||||
|
|
||||||
|
Fields for match operations. Constraint(s) and default value are optional.
|
||||||
|
|
||||||
: http_headers() = [{binary(), iodata()}]
|
: http_headers() = [{binary(), iodata()}]
|
||||||
|
|
||||||
HTTP headers as a list of key/values.
|
HTTP headers as a list of key/values.
|
||||||
|
|
|
@ -6,21 +6,19 @@ and respond to requests.
|
||||||
The functions in this module follow patterns for their return types,
|
The functions in this module follow patterns for their return types,
|
||||||
based on the kind of function.
|
based on the kind of function.
|
||||||
|
|
||||||
* access: `{Value, Req}`
|
* access: `Value`
|
||||||
* action: `{Result, Req} | {Result, Value, Req} | {error, atom()}`
|
* action: `ok | {Result, Req} | {Result, Value, Req}`
|
||||||
* modification: `Req`
|
* modification: `Req`
|
||||||
* question: `boolean()`
|
* question: `boolean()`
|
||||||
|
|
||||||
The only exception is the `chunk/2` function which may return `ok`.
|
|
||||||
|
|
||||||
Whenever `Req` is returned, you must use this returned value and
|
Whenever `Req` is returned, you must use this returned value and
|
||||||
ignore any previous you may have had. This value contains various
|
ignore any previous you may have had. This value contains various
|
||||||
state informations which are necessary for Cowboy to do some lazy
|
values which are necessary for Cowboy to keep track of the request
|
||||||
evaluation or cache results where appropriate.
|
and response states.
|
||||||
|
|
||||||
All functions which perform an action should only be called once.
|
All functions which perform an action should only be called once.
|
||||||
This includes reading the request body or replying. Cowboy will
|
This includes reading the request body or replying. Cowboy will
|
||||||
generally throw an error on the second call.
|
throw an error on the second call when it detects suspicious behavior.
|
||||||
|
|
||||||
It is highly discouraged to pass the Req object to another process.
|
It is highly discouraged to pass the Req object to another process.
|
||||||
Doing so and calling `cowboy_req` functions from it leads to
|
Doing so and calling `cowboy_req` functions from it leads to
|
||||||
|
@ -54,7 +52,7 @@ the function descriptions below.
|
||||||
:: Request related exports
|
:: Request related exports
|
||||||
|
|
||||||
: binding(Name, Req) -> binding(Name, Req, undefined)
|
: binding(Name, Req) -> binding(Name, Req, undefined)
|
||||||
: binding(Name, Req, Default) -> {Value, Req2}
|
: binding(Name, Req, Default) -> Value
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -68,7 +66,7 @@ By default the value is a binary, however constraints may change
|
||||||
the type of this value (for example automatically converting
|
the type of this value (for example automatically converting
|
||||||
numbers to integer).
|
numbers to integer).
|
||||||
|
|
||||||
: bindings(Req) -> {[{Name, Value}], Req2}
|
: bindings(Req) -> [{Name, Value}]
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -81,30 +79,8 @@ By default the value is a binary, however constraints may change
|
||||||
the type of this value (for example automatically converting
|
the type of this value (for example automatically converting
|
||||||
numbers to integer).
|
numbers to integer).
|
||||||
|
|
||||||
: cookie(Name, Req) -> cookie(Name, Req, undefined)
|
|
||||||
: cookie(Name, Req, Default) -> {Value, Req2}
|
|
||||||
|
|
||||||
Types:
|
|
||||||
|
|
||||||
* Name = binary()
|
|
||||||
* Default = any()
|
|
||||||
* Value = binary() | Default
|
|
||||||
|
|
||||||
Return the value for the given cookie.
|
|
||||||
|
|
||||||
Cookie names are case sensitive.
|
|
||||||
|
|
||||||
: cookies(Req) -> {[{Name, Value}], Req2}
|
|
||||||
|
|
||||||
Types:
|
|
||||||
|
|
||||||
* Name = binary()
|
|
||||||
* Value = binary()
|
|
||||||
|
|
||||||
Return all cookies.
|
|
||||||
|
|
||||||
: header(Name, Req) -> header(Name, Req, undefined)
|
: header(Name, Req) -> header(Name, Req, undefined)
|
||||||
: header(Name, Req, Default) -> {Value, Req2}
|
: header(Name, Req, Default) -> Value
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -117,7 +93,7 @@ Return the value for the given header.
|
||||||
While header names are case insensitive, this function expects
|
While header names are case insensitive, this function expects
|
||||||
the name to be a lowercase binary.
|
the name to be a lowercase binary.
|
||||||
|
|
||||||
: headers(Req) -> {Headers, Req2}
|
: headers(Req) -> Headers
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -125,7 +101,7 @@ Types:
|
||||||
|
|
||||||
Return all headers.
|
Return all headers.
|
||||||
|
|
||||||
: host(Req) -> {Host, Req2}
|
: host(Req) -> Host
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -133,7 +109,7 @@ Types:
|
||||||
|
|
||||||
Return the requested host.
|
Return the requested host.
|
||||||
|
|
||||||
: host_info(Req) -> {HostInfo, Req2}
|
: host_info(Req) -> HostInfo
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -141,7 +117,7 @@ Types:
|
||||||
|
|
||||||
Return the extra tokens from matching against `...` during routing.
|
Return the extra tokens from matching against `...` during routing.
|
||||||
|
|
||||||
: host_url(Req) -> {HostURL, Req2}
|
: host_url(Req) -> HostURL
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -153,8 +129,57 @@ This function will always return `undefined` until the
|
||||||
`cowboy_router` middleware has been executed. This includes
|
`cowboy_router` middleware has been executed. This includes
|
||||||
the `onrequest` hook.
|
the `onrequest` hook.
|
||||||
|
|
||||||
|
: match_cookies(Req, Fields) -> Map
|
||||||
|
|
||||||
|
Types:
|
||||||
|
|
||||||
|
* Fields = cowboy:fields()
|
||||||
|
* Map = map()
|
||||||
|
|
||||||
|
Match cookies against the given fields.
|
||||||
|
|
||||||
|
Cowboy will only return the cookie values specified in the
|
||||||
|
fields list, and ignore all others. Fields can be either
|
||||||
|
the name of the cookie requested; the name along with a
|
||||||
|
list of constraints; or the name, a list of constraints
|
||||||
|
and a default value in case the cookie is missing.
|
||||||
|
|
||||||
|
This function will crash if the cookie is missing and no
|
||||||
|
default value is provided. This function will also crash
|
||||||
|
if a constraint fails.
|
||||||
|
|
||||||
|
The name of the cookie must be provided as an atom. The
|
||||||
|
key of the returned map will be that atom. The value may
|
||||||
|
be converted through the use of constraints, making this
|
||||||
|
function able to extract, validate and convert values all
|
||||||
|
in one step.
|
||||||
|
|
||||||
|
: match_qs(Req, Fields) -> Map
|
||||||
|
|
||||||
|
Types:
|
||||||
|
|
||||||
|
* Fields = cowboy:fields()
|
||||||
|
* Map = map()
|
||||||
|
|
||||||
|
Match the query string against the given fields.
|
||||||
|
|
||||||
|
Cowboy will only return the query string values specified
|
||||||
|
in the fields list, and ignore all others. Fields can be
|
||||||
|
either the key requested; the key along with a list of
|
||||||
|
constraints; or the key, a list of constraints and a
|
||||||
|
default value in case the key is missing.
|
||||||
|
|
||||||
|
This function will crash if the key is missing and no
|
||||||
|
default value is provided. This function will also crash
|
||||||
|
if a constraint fails.
|
||||||
|
|
||||||
|
The key must be provided as an atom. The key of the
|
||||||
|
returned map will be that atom. The value may be converted
|
||||||
|
through the use of constraints, making this function able
|
||||||
|
to extract, validate and convert values all in one step.
|
||||||
|
|
||||||
: meta(Name, Req) -> meta(Name, Req, undefined)
|
: meta(Name, Req) -> meta(Name, Req, undefined)
|
||||||
: meta(Name, Req, Default) -> {Value, Req2}
|
: meta(Name, Req, Default) -> Value
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -164,7 +189,7 @@ Types:
|
||||||
|
|
||||||
Return metadata about the request.
|
Return metadata about the request.
|
||||||
|
|
||||||
: method(Req) -> {Method, Req2}
|
: method(Req) -> Method
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -174,16 +199,25 @@ Return the method.
|
||||||
|
|
||||||
Methods are case sensitive. Standard methods are always uppercase.
|
Methods are case sensitive. Standard methods are always uppercase.
|
||||||
|
|
||||||
: parse_header(Name, Req) ->
|
: parse_cookies(Req) -> [{Name, Value}]
|
||||||
: parse_header(Name, Req, Default) -> {ok, ParsedValue, Req2}
|
|
||||||
| {undefined, Value, Req2} | {error, badarg}
|
Types:
|
||||||
|
|
||||||
|
* Name = binary()
|
||||||
|
* Value = binary()
|
||||||
|
|
||||||
|
Parse and return all cookies.
|
||||||
|
|
||||||
|
Cookie names are case sensitive.
|
||||||
|
|
||||||
|
: parse_header(Name, Req) -> see below
|
||||||
|
: parse_header(Name, Req, Default) -> ParsedValue | Default
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
* Name = binary()
|
* Name = binary()
|
||||||
* Default = any()
|
* Default = any()
|
||||||
* ParsedValue - see below
|
* ParsedValue - see below
|
||||||
* Value = any()
|
|
||||||
|
|
||||||
Parse the given header.
|
Parse the given header.
|
||||||
|
|
||||||
|
@ -196,6 +230,8 @@ following table summarizes the default values used.
|
||||||
|
|
||||||
|| Header name Default value
|
|| Header name Default value
|
||||||
|
|
|
|
||||||
|
| content-length `0`
|
||||||
|
| cookie `[]`
|
||||||
| transfer-encoding `[<<"identity">>]`
|
| transfer-encoding `[<<"identity">>]`
|
||||||
| Any other header `undefined`
|
| Any other header `undefined`
|
||||||
|
|
||||||
|
@ -241,8 +277,8 @@ except the value of the charset parameter, which is case insensitive.
|
||||||
All other values are case insensitive and will be returned as lowercase.
|
All other values are case insensitive and will be returned as lowercase.
|
||||||
|
|
||||||
The headers accept, accept-encoding and cookie headers can return
|
The headers accept, accept-encoding and cookie headers can return
|
||||||
an empty list. Others will return `{error, badarg}` if the header
|
an empty list. Some other headers are expected to have a value if provided
|
||||||
value is empty.
|
and may crash if the value is missing.
|
||||||
|
|
||||||
The authorization header parsing code currently only supports basic
|
The authorization header parsing code currently only supports basic
|
||||||
HTTP authentication. The `Credentials` type is thus `{Username, Password}`
|
HTTP authentication. The `Credentials` type is thus `{Username, Password}`
|
||||||
|
@ -257,7 +293,21 @@ The range header value `Range` can take three forms:
|
||||||
An `undefined` tuple will be returned if Cowboy doesn't know how
|
An `undefined` tuple will be returned if Cowboy doesn't know how
|
||||||
to parse the requested header.
|
to parse the requested header.
|
||||||
|
|
||||||
: path(Req) -> {Path, Req2}
|
: parse_qs(Req) -> [{Name, Value}]
|
||||||
|
|
||||||
|
Types:
|
||||||
|
|
||||||
|
* Name = binary()
|
||||||
|
* Value = binary() | true
|
||||||
|
|
||||||
|
Return the request's query string as a list of tuples.
|
||||||
|
|
||||||
|
The atom `true` is returned for keys which have no value.
|
||||||
|
Keys with no value are different from keys with an empty
|
||||||
|
value in that they do not have a `=` indicating the presence
|
||||||
|
of a value.
|
||||||
|
|
||||||
|
: path(Req) -> Path
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -265,7 +315,7 @@ Types:
|
||||||
|
|
||||||
Return the requested path.
|
Return the requested path.
|
||||||
|
|
||||||
: path_info(Req) -> {PathInfo, Req2}
|
: path_info(Req) -> PathInfo
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -273,7 +323,7 @@ Types:
|
||||||
|
|
||||||
Return the extra tokens from matching against `...` during routing.
|
Return the extra tokens from matching against `...` during routing.
|
||||||
|
|
||||||
: peer(Req) -> {Peer, Req2}
|
: peer(Req) -> Peer
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -281,7 +331,7 @@ Types:
|
||||||
|
|
||||||
Return the client's IP address and port number.
|
Return the client's IP address and port number.
|
||||||
|
|
||||||
: port(Req) -> {Port, Req2}
|
: port(Req) -> Port
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -293,7 +343,7 @@ The port returned by this function is obtained by parsing
|
||||||
the host header. It may be different than the actual port
|
the host header. It may be different than the actual port
|
||||||
the client used to connect to the Cowboy server.
|
the client used to connect to the Cowboy server.
|
||||||
|
|
||||||
: qs(Req) -> {QueryString, Req2}
|
: qs(Req) -> QueryString
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -301,32 +351,6 @@ Types:
|
||||||
|
|
||||||
Return the request's query string.
|
Return the request's query string.
|
||||||
|
|
||||||
: qs_val(Name, Req) -> qs_val(Name, Req, undefined)
|
|
||||||
: qs_val(Name, Req, Default) -> {Value, Req2}
|
|
||||||
|
|
||||||
Types:
|
|
||||||
|
|
||||||
* Name = binary()
|
|
||||||
* Default = any()
|
|
||||||
* Value = binary() | true
|
|
||||||
|
|
||||||
Return a value from the request's query string.
|
|
||||||
|
|
||||||
The value `true` will be returned when the name was found
|
|
||||||
in the query string without an associated value.
|
|
||||||
|
|
||||||
: qs_vals(Req) -> {[{Name, Value}], Req2}
|
|
||||||
|
|
||||||
Types:
|
|
||||||
|
|
||||||
* Name = binary()
|
|
||||||
* Value = binary() | true
|
|
||||||
|
|
||||||
Return the request's query string as a list of tuples.
|
|
||||||
|
|
||||||
The value `true` will be returned when a name was found
|
|
||||||
in the query string without an associated value.
|
|
||||||
|
|
||||||
: set_meta(Name, Value, Req) -> Req2
|
: set_meta(Name, Value, Req) -> Req2
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
@ -338,7 +362,7 @@ Set metadata about the request.
|
||||||
|
|
||||||
An existing value will be overwritten.
|
An existing value will be overwritten.
|
||||||
|
|
||||||
: url(Req) -> {URL, Req2}
|
: url(Req) -> URL
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -350,7 +374,7 @@ This function will always return `undefined` until the
|
||||||
`cowboy_router` middleware has been executed. This includes
|
`cowboy_router` middleware has been executed. This includes
|
||||||
the `onrequest` hook.
|
the `onrequest` hook.
|
||||||
|
|
||||||
: version(Req) -> {Version, Req2}
|
: version(Req) -> Version
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -361,7 +385,7 @@ Return the HTTP version used for this request.
|
||||||
:: Request body related exports
|
:: Request body related exports
|
||||||
|
|
||||||
: body(Req) -> body(Req, [])
|
: body(Req) -> body(Req, [])
|
||||||
: body(Req, Opts) -> {ok, Data, Req2} | {more, Data, Req2} | {error, Reason}
|
: body(Req, Opts) -> {ok, Data, Req2} | {more, Data, Req2}
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -400,7 +424,7 @@ the content-length header if it wasn't already there.
|
||||||
This function can only be called once. Cowboy will not cache
|
This function can only be called once. Cowboy will not cache
|
||||||
the result of this call.
|
the result of this call.
|
||||||
|
|
||||||
: body_length(Req) -> {Length, Req2}
|
: body_length(Req) -> Length
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -414,8 +438,7 @@ is present.
|
||||||
|
|
||||||
: body_qs(Req) -> body_qs(Req,
|
: body_qs(Req) -> body_qs(Req,
|
||||||
[{length, 64000}, {read_length, 64000}, {read_timeout, 5000}])
|
[{length, 64000}, {read_length, 64000}, {read_timeout, 5000}])
|
||||||
: body_qs(Req, Opts) -> {ok, [{Name, Value}], Req2}
|
: body_qs(Req, Opts) -> {ok, [{Name, Value}], Req2} | {badlength, Req2}
|
||||||
| {badlength, Req2} | {error, Reason}
|
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -501,7 +524,7 @@ it cannot be read again.
|
||||||
|
|
||||||
:: Response related exports
|
:: Response related exports
|
||||||
|
|
||||||
: chunk(Data, Req) -> ok | {error, Reason}
|
: chunk(Data, Req) -> ok
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -520,7 +543,7 @@ without wrapping it in an HTTP/1.1 chunk, providing
|
||||||
compatibility with older clients.
|
compatibility with older clients.
|
||||||
|
|
||||||
: chunked_reply(StatusCode, Req) -> chunked_reply(StatusCode, [], Req)
|
: chunked_reply(StatusCode, Req) -> chunked_reply(StatusCode, [], Req)
|
||||||
: chunked_reply(StatusCode, Headers, Req) -> {ok, Req2}
|
: chunked_reply(StatusCode, Headers, Req) -> Req2
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -543,7 +566,7 @@ compatibility with older clients.
|
||||||
This function can only be called once, with the exception
|
This function can only be called once, with the exception
|
||||||
of overriding the response in the `onresponse` hook.
|
of overriding the response in the `onresponse` hook.
|
||||||
|
|
||||||
: continue(Req) -> ok | {error, Reason}
|
: continue(Req) -> ok
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -591,7 +614,7 @@ the name to be a lowercase binary.
|
||||||
|
|
||||||
: reply(StatusCode, Req) -> reply(StatusCode, [], Req)
|
: reply(StatusCode, Req) -> reply(StatusCode, [], Req)
|
||||||
: reply(StatusCode, Headers, Req) - see below
|
: reply(StatusCode, Headers, Req) - see below
|
||||||
: reply(StatusCode, Headers, Body, Req) -> {ok, Req2}
|
: reply(StatusCode, Headers, Body, Req) -> Req2
|
||||||
|
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
|
@ -657,7 +680,7 @@ arguments. Only send and sendfile operations are supported.
|
||||||
Types:
|
Types:
|
||||||
|
|
||||||
* Fun = fun((ChunkFun) -> ok)
|
* Fun = fun((ChunkFun) -> ok)
|
||||||
* ChunkFun = fun((iodata()) -> ok | {error, atom()})
|
* ChunkFun = fun((iodata()) -> ok)
|
||||||
|
|
||||||
Set a fun for sending the response body using chunked transfer-encoding.
|
Set a fun for sending the response body using chunked transfer-encoding.
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,12 @@ init(_Transport, Req, []) ->
|
||||||
{ok, Req, undefined}.
|
{ok, Req, undefined}.
|
||||||
|
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
{ok, Req2} = cowboy_req:chunked_reply(200, Req),
|
Req2 = cowboy_req:chunked_reply(200, Req),
|
||||||
ok = cowboy_req:chunk("Hello\r\n", Req2),
|
cowboy_req:chunk("Hello\r\n", Req2),
|
||||||
ok = timer:sleep(1000),
|
timer:sleep(1000),
|
||||||
ok = cowboy_req:chunk("World\r\n", Req2),
|
cowboy_req:chunk("World\r\n", Req2),
|
||||||
ok = timer:sleep(1000),
|
timer:sleep(1000),
|
||||||
ok = cowboy_req:chunk("Chunked!\r\n", Req2),
|
cowboy_req:chunk("Chunked!\r\n", Req2),
|
||||||
{ok, Req2, State}.
|
{ok, Req2, State}.
|
||||||
|
|
||||||
terminate(_Reason, _Req, _State) ->
|
terminate(_Reason, _Req, _State) ->
|
||||||
|
|
|
@ -24,7 +24,7 @@ have established the ability to work at virtually identical tasks and obtained
|
||||||
considerable respect for their achievements. There are also cattle handlers
|
considerable respect for their achievements. There are also cattle handlers
|
||||||
in many other parts of the world, particularly South America and Australia,
|
in many other parts of the world, particularly South America and Australia,
|
||||||
who perform work similar to the cowboy in their respective nations.\n">>,
|
who perform work similar to the cowboy in their respective nations.\n">>,
|
||||||
{ok, Req2} = cowboy_req:reply(200, [], BigBody, Req),
|
Req2 = cowboy_req:reply(200, [], BigBody, Req),
|
||||||
{ok, Req2, State}.
|
{ok, Req2, State}.
|
||||||
|
|
||||||
terminate(_Reason, _Req, _State) ->
|
terminate(_Reason, _Req, _State) ->
|
||||||
|
|
|
@ -14,16 +14,16 @@ handle(Req, State) ->
|
||||||
NewValue = integer_to_list(random:uniform(1000000)),
|
NewValue = integer_to_list(random:uniform(1000000)),
|
||||||
Req2 = cowboy_req:set_resp_cookie(
|
Req2 = cowboy_req:set_resp_cookie(
|
||||||
<<"server">>, NewValue, [{path, <<"/">>}], Req),
|
<<"server">>, NewValue, [{path, <<"/">>}], Req),
|
||||||
{ClientCookie, Req3} = cowboy_req:cookie(<<"client">>, Req2),
|
#{client := ClientCookie, server := ServerCookie}
|
||||||
{ServerCookie, Req4} = cowboy_req:cookie(<<"server">>, Req3),
|
= cowboy_req:match_cookies(Req2, [client, server]),
|
||||||
{ok, Body} = toppage_dtl:render([
|
{ok, Body} = toppage_dtl:render([
|
||||||
{client, ClientCookie},
|
{client, ClientCookie},
|
||||||
{server, ServerCookie}
|
{server, ServerCookie}
|
||||||
]),
|
]),
|
||||||
{ok, Req5} = cowboy_req:reply(200,
|
Req3 = cowboy_req:reply(200,
|
||||||
[{<<"content-type">>, <<"text/html">>}],
|
[{<<"content-type">>, <<"text/html">>}],
|
||||||
Body, Req4),
|
Body, Req2),
|
||||||
{ok, Req5, State}.
|
{ok, Req3, State}.
|
||||||
|
|
||||||
terminate(_Reason, _Req, _State) ->
|
terminate(_Reason, _Req, _State) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -11,10 +11,10 @@ init(_Transport, Req, []) ->
|
||||||
{ok, Req, undefined}.
|
{ok, Req, undefined}.
|
||||||
|
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
{Method, Req2} = cowboy_req:method(Req),
|
Method = cowboy_req:method(Req),
|
||||||
{Echo, Req3} = cowboy_req:qs_val(<<"echo">>, Req2),
|
#{echo := Echo} = cowboy_req:match_qs(Req, [echo]),
|
||||||
{ok, Req4} = echo(Method, Echo, Req3),
|
Req2 = echo(Method, Echo, Req),
|
||||||
{ok, Req4, State}.
|
{ok, Req2, State}.
|
||||||
|
|
||||||
echo(<<"GET">>, undefined, Req) ->
|
echo(<<"GET">>, undefined, Req) ->
|
||||||
cowboy_req:reply(400, [], <<"Missing echo parameter.">>, Req);
|
cowboy_req:reply(400, [], <<"Missing echo parameter.">>, Req);
|
||||||
|
|
|
@ -11,10 +11,10 @@ init(_Transport, Req, []) ->
|
||||||
{ok, Req, undefined}.
|
{ok, Req, undefined}.
|
||||||
|
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
{Method, Req2} = cowboy_req:method(Req),
|
Method = cowboy_req:method(Req),
|
||||||
HasBody = cowboy_req:has_body(Req2),
|
HasBody = cowboy_req:has_body(Req),
|
||||||
{ok, Req3} = maybe_echo(Method, HasBody, Req2),
|
Req2 = maybe_echo(Method, HasBody, Req),
|
||||||
{ok, Req3, State}.
|
{ok, Req2, State}.
|
||||||
|
|
||||||
maybe_echo(<<"POST">>, true, Req) ->
|
maybe_echo(<<"POST">>, true, Req) ->
|
||||||
{ok, PostVals, Req2} = cowboy_req:body_qs(Req),
|
{ok, PostVals, Req2} = cowboy_req:body_qs(Req),
|
||||||
|
|
|
@ -24,18 +24,16 @@ stop(_State) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
error_hook(404, Headers, <<>>, Req) ->
|
error_hook(404, Headers, <<>>, Req) ->
|
||||||
{Path, Req2} = cowboy_req:path(Req),
|
Path = cowboy_req:path(Req),
|
||||||
Body = ["404 Not Found: \"", Path,
|
Body = ["404 Not Found: \"", Path,
|
||||||
"\" is not the path you are looking for.\n"],
|
"\" is not the path you are looking for.\n"],
|
||||||
Headers2 = lists:keyreplace(<<"content-length">>, 1, Headers,
|
Headers2 = lists:keyreplace(<<"content-length">>, 1, Headers,
|
||||||
{<<"content-length">>, integer_to_list(iolist_size(Body))}),
|
{<<"content-length">>, integer_to_list(iolist_size(Body))}),
|
||||||
{ok, Req3} = cowboy_req:reply(404, Headers2, Body, Req2),
|
cowboy_req:reply(404, Headers2, Body, Req);
|
||||||
Req3;
|
|
||||||
error_hook(Code, Headers, <<>>, Req) when is_integer(Code), Code >= 400 ->
|
error_hook(Code, Headers, <<>>, Req) when is_integer(Code), Code >= 400 ->
|
||||||
Body = ["HTTP Error ", integer_to_list(Code), $\n],
|
Body = ["HTTP Error ", integer_to_list(Code), $\n],
|
||||||
Headers2 = lists:keyreplace(<<"content-length">>, 1, Headers,
|
Headers2 = lists:keyreplace(<<"content-length">>, 1, Headers,
|
||||||
{<<"content-length">>, integer_to_list(iolist_size(Body))}),
|
{<<"content-length">>, integer_to_list(iolist_size(Body))}),
|
||||||
{ok, Req2} = cowboy_req:reply(Code, Headers2, Body, Req),
|
cowboy_req:reply(Code, Headers2, Body, Req);
|
||||||
Req2;
|
|
||||||
error_hook(_Code, _Headers, _Body, Req) ->
|
error_hook(_Code, _Headers, _Body, Req) ->
|
||||||
Req.
|
Req.
|
||||||
|
|
|
@ -9,12 +9,12 @@
|
||||||
|
|
||||||
init(_Transport, Req, []) ->
|
init(_Transport, Req, []) ->
|
||||||
Headers = [{<<"content-type">>, <<"text/event-stream">>}],
|
Headers = [{<<"content-type">>, <<"text/event-stream">>}],
|
||||||
{ok, Req2} = cowboy_req:chunked_reply(200, Headers, Req),
|
Req2 = cowboy_req:chunked_reply(200, Headers, Req),
|
||||||
erlang:send_after(1000, self(), {message, "Tick"}),
|
erlang:send_after(1000, self(), {message, "Tick"}),
|
||||||
{loop, Req2, undefined, 5000}.
|
{loop, Req2, undefined, 5000}.
|
||||||
|
|
||||||
info({message, Msg}, Req, State) ->
|
info({message, Msg}, Req, State) ->
|
||||||
ok = cowboy_req:chunk(["id: ", id(), "\ndata: ", Msg, "\n\n"], Req),
|
cowboy_req:chunk(["id: ", id(), "\ndata: ", Msg, "\n\n"], Req),
|
||||||
erlang:send_after(1000, self(), {message, "Tick"}),
|
erlang:send_after(1000, self(), {message, "Tick"}),
|
||||||
{loop, Req, State}.
|
{loop, Req, State}.
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ init(_Type, Req, []) ->
|
||||||
{ok, Req, undefined}.
|
{ok, Req, undefined}.
|
||||||
|
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
{ok, Req2} = cowboy_req:reply(200, [
|
Req2 = cowboy_req:reply(200, [
|
||||||
{<<"content-type">>, <<"text/plain">>}
|
{<<"content-type">>, <<"text/plain">>}
|
||||||
], <<"Hello world!">>, Req),
|
], <<"Hello world!">>, Req),
|
||||||
{ok, Req2, State}.
|
{ok, Req2, State}.
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
-export([execute/2]).
|
-export([execute/2]).
|
||||||
|
|
||||||
execute(Req, Env) ->
|
execute(Req, Env) ->
|
||||||
{[Path], Req1} = cowboy_req:path_info(Req),
|
[Path] = cowboy_req:path_info(Req),
|
||||||
case filename:extension(Path) of
|
case filename:extension(Path) of
|
||||||
<<".html">> -> maybe_generate_markdown(resource_path(Path));
|
<<".html">> -> maybe_generate_markdown(resource_path(Path));
|
||||||
_Ext -> ok
|
_Ext -> ok
|
||||||
end,
|
end,
|
||||||
{ok, Req1, Env}.
|
{ok, Req, Env}.
|
||||||
|
|
||||||
maybe_generate_markdown(Path) ->
|
maybe_generate_markdown(Path) ->
|
||||||
ModifiedAt = filelib:last_modified(source_path(Path)),
|
ModifiedAt = filelib:last_modified(source_path(Path)),
|
||||||
|
|
|
@ -12,12 +12,11 @@ init(_Transport, _Req, []) ->
|
||||||
{upgrade, protocol, cowboy_rest}.
|
{upgrade, protocol, cowboy_rest}.
|
||||||
|
|
||||||
is_authorized(Req, State) ->
|
is_authorized(Req, State) ->
|
||||||
{ok, Auth, Req1} = cowboy_req:parse_header(<<"authorization">>, Req),
|
case cowboy_req:parse_header(<<"authorization">>, Req) of
|
||||||
case Auth of
|
|
||||||
{<<"basic">>, {User = <<"Alladin">>, <<"open sesame">>}} ->
|
{<<"basic">>, {User = <<"Alladin">>, <<"open sesame">>}} ->
|
||||||
{true, Req1, User};
|
{true, Req, User};
|
||||||
_ ->
|
_ ->
|
||||||
{{false, <<"Basic realm=\"cowboy\"">>}, Req1, State}
|
{{false, <<"Basic realm=\"cowboy\"">>}, Req, State}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
content_types_provided(Req, State) ->
|
content_types_provided(Req, State) ->
|
||||||
|
|
|
@ -36,37 +36,37 @@ content_types_accepted(Req, State) ->
|
||||||
|
|
||||||
resource_exists(Req, _State) ->
|
resource_exists(Req, _State) ->
|
||||||
case cowboy_req:binding(paste_id, Req) of
|
case cowboy_req:binding(paste_id, Req) of
|
||||||
{undefined, Req2} ->
|
undefined ->
|
||||||
{true, Req2, index};
|
{true, Req, index};
|
||||||
{PasteID, Req2} ->
|
PasteID ->
|
||||||
case valid_path(PasteID) and file_exists(PasteID) of
|
case valid_path(PasteID) and file_exists(PasteID) of
|
||||||
true -> {true, Req2, PasteID};
|
true -> {true, Req, PasteID};
|
||||||
false -> {false, Req2, PasteID}
|
false -> {false, Req, PasteID}
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
create_paste(Req, State) ->
|
create_paste(Req, State) ->
|
||||||
PasteID = new_paste_id(),
|
PasteID = new_paste_id(),
|
||||||
{ok, [{<<"paste">>, Paste}], Req3} = cowboy_req:body_qs(Req),
|
{ok, [{<<"paste">>, Paste}], Req2} = cowboy_req:body_qs(Req),
|
||||||
ok = file:write_file(full_path(PasteID), Paste),
|
ok = file:write_file(full_path(PasteID), Paste),
|
||||||
case cowboy_req:method(Req3) of
|
case cowboy_req:method(Req2) of
|
||||||
{<<"POST">>, Req4} ->
|
<<"POST">> ->
|
||||||
{{true, <<$/, PasteID/binary>>}, Req4, State};
|
{{true, <<$/, PasteID/binary>>}, Req2, State};
|
||||||
{_, Req4} ->
|
_ ->
|
||||||
{true, Req4, State}
|
{true, Req2, State}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
paste_html(Req, index) ->
|
paste_html(Req, index) ->
|
||||||
{read_file("index.html"), Req, index};
|
{read_file("index.html"), Req, index};
|
||||||
paste_html(Req, Paste) ->
|
paste_html(Req, Paste) ->
|
||||||
{Style, Req2} = cowboy_req:qs_val(<<"lang">>, Req, plain),
|
#{lang := Lang} = cowboy_req:match_qs(Req, [lang]),
|
||||||
{format_html(Paste, Style), Req2, Paste}.
|
{format_html(Paste, Lang), Req, Paste}.
|
||||||
|
|
||||||
paste_text(Req, index) ->
|
paste_text(Req, index) ->
|
||||||
{read_file("index.txt"), Req, index};
|
{read_file("index.txt"), Req, index};
|
||||||
paste_text(Req, Paste) ->
|
paste_text(Req, Paste) ->
|
||||||
{Style, Req2} = cowboy_req:qs_val(<<"lang">>, Req, plain),
|
#{lang := Lang} = cowboy_req:match_qs(Req, [lang]),
|
||||||
{format_text(Paste, Style), Req2, Paste}.
|
{format_text(Paste, Lang), Req, Paste}.
|
||||||
|
|
||||||
% Private
|
% Private
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,9 @@ content_types_provided(Req, State) ->
|
||||||
], Req, State}.
|
], Req, State}.
|
||||||
|
|
||||||
streaming_csv(Req, Table) ->
|
streaming_csv(Req, Table) ->
|
||||||
{N, Req1} = cowboy_req:binding(v1, Req, 1),
|
N = cowboy_req:binding(v1, Req, 1),
|
||||||
MS = [{{'$1', '$2', '$3'}, [{'==', '$2', N}], ['$$']}],
|
MS = [{{'$1', '$2', '$3'}, [{'==', '$2', N}], ['$$']}],
|
||||||
{{stream, result_streamer(Table, MS)}, Req1, Table}.
|
{{stream, result_streamer(Table, MS)}, Req, Table}.
|
||||||
|
|
||||||
result_streamer(Table, MS) ->
|
result_streamer(Table, MS) ->
|
||||||
fun (Socket, Transport) ->
|
fun (Socket, Transport) ->
|
||||||
|
|
|
@ -11,7 +11,7 @@ init(_Transport, Req, []) ->
|
||||||
{ok, Req, undefined}.
|
{ok, Req, undefined}.
|
||||||
|
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
{ok, Req2} = cowboy_req:reply(200, [
|
Req2 = cowboy_req:reply(200, [
|
||||||
{<<"content-type">>, <<"text/plain">>}
|
{<<"content-type">>, <<"text/plain">>}
|
||||||
], <<"Hello world!">>, Req),
|
], <<"Hello world!">>, Req),
|
||||||
{ok, Req2, State}.
|
{ok, Req2, State}.
|
||||||
|
|
|
@ -12,14 +12,14 @@ execute(Req, Env) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
redirect_directory(Req, Env) ->
|
redirect_directory(Req, Env) ->
|
||||||
{Path, Req1} = cowboy_req:path_info(Req),
|
Path = cowboy_req:path_info(Req),
|
||||||
Path1 = << <<S/binary, $/>> || S <- Path >>,
|
Path1 = << <<S/binary, $/>> || S <- Path >>,
|
||||||
{handler_opts, {_, _, _, Extra}} = lists:keyfind(handler_opts, 1, Env),
|
{handler_opts, {_, _, _, Extra}} = lists:keyfind(handler_opts, 1, Env),
|
||||||
{dir_handler, DirHandler} = lists:keyfind(dir_handler, 1, Extra),
|
{dir_handler, DirHandler} = lists:keyfind(dir_handler, 1, Extra),
|
||||||
FullPath = resource_path(Path1),
|
FullPath = resource_path(Path1),
|
||||||
case valid_path(Path) and filelib:is_dir(FullPath) of
|
case valid_path(Path) and filelib:is_dir(FullPath) of
|
||||||
true -> handle_directory(Req1, Env, Path1, FullPath, DirHandler);
|
true -> handle_directory(Req, Env, Path1, FullPath, DirHandler);
|
||||||
false -> {ok, Req1, Env}
|
false -> {ok, Req, Env}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
handle_directory(Req, Env, Prefix, Path, DirHandler) ->
|
handle_directory(Req, Env, Prefix, Path, DirHandler) ->
|
||||||
|
|
|
@ -20,6 +20,11 @@
|
||||||
-export([stop_listener/1]).
|
-export([stop_listener/1]).
|
||||||
-export([set_env/3]).
|
-export([set_env/3]).
|
||||||
|
|
||||||
|
-type fields() :: [atom()
|
||||||
|
| {atom(), cowboy_constraints:constraint() | [cowboy_constraints:constraint()]}
|
||||||
|
| {atom(), cowboy_constraints:constraint() | [cowboy_constraints:constraint()], any()}].
|
||||||
|
-export_type([fields/0]).
|
||||||
|
|
||||||
-type http_headers() :: [{binary(), iodata()}].
|
-type http_headers() :: [{binary(), iodata()}].
|
||||||
-export_type([http_headers/0]).
|
-export_type([http_headers/0]).
|
||||||
|
|
||||||
|
|
60
src/cowboy_constraints.erl
Normal file
60
src/cowboy_constraints.erl
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
%% Copyright (c) 2014, Loïc Hoguin <essen@ninenines.eu>
|
||||||
|
%%
|
||||||
|
%% Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
%% purpose with or without fee is hereby granted, provided that the above
|
||||||
|
%% copyright notice and this permission notice appear in all copies.
|
||||||
|
%%
|
||||||
|
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
-module(cowboy_constraints).
|
||||||
|
|
||||||
|
-export([validate/2]).
|
||||||
|
|
||||||
|
-type constraint() :: int | nonempty | fun().
|
||||||
|
-export_type([constraint/0]).
|
||||||
|
|
||||||
|
-spec validate(binary(), [constraint()]) -> true | {true, any()} | false.
|
||||||
|
validate(Value, [Constraint]) ->
|
||||||
|
apply_constraint(Value, Constraint);
|
||||||
|
validate(Value, Constraints) when is_list(Constraints) ->
|
||||||
|
validate_list(Value, Constraints, original);
|
||||||
|
validate(Value, Constraint) ->
|
||||||
|
apply_constraint(Value, Constraint).
|
||||||
|
|
||||||
|
validate_list(_, [], original) ->
|
||||||
|
true;
|
||||||
|
validate_list(Value, [], modified) ->
|
||||||
|
{true, Value};
|
||||||
|
validate_list(Value, [Constraint|Tail], State) ->
|
||||||
|
case apply_constraint(Value, Constraint) of
|
||||||
|
true ->
|
||||||
|
validate_list(Value, Tail, State);
|
||||||
|
{true, Value2} ->
|
||||||
|
validate_list(Value2, Tail, modified);
|
||||||
|
false ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% @todo {int, From, To}, etc.
|
||||||
|
apply_constraint(Value, int) ->
|
||||||
|
int(Value);
|
||||||
|
apply_constraint(Value, nonempty) ->
|
||||||
|
nonempty(Value);
|
||||||
|
apply_constraint(Value, F) when is_function(F) ->
|
||||||
|
F(Value).
|
||||||
|
|
||||||
|
%% Constraint functions.
|
||||||
|
|
||||||
|
int(Value) when is_binary(Value) ->
|
||||||
|
try {true, list_to_integer(binary_to_list(Value))}
|
||||||
|
catch _:_ -> false
|
||||||
|
end.
|
||||||
|
|
||||||
|
nonempty(<<>>) -> false;
|
||||||
|
nonempty(Value) when is_binary(Value) -> true.
|
|
@ -852,9 +852,9 @@ parameterized_tokens_param(Data, Fun) ->
|
||||||
%% Decoding.
|
%% Decoding.
|
||||||
|
|
||||||
%% @todo Move this to cowlib too I suppose. :-)
|
%% @todo Move this to cowlib too I suppose. :-)
|
||||||
-spec ce_identity(binary()) -> {ok, binary()}.
|
-spec ce_identity(Data) -> Data when Data::binary().
|
||||||
ce_identity(Data) ->
|
ce_identity(Data) ->
|
||||||
{ok, Data}.
|
Data.
|
||||||
|
|
||||||
%% Tests.
|
%% Tests.
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,8 @@
|
||||||
-export([path/1]).
|
-export([path/1]).
|
||||||
-export([path_info/1]).
|
-export([path_info/1]).
|
||||||
-export([qs/1]).
|
-export([qs/1]).
|
||||||
-export([qs_val/2]).
|
-export([parse_qs/1]).
|
||||||
-export([qs_val/3]).
|
-export([match_qs/2]).
|
||||||
-export([qs_vals/1]).
|
|
||||||
-export([host_url/1]).
|
-export([host_url/1]).
|
||||||
-export([url/1]).
|
-export([url/1]).
|
||||||
-export([binding/2]).
|
-export([binding/2]).
|
||||||
|
@ -39,9 +38,8 @@
|
||||||
-export([headers/1]).
|
-export([headers/1]).
|
||||||
-export([parse_header/2]).
|
-export([parse_header/2]).
|
||||||
-export([parse_header/3]).
|
-export([parse_header/3]).
|
||||||
-export([cookie/2]).
|
-export([parse_cookies/1]).
|
||||||
-export([cookie/3]).
|
-export([match_cookies/2]).
|
||||||
-export([cookies/1]).
|
|
||||||
-export([meta/2]).
|
-export([meta/2]).
|
||||||
-export([meta/3]).
|
-export([meta/3]).
|
||||||
-export([set_meta/3]).
|
-export([set_meta/3]).
|
||||||
|
@ -94,9 +92,7 @@
|
||||||
-type cookie_opts() :: cow_cookie:cookie_opts().
|
-type cookie_opts() :: cow_cookie:cookie_opts().
|
||||||
-export_type([cookie_opts/0]).
|
-export_type([cookie_opts/0]).
|
||||||
|
|
||||||
-type content_decode_fun() :: fun((binary())
|
-type content_decode_fun() :: fun((binary()) -> binary()).
|
||||||
-> {ok, binary()}
|
|
||||||
| {error, atom()}).
|
|
||||||
-type transfer_decode_fun() :: fun((binary(), any())
|
-type transfer_decode_fun() :: fun((binary(), any())
|
||||||
-> cow_http_te:decode_ret()).
|
-> cow_http_te:decode_ret()).
|
||||||
|
|
||||||
|
@ -109,7 +105,7 @@
|
||||||
-export_type([body_opts/0]).
|
-export_type([body_opts/0]).
|
||||||
|
|
||||||
-type resp_body_fun() :: fun((any(), module()) -> ok).
|
-type resp_body_fun() :: fun((any(), module()) -> ok).
|
||||||
-type send_chunk_fun() :: fun((iodata()) -> ok | {error, atom()}).
|
-type send_chunk_fun() :: fun((iodata()) -> ok).
|
||||||
-type resp_chunked_fun() :: fun((send_chunk_fun()) -> ok).
|
-type resp_chunked_fun() :: fun((send_chunk_fun()) -> ok).
|
||||||
|
|
||||||
-record(http_req, {
|
-record(http_req, {
|
||||||
|
@ -129,11 +125,8 @@
|
||||||
path = undefined :: binary(),
|
path = undefined :: binary(),
|
||||||
path_info = undefined :: undefined | cowboy_router:tokens(),
|
path_info = undefined :: undefined | cowboy_router:tokens(),
|
||||||
qs = undefined :: binary(),
|
qs = undefined :: binary(),
|
||||||
qs_vals = undefined :: undefined | list({binary(), binary() | true}),
|
|
||||||
bindings = undefined :: undefined | cowboy_router:bindings(),
|
bindings = undefined :: undefined | cowboy_router:bindings(),
|
||||||
headers = [] :: cowboy:http_headers(),
|
headers = [] :: cowboy:http_headers(),
|
||||||
p_headers = [] :: [any()],
|
|
||||||
cookies = undefined :: undefined | [{binary(), binary()}],
|
|
||||||
meta = [] :: [{atom(), any()}],
|
meta = [] :: [{atom(), any()}],
|
||||||
|
|
||||||
%% Request body.
|
%% Request body.
|
||||||
|
@ -179,89 +172,67 @@ new(Socket, Transport, Peer, Method, Path, Query,
|
||||||
false ->
|
false ->
|
||||||
Req#http_req{connection=close};
|
Req#http_req{connection=close};
|
||||||
true ->
|
true ->
|
||||||
case lists:keyfind(<<"connection">>, 1, Headers) of
|
case parse_header(<<"connection">>, Req) of
|
||||||
false ->
|
undefined ->
|
||||||
case Version of
|
case Version of
|
||||||
'HTTP/1.1' -> Req; %% keepalive
|
'HTTP/1.1' -> Req; %% keepalive
|
||||||
'HTTP/1.0' -> Req#http_req{connection=close}
|
'HTTP/1.0' -> Req#http_req{connection=close}
|
||||||
end;
|
end;
|
||||||
{_, ConnectionHeader} ->
|
Tokens ->
|
||||||
Tokens = cow_http_hd:parse_connection(ConnectionHeader),
|
|
||||||
Connection = connection_to_atom(Tokens),
|
Connection = connection_to_atom(Tokens),
|
||||||
Req#http_req{connection=Connection,
|
Req#http_req{connection=Connection}
|
||||||
p_headers=[{<<"connection">>, Tokens}]}
|
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec method(Req) -> {binary(), Req} when Req::req().
|
-spec method(req()) -> binary().
|
||||||
method(Req) ->
|
method(Req) ->
|
||||||
{Req#http_req.method, Req}.
|
Req#http_req.method.
|
||||||
|
|
||||||
-spec version(Req) -> {cowboy:http_version(), Req} when Req::req().
|
-spec version(req()) -> cowboy:http_version().
|
||||||
version(Req) ->
|
version(Req) ->
|
||||||
{Req#http_req.version, Req}.
|
Req#http_req.version.
|
||||||
|
|
||||||
-spec peer(Req)
|
-spec peer(req()) -> {inet:ip_address(), inet:port_number()}.
|
||||||
-> {{inet:ip_address(), inet:port_number()}, Req}
|
|
||||||
when Req::req().
|
|
||||||
peer(Req) ->
|
peer(Req) ->
|
||||||
{Req#http_req.peer, Req}.
|
Req#http_req.peer.
|
||||||
|
|
||||||
-spec host(Req) -> {binary(), Req} when Req::req().
|
-spec host(req()) -> binary().
|
||||||
host(Req) ->
|
host(Req) ->
|
||||||
{Req#http_req.host, Req}.
|
Req#http_req.host.
|
||||||
|
|
||||||
-spec host_info(Req)
|
-spec host_info(req()) -> cowboy_router:tokens() | undefined.
|
||||||
-> {cowboy_router:tokens() | undefined, Req} when Req::req().
|
|
||||||
host_info(Req) ->
|
host_info(Req) ->
|
||||||
{Req#http_req.host_info, Req}.
|
Req#http_req.host_info.
|
||||||
|
|
||||||
-spec port(Req) -> {inet:port_number(), Req} when Req::req().
|
-spec port(req()) -> inet:port_number().
|
||||||
port(Req) ->
|
port(Req) ->
|
||||||
{Req#http_req.port, Req}.
|
Req#http_req.port.
|
||||||
|
|
||||||
-spec path(Req) -> {binary(), Req} when Req::req().
|
-spec path(req()) -> binary().
|
||||||
path(Req) ->
|
path(Req) ->
|
||||||
{Req#http_req.path, Req}.
|
Req#http_req.path.
|
||||||
|
|
||||||
-spec path_info(Req)
|
-spec path_info(req()) -> cowboy_router:tokens() | undefined.
|
||||||
-> {cowboy_router:tokens() | undefined, Req} when Req::req().
|
|
||||||
path_info(Req) ->
|
path_info(Req) ->
|
||||||
{Req#http_req.path_info, Req}.
|
Req#http_req.path_info.
|
||||||
|
|
||||||
-spec qs(Req) -> {binary(), Req} when Req::req().
|
-spec qs(req()) -> binary().
|
||||||
qs(Req) ->
|
qs(Req) ->
|
||||||
{Req#http_req.qs, Req}.
|
Req#http_req.qs.
|
||||||
|
|
||||||
-spec qs_val(binary(), Req)
|
-spec parse_qs(req()) -> [{binary(), binary() | true}].
|
||||||
-> {binary() | true | undefined, Req} when Req::req().
|
parse_qs(#http_req{qs=Qs}) ->
|
||||||
qs_val(Name, Req) when is_binary(Name) ->
|
cow_qs:parse_qs(Qs).
|
||||||
qs_val(Name, Req, undefined).
|
|
||||||
|
|
||||||
-spec qs_val(binary(), Req, Default)
|
-spec match_qs(req(), cowboy:fields()) -> map().
|
||||||
-> {binary() | true | Default, Req} when Req::req(), Default::any().
|
match_qs(Req, Fields) ->
|
||||||
qs_val(Name, Req=#http_req{qs=RawQs, qs_vals=undefined}, Default)
|
filter(kvlist_to_map(parse_qs(Req), Fields), Fields).
|
||||||
when is_binary(Name) ->
|
|
||||||
QsVals = cow_qs:parse_qs(RawQs),
|
|
||||||
qs_val(Name, Req#http_req{qs_vals=QsVals}, Default);
|
|
||||||
qs_val(Name, Req, Default) ->
|
|
||||||
case lists:keyfind(Name, 1, Req#http_req.qs_vals) of
|
|
||||||
{Name, Value} -> {Value, Req};
|
|
||||||
false -> {Default, Req}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec qs_vals(Req) -> {list({binary(), binary() | true}), Req} when Req::req().
|
|
||||||
qs_vals(Req=#http_req{qs=RawQs, qs_vals=undefined}) ->
|
|
||||||
QsVals = cow_qs:parse_qs(RawQs),
|
|
||||||
qs_vals(Req#http_req{qs_vals=QsVals});
|
|
||||||
qs_vals(Req=#http_req{qs_vals=QsVals}) ->
|
|
||||||
{QsVals, Req}.
|
|
||||||
|
|
||||||
%% The URL includes the scheme, host and port only.
|
%% The URL includes the scheme, host and port only.
|
||||||
-spec host_url(Req) -> {undefined | binary(), Req} when Req::req().
|
-spec host_url(req()) -> undefined | binary().
|
||||||
host_url(Req=#http_req{port=undefined}) ->
|
host_url(#http_req{port=undefined}) ->
|
||||||
{undefined, Req};
|
undefined;
|
||||||
host_url(Req=#http_req{transport=Transport, host=Host, port=Port}) ->
|
host_url(#http_req{transport=Transport, host=Host, port=Port}) ->
|
||||||
TransportName = Transport:name(),
|
TransportName = Transport:name(),
|
||||||
Secure = case TransportName of
|
Secure = case TransportName of
|
||||||
ssl -> <<"s">>;
|
ssl -> <<"s">>;
|
||||||
|
@ -272,108 +243,101 @@ host_url(Req=#http_req{transport=Transport, host=Host, port=Port}) ->
|
||||||
{tcp, 80} -> <<>>;
|
{tcp, 80} -> <<>>;
|
||||||
_ -> << ":", (integer_to_binary(Port))/binary >>
|
_ -> << ":", (integer_to_binary(Port))/binary >>
|
||||||
end,
|
end,
|
||||||
{<< "http", Secure/binary, "://", Host/binary, PortBin/binary >>, Req}.
|
<< "http", Secure/binary, "://", Host/binary, PortBin/binary >>.
|
||||||
|
|
||||||
%% The URL includes the scheme, host, port, path and query string.
|
%% The URL includes the scheme, host, port, path and query string.
|
||||||
-spec url(Req) -> {undefined | binary(), Req} when Req::req().
|
-spec url(req()) -> undefined | binary().
|
||||||
url(Req=#http_req{}) ->
|
url(Req=#http_req{}) ->
|
||||||
{HostURL, Req2} = host_url(Req),
|
HostURL = host_url(Req),
|
||||||
url(HostURL, Req2).
|
url(Req, HostURL).
|
||||||
|
|
||||||
url(undefined, Req=#http_req{}) ->
|
url(_, undefined) ->
|
||||||
{undefined, Req};
|
undefined;
|
||||||
url(HostURL, Req=#http_req{path=Path, qs=QS}) ->
|
url(#http_req{path=Path, qs=QS}, HostURL) ->
|
||||||
QS2 = case QS of
|
QS2 = case QS of
|
||||||
<<>> -> <<>>;
|
<<>> -> <<>>;
|
||||||
_ -> << "?", QS/binary >>
|
_ -> << "?", QS/binary >>
|
||||||
end,
|
end,
|
||||||
{<< HostURL/binary, Path/binary, QS2/binary >>, Req}.
|
<< HostURL/binary, Path/binary, QS2/binary >>.
|
||||||
|
|
||||||
-spec binding(atom(), Req) -> {any() | undefined, Req} when Req::req().
|
-spec binding(atom(), req()) -> any() | undefined.
|
||||||
binding(Name, Req) when is_atom(Name) ->
|
binding(Name, Req) ->
|
||||||
binding(Name, Req, undefined).
|
binding(Name, Req, undefined).
|
||||||
|
|
||||||
-spec binding(atom(), Req, Default)
|
-spec binding(atom(), req(), Default) -> any() | Default when Default::any().
|
||||||
-> {any() | Default, Req} when Req::req(), Default::any().
|
|
||||||
binding(Name, Req, Default) when is_atom(Name) ->
|
binding(Name, Req, Default) when is_atom(Name) ->
|
||||||
case lists:keyfind(Name, 1, Req#http_req.bindings) of
|
case lists:keyfind(Name, 1, Req#http_req.bindings) of
|
||||||
{Name, Value} -> {Value, Req};
|
{_, Value} -> Value;
|
||||||
false -> {Default, Req}
|
false -> Default
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec bindings(Req) -> {[{atom(), any()}], Req} when Req::req().
|
-spec bindings(req()) -> [{atom(), any()}].
|
||||||
bindings(Req) ->
|
bindings(Req) ->
|
||||||
{Req#http_req.bindings, Req}.
|
Req#http_req.bindings.
|
||||||
|
|
||||||
-spec header(binary(), Req)
|
-spec header(binary(), req()) -> binary() | undefined.
|
||||||
-> {binary() | undefined, Req} when Req::req().
|
|
||||||
header(Name, Req) ->
|
header(Name, Req) ->
|
||||||
header(Name, Req, undefined).
|
header(Name, Req, undefined).
|
||||||
|
|
||||||
-spec header(binary(), Req, Default)
|
-spec header(binary(), req(), Default) -> binary() | Default when Default::any().
|
||||||
-> {binary() | Default, Req} when Req::req(), Default::any().
|
|
||||||
header(Name, Req, Default) ->
|
header(Name, Req, Default) ->
|
||||||
case lists:keyfind(Name, 1, Req#http_req.headers) of
|
case lists:keyfind(Name, 1, Req#http_req.headers) of
|
||||||
{Name, Value} -> {Value, Req};
|
{Name, Value} -> Value;
|
||||||
false -> {Default, Req}
|
false -> Default
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec headers(Req) -> {cowboy:http_headers(), Req} when Req::req().
|
-spec headers(req()) -> cowboy:http_headers().
|
||||||
headers(Req) ->
|
headers(Req) ->
|
||||||
{Req#http_req.headers, Req}.
|
Req#http_req.headers.
|
||||||
|
|
||||||
-spec parse_header(binary(), Req)
|
-spec parse_header(binary(), Req) -> any() when Req::req().
|
||||||
-> {ok, any(), Req} | {undefined, binary(), Req}
|
parse_header(Name = <<"content-length">>, Req) ->
|
||||||
| {error, badarg} when Req::req().
|
parse_header(Name, Req, 0);
|
||||||
parse_header(Name, Req=#http_req{p_headers=PHeaders}) ->
|
parse_header(Name = <<"cookie">>, Req) ->
|
||||||
case lists:keyfind(Name, 1, PHeaders) of
|
parse_header(Name, Req, []);
|
||||||
false -> parse_header(Name, Req, parse_header_default(Name));
|
parse_header(Name = <<"transfer-encoding">>, Req) ->
|
||||||
{Name, Value} -> {ok, Value, Req}
|
parse_header(Name, Req, [<<"identity">>]);
|
||||||
end.
|
parse_header(Name, Req) ->
|
||||||
|
parse_header(Name, Req, undefined).
|
||||||
|
|
||||||
-spec parse_header_default(binary()) -> any().
|
-spec parse_header(binary(), Req, any()) -> any() when Req::req().
|
||||||
parse_header_default(<<"transfer-encoding">>) -> [<<"identity">>];
|
|
||||||
parse_header_default(_Name) -> undefined.
|
|
||||||
|
|
||||||
-spec parse_header(binary(), Req, any())
|
|
||||||
-> {ok, any(), Req} | {undefined, binary(), Req}
|
|
||||||
| {error, badarg} when Req::req().
|
|
||||||
parse_header(Name = <<"accept">>, Req, Default) ->
|
parse_header(Name = <<"accept">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default,
|
parse_header(Name, Req, Default, fun (Value) ->
|
||||||
fun (Value) ->
|
cowboy_http:list(Value, fun cowboy_http:media_range/2) end);
|
||||||
cowboy_http:list(Value, fun cowboy_http:media_range/2)
|
|
||||||
end);
|
|
||||||
parse_header(Name = <<"accept-charset">>, Req, Default) ->
|
parse_header(Name = <<"accept-charset">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default,
|
parse_header(Name, Req, Default, fun (Value) ->
|
||||||
fun (Value) ->
|
cowboy_http:nonempty_list(Value, fun cowboy_http:conneg/2) end);
|
||||||
cowboy_http:nonempty_list(Value, fun cowboy_http:conneg/2)
|
|
||||||
end);
|
|
||||||
parse_header(Name = <<"accept-encoding">>, Req, Default) ->
|
parse_header(Name = <<"accept-encoding">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default,
|
parse_header(Name, Req, Default, fun (Value) ->
|
||||||
fun (Value) ->
|
cowboy_http:list(Value, fun cowboy_http:conneg/2) end);
|
||||||
cowboy_http:list(Value, fun cowboy_http:conneg/2)
|
|
||||||
end);
|
|
||||||
parse_header(Name = <<"accept-language">>, Req, Default) ->
|
parse_header(Name = <<"accept-language">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default,
|
parse_header(Name, Req, Default, fun (Value) ->
|
||||||
fun (Value) ->
|
cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2) end);
|
||||||
cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2)
|
|
||||||
end);
|
|
||||||
parse_header(Name = <<"authorization">>, Req, Default) ->
|
parse_header(Name = <<"authorization">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default,
|
parse_header(Name, Req, Default, fun (Value) ->
|
||||||
fun (Value) ->
|
cowboy_http:token_ci(Value, fun cowboy_http:authorization/2) end);
|
||||||
cowboy_http:token_ci(Value, fun cowboy_http:authorization/2)
|
parse_header(Name = <<"connection">>, Req, Default) ->
|
||||||
end);
|
case header(Name, Req) of
|
||||||
|
undefined -> Default;
|
||||||
|
Value -> cow_http_hd:parse_connection(Value)
|
||||||
|
end;
|
||||||
parse_header(Name = <<"content-length">>, Req, Default) ->
|
parse_header(Name = <<"content-length">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default, fun cow_http_hd:parse_content_length/1);
|
case header(Name, Req) of
|
||||||
|
undefined -> Default;
|
||||||
|
Value -> cow_http_hd:parse_content_length(Value)
|
||||||
|
end;
|
||||||
parse_header(Name = <<"content-type">>, Req, Default) ->
|
parse_header(Name = <<"content-type">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default, fun cowboy_http:content_type/1);
|
parse_header(Name, Req, Default, fun cowboy_http:content_type/1);
|
||||||
parse_header(Name = <<"cookie">>, Req, Default) ->
|
parse_header(Name = <<"cookie">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default, fun cow_cookie:parse_cookie/1);
|
case header(Name, Req) of
|
||||||
|
undefined -> Default;
|
||||||
|
%% Flash player incorrectly sends an empty Cookie header.
|
||||||
|
<<>> -> Default;
|
||||||
|
Value -> cow_cookie:parse_cookie(Value)
|
||||||
|
end;
|
||||||
parse_header(Name = <<"expect">>, Req, Default) ->
|
parse_header(Name = <<"expect">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default,
|
parse_header(Name, Req, Default, fun (Value) ->
|
||||||
fun (Value) ->
|
cowboy_http:nonempty_list(Value, fun cowboy_http:expectation/2) end);
|
||||||
cowboy_http:nonempty_list(Value, fun cowboy_http:expectation/2)
|
|
||||||
end);
|
|
||||||
parse_header(Name, Req, Default)
|
parse_header(Name, Req, Default)
|
||||||
when Name =:= <<"if-match">>;
|
when Name =:= <<"if-match">>;
|
||||||
Name =:= <<"if-none-match">> ->
|
Name =:= <<"if-none-match">> ->
|
||||||
|
@ -387,80 +351,51 @@ parse_header(Name = <<"range">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default)
|
parse_header(Name, Req, Default)
|
||||||
when Name =:= <<"sec-websocket-protocol">>;
|
when Name =:= <<"sec-websocket-protocol">>;
|
||||||
Name =:= <<"x-forwarded-for">> ->
|
Name =:= <<"x-forwarded-for">> ->
|
||||||
parse_header(Name, Req, Default,
|
parse_header(Name, Req, Default, fun (Value) ->
|
||||||
fun (Value) ->
|
cowboy_http:nonempty_list(Value, fun cowboy_http:token/2) end);
|
||||||
cowboy_http:nonempty_list(Value, fun cowboy_http:token/2)
|
|
||||||
end);
|
|
||||||
parse_header(Name = <<"transfer-encoding">>, Req, Default) ->
|
parse_header(Name = <<"transfer-encoding">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default, fun cow_http_hd:parse_transfer_encoding/1);
|
case header(Name, Req) of
|
||||||
|
undefined -> Default;
|
||||||
|
Value -> cow_http_hd:parse_transfer_encoding(Value)
|
||||||
|
end;
|
||||||
%% @todo Product version.
|
%% @todo Product version.
|
||||||
parse_header(Name = <<"upgrade">>, Req, Default) ->
|
parse_header(Name = <<"upgrade">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default,
|
parse_header(Name, Req, Default, fun (Value) ->
|
||||||
fun (Value) ->
|
cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) end);
|
||||||
cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
|
|
||||||
end);
|
|
||||||
parse_header(Name = <<"sec-websocket-extensions">>, Req, Default) ->
|
parse_header(Name = <<"sec-websocket-extensions">>, Req, Default) ->
|
||||||
parse_header(Name, Req, Default, fun cowboy_http:parameterized_tokens/1);
|
parse_header(Name, Req, Default, fun cowboy_http:parameterized_tokens/1).
|
||||||
parse_header(Name, Req, Default) ->
|
|
||||||
{Value, Req2} = header(Name, Req, Default),
|
|
||||||
{undefined, Value, Req2}.
|
|
||||||
|
|
||||||
parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) ->
|
%% @todo Remove this function when everything moved to cowlib.
|
||||||
|
parse_header(Name, Req, Default, ParseFun) ->
|
||||||
case header(Name, Req) of
|
case header(Name, Req) of
|
||||||
{undefined, Req2} ->
|
undefined ->
|
||||||
{ok, Default, Req2#http_req{p_headers=[{Name, Default}|PHeaders]}};
|
Default;
|
||||||
{Value, Req2} ->
|
Value ->
|
||||||
case Fun(Value) of
|
case ParseFun(Value) of
|
||||||
{error, badarg} ->
|
{error, badarg} ->
|
||||||
{error, badarg};
|
error(badarg);
|
||||||
P ->
|
ParsedValue ->
|
||||||
{ok, P, Req2#http_req{p_headers=[{Name, P}|PHeaders]}}
|
ParsedValue
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec cookie(binary(), Req)
|
-spec parse_cookies(req()) -> [{binary(), binary()}].
|
||||||
-> {binary() | undefined, Req} when Req::req().
|
parse_cookies(Req) ->
|
||||||
cookie(Name, Req) when is_binary(Name) ->
|
parse_header(<<"cookie">>, Req).
|
||||||
cookie(Name, Req, undefined).
|
|
||||||
|
|
||||||
-spec cookie(binary(), Req, Default)
|
-spec match_cookies(req(), cowboy:fields()) -> map().
|
||||||
-> {binary() | Default, Req} when Req::req(), Default::any().
|
match_cookies(Req, Fields) ->
|
||||||
cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) ->
|
filter(kvlist_to_map(parse_cookies(Req), Fields), Fields).
|
||||||
case parse_header(<<"cookie">>, Req) of
|
|
||||||
{ok, undefined, Req2} ->
|
|
||||||
{Default, Req2#http_req{cookies=[]}};
|
|
||||||
{ok, Cookies, Req2} ->
|
|
||||||
cookie(Name, Req2#http_req{cookies=Cookies}, Default)
|
|
||||||
end;
|
|
||||||
cookie(Name, Req, Default) ->
|
|
||||||
case lists:keyfind(Name, 1, Req#http_req.cookies) of
|
|
||||||
{Name, Value} -> {Value, Req};
|
|
||||||
false -> {Default, Req}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec cookies(Req) -> {list({binary(), binary()}), Req} when Req::req().
|
-spec meta(atom(), req()) -> any() | undefined.
|
||||||
cookies(Req=#http_req{cookies=undefined}) ->
|
|
||||||
case parse_header(<<"cookie">>, Req) of
|
|
||||||
{ok, undefined, Req2} ->
|
|
||||||
{[], Req2#http_req{cookies=[]}};
|
|
||||||
{ok, Cookies, Req2} ->
|
|
||||||
cookies(Req2#http_req{cookies=Cookies});
|
|
||||||
%% Flash player incorrectly sends an empty Cookie header.
|
|
||||||
{error, badarg} ->
|
|
||||||
{[], Req#http_req{cookies=[]}}
|
|
||||||
end;
|
|
||||||
cookies(Req=#http_req{cookies=Cookies}) ->
|
|
||||||
{Cookies, Req}.
|
|
||||||
|
|
||||||
-spec meta(atom(), Req) -> {any() | undefined, Req} when Req::req().
|
|
||||||
meta(Name, Req) ->
|
meta(Name, Req) ->
|
||||||
meta(Name, Req, undefined).
|
meta(Name, Req, undefined).
|
||||||
|
|
||||||
-spec meta(atom(), Req, any()) -> {any(), Req} when Req::req().
|
-spec meta(atom(), req(), any()) -> any().
|
||||||
meta(Name, Req, Default) ->
|
meta(Name, Req, Default) ->
|
||||||
case lists:keyfind(Name, 1, Req#http_req.meta) of
|
case lists:keyfind(Name, 1, Req#http_req.meta) of
|
||||||
{Name, Value} -> {Value, Req};
|
{Name, Value} -> Value;
|
||||||
false -> {Default, Req}
|
false -> Default
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec set_meta(atom(), any(), Req) -> Req when Req::req().
|
-spec set_meta(atom(), any(), Req) -> Req when Req::req().
|
||||||
|
@ -482,37 +417,31 @@ has_body(Req) ->
|
||||||
|
|
||||||
%% The length may not be known if Transfer-Encoding is not identity,
|
%% The length may not be known if Transfer-Encoding is not identity,
|
||||||
%% and the body hasn't been read at the time of the call.
|
%% and the body hasn't been read at the time of the call.
|
||||||
-spec body_length(Req) -> {undefined | non_neg_integer(), Req} when Req::req().
|
-spec body_length(req()) -> undefined | non_neg_integer().
|
||||||
body_length(Req) ->
|
body_length(Req) ->
|
||||||
case parse_header(<<"transfer-encoding">>, Req) of
|
case parse_header(<<"transfer-encoding">>, Req) of
|
||||||
{ok, [<<"identity">>], Req2} ->
|
[<<"identity">>] ->
|
||||||
{ok, Length, Req3} = parse_header(<<"content-length">>, Req2, 0),
|
parse_header(<<"content-length">>, Req);
|
||||||
{Length, Req3};
|
_ ->
|
||||||
{ok, _, Req2} ->
|
undefined
|
||||||
{undefined, Req2}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec body(Req)
|
-spec body(Req) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req().
|
||||||
-> {ok, binary(), Req} | {more, binary(), Req}
|
|
||||||
| {error, atom()} when Req::req().
|
|
||||||
body(Req) ->
|
body(Req) ->
|
||||||
body(Req, []).
|
body(Req, []).
|
||||||
|
|
||||||
-spec body(Req, body_opts())
|
-spec body(Req, body_opts()) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req().
|
||||||
-> {ok, binary(), Req} | {more, binary(), Req}
|
|
||||||
| {error, atom()} when Req::req().
|
|
||||||
body(Req=#http_req{body_state=waiting}, Opts) ->
|
body(Req=#http_req{body_state=waiting}, Opts) ->
|
||||||
%% Send a 100 continue if needed (enabled by default).
|
%% Send a 100 continue if needed (enabled by default).
|
||||||
Req1 = case lists:keyfind(continue, 1, Opts) of
|
case lists:keyfind(continue, 1, Opts) of
|
||||||
{_, false} ->
|
{_, false} ->
|
||||||
Req;
|
ok;
|
||||||
_ ->
|
_ ->
|
||||||
{ok, ExpectHeader, Req0} = parse_header(<<"expect">>, Req),
|
ExpectHeader = parse_header(<<"expect">>, Req),
|
||||||
ok = case ExpectHeader of
|
ok = case ExpectHeader of
|
||||||
[<<"100-continue">>] -> continue(Req0);
|
[<<"100-continue">>] -> continue(Req);
|
||||||
_ -> ok
|
_ -> ok
|
||||||
end,
|
end
|
||||||
Req0
|
|
||||||
end,
|
end,
|
||||||
%% Initialize body streaming state.
|
%% Initialize body streaming state.
|
||||||
CFun = case lists:keyfind(content_decode, 1, Opts) of
|
CFun = case lists:keyfind(content_decode, 1, Opts) of
|
||||||
|
@ -523,23 +452,22 @@ body(Req=#http_req{body_state=waiting}, Opts) ->
|
||||||
end,
|
end,
|
||||||
case lists:keyfind(transfer_decode, 1, Opts) of
|
case lists:keyfind(transfer_decode, 1, Opts) of
|
||||||
false ->
|
false ->
|
||||||
case parse_header(<<"transfer-encoding">>, Req1) of
|
case parse_header(<<"transfer-encoding">>, Req) of
|
||||||
{ok, [<<"chunked">>], Req2} ->
|
[<<"chunked">>] ->
|
||||||
body(Req2#http_req{body_state={stream, 0,
|
body(Req#http_req{body_state={stream, 0,
|
||||||
fun cow_http_te:stream_chunked/2, {0, 0}, CFun}}, Opts);
|
fun cow_http_te:stream_chunked/2, {0, 0}, CFun}}, Opts);
|
||||||
{ok, [<<"identity">>], Req2} ->
|
[<<"identity">>] ->
|
||||||
{Len, Req3} = body_length(Req2),
|
case body_length(Req) of
|
||||||
case Len of
|
|
||||||
0 ->
|
0 ->
|
||||||
{ok, <<>>, Req3#http_req{body_state=done}};
|
{ok, <<>>, Req#http_req{body_state=done}};
|
||||||
_ ->
|
Len ->
|
||||||
body(Req3#http_req{body_state={stream, Len,
|
body(Req#http_req{body_state={stream, Len,
|
||||||
fun cow_http_te:stream_identity/2, {0, Len},
|
fun cow_http_te:stream_identity/2, {0, Len},
|
||||||
CFun}}, Opts)
|
CFun}}, Opts)
|
||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
{_, TFun, TState} ->
|
{_, TFun, TState} ->
|
||||||
body(Req1#http_req{body_state={stream, 0,
|
body(Req#http_req{body_state={stream, 0,
|
||||||
TFun, TState, CFun}}, Opts)
|
TFun, TState, CFun}}, Opts)
|
||||||
end;
|
end;
|
||||||
body(Req=#http_req{body_state=done}, _) ->
|
body(Req=#http_req{body_state=done}, _) ->
|
||||||
|
@ -568,27 +496,20 @@ body_loop(Req=#http_req{buffer=Buffer, body_state={stream, Length, _, _, _}},
|
||||||
body_decode(Req, ReadTimeout)
|
body_decode(Req, ReadTimeout)
|
||||||
end,
|
end,
|
||||||
case {Tag, Res} of
|
case {Tag, Res} of
|
||||||
{ok, {ok, Data}} ->
|
{ok, Data} ->
|
||||||
{ok, << Acc/binary, Data/binary >>, Req2};
|
{ok, << Acc/binary, Data/binary >>, Req2};
|
||||||
{more, {ok, Data}} ->
|
{more, Data} ->
|
||||||
Acc2 = << Acc/binary, Data/binary >>,
|
Acc2 = << Acc/binary, Data/binary >>,
|
||||||
case byte_size(Acc2) >= ChunkLength of
|
case byte_size(Acc2) >= ChunkLength of
|
||||||
true -> {more, Acc2, Req2};
|
true -> {more, Acc2, Req2};
|
||||||
false -> body_loop(Req2, ReadTimeout, ReadLength, ChunkLength, Acc2)
|
false -> body_loop(Req2, ReadTimeout, ReadLength, ChunkLength, Acc2)
|
||||||
end;
|
end
|
||||||
_ -> %% Error.
|
|
||||||
Res
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
body_recv(Req=#http_req{transport=Transport, socket=Socket, buffer=Buffer},
|
body_recv(Req=#http_req{transport=Transport, socket=Socket, buffer=Buffer},
|
||||||
ReadTimeout, ReadLength) ->
|
ReadTimeout, ReadLength) ->
|
||||||
case Transport:recv(Socket, ReadLength, ReadTimeout) of
|
{ok, Data} = Transport:recv(Socket, ReadLength, ReadTimeout),
|
||||||
{ok, Data} ->
|
body_decode(Req#http_req{buffer= << Buffer/binary, Data/binary >>}, ReadTimeout).
|
||||||
body_decode(Req#http_req{buffer= << Buffer/binary, Data/binary >>},
|
|
||||||
ReadTimeout);
|
|
||||||
Error = {error, _} ->
|
|
||||||
{error, Error, Req}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% Two decodings happen. First a decoding function is applied to the
|
%% Two decodings happen. First a decoding function is applied to the
|
||||||
%% transferred data, and then another is applied to the actual content.
|
%% transferred data, and then another is applied to the actual content.
|
||||||
|
@ -617,26 +538,20 @@ body_decode(Req=#http_req{buffer=Data, body_state={stream, _,
|
||||||
{more, CDecode(Data2), Req#http_req{body_state={stream, 0,
|
{more, CDecode(Data2), Req#http_req{body_state={stream, 0,
|
||||||
TDecode, TState2, CDecode}, buffer=Rest}};
|
TDecode, TState2, CDecode}, buffer=Rest}};
|
||||||
{done, TotalLength, Rest} ->
|
{done, TotalLength, Rest} ->
|
||||||
{ok, {ok, <<>>}, body_decode_end(Req, TotalLength, Rest)};
|
{ok, <<>>, body_decode_end(Req, TotalLength, Rest)};
|
||||||
{done, Data2, TotalLength, Rest} ->
|
{done, Data2, TotalLength, Rest} ->
|
||||||
{ok, CDecode(Data2), body_decode_end(Req, TotalLength, Rest)}
|
{ok, CDecode(Data2), body_decode_end(Req, TotalLength, Rest)}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
body_decode_end(Req=#http_req{headers=Headers, p_headers=PHeaders},
|
body_decode_end(Req=#http_req{headers=Headers}, TotalLength, Rest) ->
|
||||||
TotalLength, Rest) ->
|
|
||||||
Headers2 = lists:keystore(<<"content-length">>, 1, Headers,
|
Headers2 = lists:keystore(<<"content-length">>, 1, Headers,
|
||||||
{<<"content-length">>, integer_to_binary(TotalLength)}),
|
{<<"content-length">>, integer_to_binary(TotalLength)}),
|
||||||
%% At this point we just assume TEs were all decoded.
|
%% At this point we just assume TEs were all decoded.
|
||||||
Headers3 = lists:keydelete(<<"transfer-encoding">>, 1, Headers2),
|
Headers3 = lists:keydelete(<<"transfer-encoding">>, 1, Headers2),
|
||||||
PHeaders2 = lists:keystore(<<"content-length">>, 1, PHeaders,
|
Req#http_req{buffer=Rest, body_state=done, headers=Headers3}.
|
||||||
{<<"content-length">>, TotalLength}),
|
|
||||||
PHeaders3 = lists:keydelete(<<"transfer-encoding">>, 1, PHeaders2),
|
|
||||||
Req#http_req{buffer=Rest, body_state=done,
|
|
||||||
headers=Headers3, p_headers=PHeaders3}.
|
|
||||||
|
|
||||||
-spec body_qs(Req)
|
-spec body_qs(Req) -> {ok, [{binary(), binary() | true}], Req}
|
||||||
-> {ok, [{binary(), binary() | true}], Req} | {error, atom()}
|
| {badlength, Req} when Req::req().
|
||||||
when Req::req().
|
|
||||||
body_qs(Req) ->
|
body_qs(Req) ->
|
||||||
body_qs(Req, [
|
body_qs(Req, [
|
||||||
{length, 64000},
|
{length, 64000},
|
||||||
|
@ -644,15 +559,13 @@ body_qs(Req) ->
|
||||||
{read_timeout, 5000}]).
|
{read_timeout, 5000}]).
|
||||||
|
|
||||||
-spec body_qs(Req, body_opts()) -> {ok, [{binary(), binary() | true}], Req}
|
-spec body_qs(Req, body_opts()) -> {ok, [{binary(), binary() | true}], Req}
|
||||||
| {badlength, Req} | {error, atom()} when Req::req().
|
| {badlength, Req} when Req::req().
|
||||||
body_qs(Req, Opts) ->
|
body_qs(Req, Opts) ->
|
||||||
case body(Req, Opts) of
|
case body(Req, Opts) of
|
||||||
{ok, Body, Req2} ->
|
{ok, Body, Req2} ->
|
||||||
{ok, cow_qs:parse_qs(Body), Req2};
|
{ok, cow_qs:parse_qs(Body), Req2};
|
||||||
{more, _, Req2} ->
|
{more, _, Req2} ->
|
||||||
{badlength, Req2};
|
{badlength, Req2}
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Multipart API.
|
%% Multipart API.
|
||||||
|
@ -730,10 +643,9 @@ part_body(Buffer, Opts, Req=#http_req{multipart={Boundary, _}}, Acc) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
init_multipart(Req) ->
|
init_multipart(Req) ->
|
||||||
{ok, {<<"multipart">>, _, Params}, Req2}
|
{<<"multipart">>, _, Params} = parse_header(<<"content-type">>, Req),
|
||||||
= parse_header(<<"content-type">>, Req),
|
|
||||||
{_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params),
|
{_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params),
|
||||||
Req2#http_req{multipart={Boundary, <<>>}}.
|
Req#http_req{multipart={Boundary, <<>>}}.
|
||||||
|
|
||||||
stream_multipart(Req=#http_req{body_state=BodyState, multipart={_, <<>>}}, Opts) ->
|
stream_multipart(Req=#http_req{body_state=BodyState, multipart={_, <<>>}}, Opts) ->
|
||||||
true = BodyState =/= done,
|
true = BodyState =/= done,
|
||||||
|
@ -801,18 +713,18 @@ delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) ->
|
||||||
RespHeaders2 = lists:keydelete(Name, 1, RespHeaders),
|
RespHeaders2 = lists:keydelete(Name, 1, RespHeaders),
|
||||||
Req#http_req{resp_headers=RespHeaders2}.
|
Req#http_req{resp_headers=RespHeaders2}.
|
||||||
|
|
||||||
-spec reply(cowboy:http_status(), Req) -> {ok, Req} when Req::req().
|
-spec reply(cowboy:http_status(), Req) -> Req when Req::req().
|
||||||
reply(Status, Req=#http_req{resp_body=Body}) ->
|
reply(Status, Req=#http_req{resp_body=Body}) ->
|
||||||
reply(Status, [], Body, Req).
|
reply(Status, [], Body, Req).
|
||||||
|
|
||||||
-spec reply(cowboy:http_status(), cowboy:http_headers(), Req)
|
-spec reply(cowboy:http_status(), cowboy:http_headers(), Req)
|
||||||
-> {ok, Req} when Req::req().
|
-> Req when Req::req().
|
||||||
reply(Status, Headers, Req=#http_req{resp_body=Body}) ->
|
reply(Status, Headers, Req=#http_req{resp_body=Body}) ->
|
||||||
reply(Status, Headers, Body, Req).
|
reply(Status, Headers, Body, Req).
|
||||||
|
|
||||||
-spec reply(cowboy:http_status(), cowboy:http_headers(),
|
-spec reply(cowboy:http_status(), cowboy:http_headers(),
|
||||||
iodata() | {non_neg_integer() | resp_body_fun()}, Req)
|
iodata() | {non_neg_integer() | resp_body_fun()}, Req)
|
||||||
-> {ok, Req} when Req::req().
|
-> Req when Req::req().
|
||||||
reply(Status, Headers, Body, Req=#http_req{
|
reply(Status, Headers, Body, Req=#http_req{
|
||||||
socket=Socket, transport=Transport,
|
socket=Socket, transport=Transport,
|
||||||
version=Version, connection=Connection,
|
version=Version, connection=Connection,
|
||||||
|
@ -887,13 +799,13 @@ reply(Status, Headers, Body, Req=#http_req{
|
||||||
RespHeaders, HTTP11Headers, Method, iolist_size(Body)),
|
RespHeaders, HTTP11Headers, Method, iolist_size(Body)),
|
||||||
Req2#http_req{connection=RespConn}
|
Req2#http_req{connection=RespConn}
|
||||||
end,
|
end,
|
||||||
{ok, Req3#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}.
|
Req3#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}.
|
||||||
|
|
||||||
reply_may_compress(Status, Headers, Body, Req,
|
reply_may_compress(Status, Headers, Body, Req,
|
||||||
RespHeaders, HTTP11Headers, Method) ->
|
RespHeaders, HTTP11Headers, Method) ->
|
||||||
BodySize = iolist_size(Body),
|
BodySize = iolist_size(Body),
|
||||||
case parse_header(<<"accept-encoding">>, Req) of
|
try parse_header(<<"accept-encoding">>, Req) of
|
||||||
{ok, Encodings, Req2} ->
|
Encodings ->
|
||||||
CanGzip = (BodySize > 300)
|
CanGzip = (BodySize > 300)
|
||||||
andalso (false =:= lists:keyfind(<<"content-encoding">>,
|
andalso (false =:= lists:keyfind(<<"content-encoding">>,
|
||||||
1, Headers))
|
1, Headers))
|
||||||
|
@ -908,22 +820,22 @@ reply_may_compress(Status, Headers, Body, Req,
|
||||||
case CanGzip of
|
case CanGzip of
|
||||||
true ->
|
true ->
|
||||||
GzBody = zlib:gzip(Body),
|
GzBody = zlib:gzip(Body),
|
||||||
{_, Req3} = response(Status, Headers, RespHeaders, [
|
{_, Req2} = response(Status, Headers, RespHeaders, [
|
||||||
{<<"content-length">>, integer_to_list(byte_size(GzBody))},
|
{<<"content-length">>, integer_to_list(byte_size(GzBody))},
|
||||||
{<<"content-encoding">>, <<"gzip">>},
|
{<<"content-encoding">>, <<"gzip">>},
|
||||||
{<<"date">>, cowboy_clock:rfc1123()},
|
{<<"date">>, cowboy_clock:rfc1123()},
|
||||||
{<<"server">>, <<"Cowboy">>}
|
{<<"server">>, <<"Cowboy">>}
|
||||||
|HTTP11Headers],
|
|HTTP11Headers],
|
||||||
case Method of <<"HEAD">> -> <<>>; _ -> GzBody end,
|
case Method of <<"HEAD">> -> <<>>; _ -> GzBody end,
|
||||||
Req2),
|
Req),
|
||||||
Req3;
|
Req2;
|
||||||
false ->
|
false ->
|
||||||
reply_no_compress(Status, Headers, Body, Req,
|
reply_no_compress(Status, Headers, Body, Req,
|
||||||
RespHeaders, HTTP11Headers, Method, BodySize)
|
RespHeaders, HTTP11Headers, Method, BodySize)
|
||||||
end;
|
end
|
||||||
{error, badarg} ->
|
catch _:_ ->
|
||||||
reply_no_compress(Status, Headers, Body, Req,
|
reply_no_compress(Status, Headers, Body, Req,
|
||||||
RespHeaders, HTTP11Headers, Method, BodySize)
|
RespHeaders, HTTP11Headers, Method, BodySize)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
reply_no_compress(Status, Headers, Body, Req,
|
reply_no_compress(Status, Headers, Body, Req,
|
||||||
|
@ -937,17 +849,17 @@ reply_no_compress(Status, Headers, Body, Req,
|
||||||
Req),
|
Req),
|
||||||
Req2.
|
Req2.
|
||||||
|
|
||||||
-spec chunked_reply(cowboy:http_status(), Req) -> {ok, Req} when Req::req().
|
-spec chunked_reply(cowboy:http_status(), Req) -> Req when Req::req().
|
||||||
chunked_reply(Status, Req) ->
|
chunked_reply(Status, Req) ->
|
||||||
chunked_reply(Status, [], Req).
|
chunked_reply(Status, [], Req).
|
||||||
|
|
||||||
-spec chunked_reply(cowboy:http_status(), cowboy:http_headers(), Req)
|
-spec chunked_reply(cowboy:http_status(), cowboy:http_headers(), Req)
|
||||||
-> {ok, Req} when Req::req().
|
-> Req when Req::req().
|
||||||
chunked_reply(Status, Headers, Req) ->
|
chunked_reply(Status, Headers, Req) ->
|
||||||
{_, Req2} = chunked_response(Status, Headers, Req),
|
{_, Req2} = chunked_response(Status, Headers, Req),
|
||||||
{ok, Req2}.
|
Req2.
|
||||||
|
|
||||||
-spec chunk(iodata(), req()) -> ok | {error, atom()}.
|
-spec chunk(iodata(), req()) -> ok.
|
||||||
chunk(_Data, #http_req{method= <<"HEAD">>}) ->
|
chunk(_Data, #http_req{method= <<"HEAD">>}) ->
|
||||||
ok;
|
ok;
|
||||||
chunk(Data, #http_req{socket=Socket, transport=cowboy_spdy,
|
chunk(Data, #http_req{socket=Socket, transport=cowboy_spdy,
|
||||||
|
@ -955,10 +867,10 @@ chunk(Data, #http_req{socket=Socket, transport=cowboy_spdy,
|
||||||
cowboy_spdy:stream_data(Socket, Data);
|
cowboy_spdy:stream_data(Socket, Data);
|
||||||
chunk(Data, #http_req{socket=Socket, transport=Transport,
|
chunk(Data, #http_req{socket=Socket, transport=Transport,
|
||||||
resp_state=stream}) ->
|
resp_state=stream}) ->
|
||||||
Transport:send(Socket, Data);
|
ok = Transport:send(Socket, Data);
|
||||||
chunk(Data, #http_req{socket=Socket, transport=Transport,
|
chunk(Data, #http_req{socket=Socket, transport=Transport,
|
||||||
resp_state=chunks}) ->
|
resp_state=chunks}) ->
|
||||||
Transport:send(Socket, [integer_to_list(iolist_size(Data), 16),
|
ok = Transport:send(Socket, [integer_to_list(iolist_size(Data), 16),
|
||||||
<<"\r\n">>, Data, <<"\r\n">>]).
|
<<"\r\n">>, Data, <<"\r\n">>]).
|
||||||
|
|
||||||
%% If ever made public, need to send nothing if HEAD.
|
%% If ever made public, need to send nothing if HEAD.
|
||||||
|
@ -971,20 +883,20 @@ last_chunk(Req=#http_req{socket=Socket, transport=Transport}) ->
|
||||||
Req#http_req{resp_state=done}.
|
Req#http_req{resp_state=done}.
|
||||||
|
|
||||||
-spec upgrade_reply(cowboy:http_status(), cowboy:http_headers(), Req)
|
-spec upgrade_reply(cowboy:http_status(), cowboy:http_headers(), Req)
|
||||||
-> {ok, Req} when Req::req().
|
-> Req when Req::req().
|
||||||
upgrade_reply(Status, Headers, Req=#http_req{transport=Transport,
|
upgrade_reply(Status, Headers, Req=#http_req{transport=Transport,
|
||||||
resp_state=waiting, resp_headers=RespHeaders})
|
resp_state=waiting, resp_headers=RespHeaders})
|
||||||
when Transport =/= cowboy_spdy ->
|
when Transport =/= cowboy_spdy ->
|
||||||
{_, Req2} = response(Status, Headers, RespHeaders, [
|
{_, Req2} = response(Status, Headers, RespHeaders, [
|
||||||
{<<"connection">>, <<"Upgrade">>}
|
{<<"connection">>, <<"Upgrade">>}
|
||||||
], <<>>, Req),
|
], <<>>, Req),
|
||||||
{ok, Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}.
|
Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}.
|
||||||
|
|
||||||
-spec continue(req()) -> ok | {error, atom()}.
|
-spec continue(req()) -> ok.
|
||||||
continue(#http_req{socket=Socket, transport=Transport,
|
continue(#http_req{socket=Socket, transport=Transport,
|
||||||
version=Version}) ->
|
version=Version}) ->
|
||||||
HTTPVer = atom_to_binary(Version, latin1),
|
HTTPVer = atom_to_binary(Version, latin1),
|
||||||
Transport:send(Socket,
|
ok = Transport:send(Socket,
|
||||||
<< HTTPVer/binary, " ", (status(100))/binary, "\r\n\r\n" >>).
|
<< HTTPVer/binary, " ", (status(100))/binary, "\r\n\r\n" >>).
|
||||||
|
|
||||||
%% Meant to be used internally for sending errors after crashes.
|
%% Meant to be used internally for sending errors after crashes.
|
||||||
|
@ -997,9 +909,7 @@ maybe_reply(Stacktrace, Req) ->
|
||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_maybe_reply([
|
do_maybe_reply([{cow_http_hd, _, _, _}|_], Req) ->
|
||||||
{cow_http_hd, _, _, _},
|
|
||||||
{cowboy_req, parse_header, _, _}|_], Req) ->
|
|
||||||
cowboy_req:reply(400, Req);
|
cowboy_req:reply(400, Req);
|
||||||
do_maybe_reply(_, Req) ->
|
do_maybe_reply(_, Req) ->
|
||||||
cowboy_req:reply(500, Req).
|
cowboy_req:reply(500, Req).
|
||||||
|
@ -1039,7 +949,6 @@ g(bindings, #http_req{bindings=Ret}) -> Ret;
|
||||||
g(body_state, #http_req{body_state=Ret}) -> Ret;
|
g(body_state, #http_req{body_state=Ret}) -> Ret;
|
||||||
g(buffer, #http_req{buffer=Ret}) -> Ret;
|
g(buffer, #http_req{buffer=Ret}) -> Ret;
|
||||||
g(connection, #http_req{connection=Ret}) -> Ret;
|
g(connection, #http_req{connection=Ret}) -> Ret;
|
||||||
g(cookies, #http_req{cookies=Ret}) -> Ret;
|
|
||||||
g(headers, #http_req{headers=Ret}) -> Ret;
|
g(headers, #http_req{headers=Ret}) -> Ret;
|
||||||
g(host, #http_req{host=Ret}) -> Ret;
|
g(host, #http_req{host=Ret}) -> Ret;
|
||||||
g(host_info, #http_req{host_info=Ret}) -> Ret;
|
g(host_info, #http_req{host_info=Ret}) -> Ret;
|
||||||
|
@ -1047,14 +956,12 @@ g(meta, #http_req{meta=Ret}) -> Ret;
|
||||||
g(method, #http_req{method=Ret}) -> Ret;
|
g(method, #http_req{method=Ret}) -> Ret;
|
||||||
g(multipart, #http_req{multipart=Ret}) -> Ret;
|
g(multipart, #http_req{multipart=Ret}) -> Ret;
|
||||||
g(onresponse, #http_req{onresponse=Ret}) -> Ret;
|
g(onresponse, #http_req{onresponse=Ret}) -> Ret;
|
||||||
g(p_headers, #http_req{p_headers=Ret}) -> Ret;
|
|
||||||
g(path, #http_req{path=Ret}) -> Ret;
|
g(path, #http_req{path=Ret}) -> Ret;
|
||||||
g(path_info, #http_req{path_info=Ret}) -> Ret;
|
g(path_info, #http_req{path_info=Ret}) -> Ret;
|
||||||
g(peer, #http_req{peer=Ret}) -> Ret;
|
g(peer, #http_req{peer=Ret}) -> Ret;
|
||||||
g(pid, #http_req{pid=Ret}) -> Ret;
|
g(pid, #http_req{pid=Ret}) -> Ret;
|
||||||
g(port, #http_req{port=Ret}) -> Ret;
|
g(port, #http_req{port=Ret}) -> Ret;
|
||||||
g(qs, #http_req{qs=Ret}) -> Ret;
|
g(qs, #http_req{qs=Ret}) -> Ret;
|
||||||
g(qs_vals, #http_req{qs_vals=Ret}) -> Ret;
|
|
||||||
g(resp_body, #http_req{resp_body=Ret}) -> Ret;
|
g(resp_body, #http_req{resp_body=Ret}) -> Ret;
|
||||||
g(resp_compress, #http_req{resp_compress=Ret}) -> Ret;
|
g(resp_compress, #http_req{resp_compress=Ret}) -> Ret;
|
||||||
g(resp_headers, #http_req{resp_headers=Ret}) -> Ret;
|
g(resp_headers, #http_req{resp_headers=Ret}) -> Ret;
|
||||||
|
@ -1069,7 +976,6 @@ set([{bindings, Val}|Tail], Req) -> set(Tail, Req#http_req{bindings=Val});
|
||||||
set([{body_state, Val}|Tail], Req) -> set(Tail, Req#http_req{body_state=Val});
|
set([{body_state, Val}|Tail], Req) -> set(Tail, Req#http_req{body_state=Val});
|
||||||
set([{buffer, Val}|Tail], Req) -> set(Tail, Req#http_req{buffer=Val});
|
set([{buffer, Val}|Tail], Req) -> set(Tail, Req#http_req{buffer=Val});
|
||||||
set([{connection, Val}|Tail], Req) -> set(Tail, Req#http_req{connection=Val});
|
set([{connection, Val}|Tail], Req) -> set(Tail, Req#http_req{connection=Val});
|
||||||
set([{cookies, Val}|Tail], Req) -> set(Tail, Req#http_req{cookies=Val});
|
|
||||||
set([{headers, Val}|Tail], Req) -> set(Tail, Req#http_req{headers=Val});
|
set([{headers, Val}|Tail], Req) -> set(Tail, Req#http_req{headers=Val});
|
||||||
set([{host, Val}|Tail], Req) -> set(Tail, Req#http_req{host=Val});
|
set([{host, Val}|Tail], Req) -> set(Tail, Req#http_req{host=Val});
|
||||||
set([{host_info, Val}|Tail], Req) -> set(Tail, Req#http_req{host_info=Val});
|
set([{host_info, Val}|Tail], Req) -> set(Tail, Req#http_req{host_info=Val});
|
||||||
|
@ -1077,14 +983,12 @@ set([{meta, Val}|Tail], Req) -> set(Tail, Req#http_req{meta=Val});
|
||||||
set([{method, Val}|Tail], Req) -> set(Tail, Req#http_req{method=Val});
|
set([{method, Val}|Tail], Req) -> set(Tail, Req#http_req{method=Val});
|
||||||
set([{multipart, Val}|Tail], Req) -> set(Tail, Req#http_req{multipart=Val});
|
set([{multipart, Val}|Tail], Req) -> set(Tail, Req#http_req{multipart=Val});
|
||||||
set([{onresponse, Val}|Tail], Req) -> set(Tail, Req#http_req{onresponse=Val});
|
set([{onresponse, Val}|Tail], Req) -> set(Tail, Req#http_req{onresponse=Val});
|
||||||
set([{p_headers, Val}|Tail], Req) -> set(Tail, Req#http_req{p_headers=Val});
|
|
||||||
set([{path, Val}|Tail], Req) -> set(Tail, Req#http_req{path=Val});
|
set([{path, Val}|Tail], Req) -> set(Tail, Req#http_req{path=Val});
|
||||||
set([{path_info, Val}|Tail], Req) -> set(Tail, Req#http_req{path_info=Val});
|
set([{path_info, Val}|Tail], Req) -> set(Tail, Req#http_req{path_info=Val});
|
||||||
set([{peer, Val}|Tail], Req) -> set(Tail, Req#http_req{peer=Val});
|
set([{peer, Val}|Tail], Req) -> set(Tail, Req#http_req{peer=Val});
|
||||||
set([{pid, Val}|Tail], Req) -> set(Tail, Req#http_req{pid=Val});
|
set([{pid, Val}|Tail], Req) -> set(Tail, Req#http_req{pid=Val});
|
||||||
set([{port, Val}|Tail], Req) -> set(Tail, Req#http_req{port=Val});
|
set([{port, Val}|Tail], Req) -> set(Tail, Req#http_req{port=Val});
|
||||||
set([{qs, Val}|Tail], Req) -> set(Tail, Req#http_req{qs=Val});
|
set([{qs, Val}|Tail], Req) -> set(Tail, Req#http_req{qs=Val});
|
||||||
set([{qs_vals, Val}|Tail], Req) -> set(Tail, Req#http_req{qs_vals=Val});
|
|
||||||
set([{resp_body, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_body=Val});
|
set([{resp_body, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_body=Val});
|
||||||
set([{resp_headers, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_headers=Val});
|
set([{resp_headers, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_headers=Val});
|
||||||
set([{resp_state, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_state=Val});
|
set([{resp_state, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_state=Val});
|
||||||
|
@ -1102,10 +1006,8 @@ set_bindings(HostInfo, PathInfo, Bindings, Req) ->
|
||||||
|
|
||||||
-spec compact(Req) -> Req when Req::req().
|
-spec compact(Req) -> Req when Req::req().
|
||||||
compact(Req) ->
|
compact(Req) ->
|
||||||
Req#http_req{host_info=undefined,
|
Req#http_req{host_info=undefined, path_info=undefined,
|
||||||
path_info=undefined, qs_vals=undefined,
|
bindings=undefined, headers=[]}.
|
||||||
bindings=undefined, headers=[],
|
|
||||||
p_headers=[], cookies=[]}.
|
|
||||||
|
|
||||||
-spec lock(Req) -> Req when Req::req().
|
-spec lock(Req) -> Req when Req::req().
|
||||||
lock(Req) ->
|
lock(Req) ->
|
||||||
|
@ -1193,7 +1095,7 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
|
||||||
(status(Status2))/binary, "\r\n" >>,
|
(status(Status2))/binary, "\r\n" >>,
|
||||||
HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>]
|
HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>]
|
||||||
|| {Key, Value} <- FullHeaders2],
|
|| {Key, Value} <- FullHeaders2],
|
||||||
Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>, Body2]),
|
ok = Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>, Body2]),
|
||||||
ReqPid ! {?MODULE, resp_sent},
|
ReqPid ! {?MODULE, resp_sent},
|
||||||
normal;
|
normal;
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -1319,32 +1221,94 @@ status(510) -> <<"510 Not Extended">>;
|
||||||
status(511) -> <<"511 Network Authentication Required">>;
|
status(511) -> <<"511 Network Authentication Required">>;
|
||||||
status(B) when is_binary(B) -> B.
|
status(B) when is_binary(B) -> B.
|
||||||
|
|
||||||
|
%% Create map, convert keys to atoms and group duplicate keys into lists.
|
||||||
|
%% Keys that are not found in the user provided list are entirely skipped.
|
||||||
|
%% @todo Can probably be done directly while parsing.
|
||||||
|
kvlist_to_map(KvList, Fields) ->
|
||||||
|
Keys = [case K of
|
||||||
|
{Key, _} -> Key;
|
||||||
|
{Key, _, _} -> Key;
|
||||||
|
Key -> Key
|
||||||
|
end || K <- Fields],
|
||||||
|
kvlist_to_map(KvList, Keys, #{}).
|
||||||
|
|
||||||
|
kvlist_to_map([], _, Map) ->
|
||||||
|
Map;
|
||||||
|
kvlist_to_map([{Key, Value}|Tail], Keys, Map) ->
|
||||||
|
try binary_to_existing_atom(Key, utf8) of
|
||||||
|
Atom ->
|
||||||
|
case lists:member(Atom, Keys) of
|
||||||
|
true ->
|
||||||
|
case maps:find(Atom, Map) of
|
||||||
|
{ok, MapValue} when is_list(MapValue) ->
|
||||||
|
kvlist_to_map(Tail, Keys,
|
||||||
|
maps:put(Atom, [Value|MapValue], Map));
|
||||||
|
{ok, MapValue} ->
|
||||||
|
kvlist_to_map(Tail, Keys,
|
||||||
|
maps:put(Atom, [Value, MapValue], Map));
|
||||||
|
error ->
|
||||||
|
kvlist_to_map(Tail, Keys,
|
||||||
|
maps:put(Atom, Value, Map))
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
kvlist_to_map(Tail, Keys, Map)
|
||||||
|
end
|
||||||
|
catch error:badarg ->
|
||||||
|
kvlist_to_map(Tail, Keys, Map)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% Loop through fields, if value is missing and no default, crash;
|
||||||
|
%% else if value is missing and has a default, set default;
|
||||||
|
%% otherwise apply constraints. If constraint fails, crash.
|
||||||
|
filter(Map, []) ->
|
||||||
|
Map;
|
||||||
|
filter(Map, [{Key, Constraints}|Tail]) ->
|
||||||
|
filter_constraints(Map, Tail, Key, maps:get(Key, Map), Constraints);
|
||||||
|
filter(Map, [{Key, Constraints, Default}|Tail]) ->
|
||||||
|
case maps:find(Key, Map) of
|
||||||
|
{ok, Value} ->
|
||||||
|
filter_constraints(Map, Tail, Key, Value, Constraints);
|
||||||
|
error ->
|
||||||
|
filter(maps:put(Key, Default, Map), Tail)
|
||||||
|
end;
|
||||||
|
filter(Map, [Key|Tail]) ->
|
||||||
|
true = maps:is_key(Key, Map),
|
||||||
|
filter(Map, Tail).
|
||||||
|
|
||||||
|
filter_constraints(Map, Tail, Key, Value, Constraints) ->
|
||||||
|
case cowboy_constraints:validate(Value, Constraints) of
|
||||||
|
true ->
|
||||||
|
filter(Map, Tail);
|
||||||
|
{true, Value2} ->
|
||||||
|
filter(maps:put(Key, Value2, Map), Tail)
|
||||||
|
end.
|
||||||
|
|
||||||
%% Tests.
|
%% Tests.
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
url_test() ->
|
url_test() ->
|
||||||
{undefined, _} =
|
undefined =
|
||||||
url(#http_req{transport=ranch_tcp, host= <<>>, port= undefined,
|
url(#http_req{transport=ranch_tcp, host= <<>>, port= undefined,
|
||||||
path= <<>>, qs= <<>>, pid=self()}),
|
path= <<>>, qs= <<>>, pid=self()}),
|
||||||
{<<"http://localhost/path">>, _ } =
|
<<"http://localhost/path">> =
|
||||||
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=80,
|
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=80,
|
||||||
path= <<"/path">>, qs= <<>>, pid=self()}),
|
path= <<"/path">>, qs= <<>>, pid=self()}),
|
||||||
{<<"http://localhost:443/path">>, _} =
|
<<"http://localhost:443/path">> =
|
||||||
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=443,
|
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=443,
|
||||||
path= <<"/path">>, qs= <<>>, pid=self()}),
|
path= <<"/path">>, qs= <<>>, pid=self()}),
|
||||||
{<<"http://localhost:8080/path">>, _} =
|
<<"http://localhost:8080/path">> =
|
||||||
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
|
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
|
||||||
path= <<"/path">>, qs= <<>>, pid=self()}),
|
path= <<"/path">>, qs= <<>>, pid=self()}),
|
||||||
{<<"http://localhost:8080/path?dummy=2785">>, _} =
|
<<"http://localhost:8080/path?dummy=2785">> =
|
||||||
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
|
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
|
||||||
path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
|
path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
|
||||||
{<<"https://localhost/path">>, _} =
|
<<"https://localhost/path">> =
|
||||||
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=443,
|
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=443,
|
||||||
path= <<"/path">>, qs= <<>>, pid=self()}),
|
path= <<"/path">>, qs= <<>>, pid=self()}),
|
||||||
{<<"https://localhost:8443/path">>, _} =
|
<<"https://localhost:8443/path">> =
|
||||||
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
|
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
|
||||||
path= <<"/path">>, qs= <<>>, pid=self()}),
|
path= <<"/path">>, qs= <<>>, pid=self()}),
|
||||||
{<<"https://localhost:8443/path?dummy=2785">>, _} =
|
<<"https://localhost:8443/path?dummy=2785">> =
|
||||||
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
|
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
|
||||||
path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
|
path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
-spec upgrade(Req, Env, module(), any())
|
-spec upgrade(Req, Env, module(), any())
|
||||||
-> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
|
-> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
|
||||||
upgrade(Req, Env, Handler, HandlerOpts) ->
|
upgrade(Req, Env, Handler, HandlerOpts) ->
|
||||||
Method = cowboy_req:get(method, Req),
|
Method = cowboy_req:method(Req),
|
||||||
case erlang:function_exported(Handler, rest_init, 2) of
|
case erlang:function_exported(Handler, rest_init, 2) of
|
||||||
true ->
|
true ->
|
||||||
try Handler:rest_init(Req, HandlerOpts) of
|
try Handler:rest_init(Req, HandlerOpts) of
|
||||||
|
@ -215,16 +215,15 @@ content_types_provided(Req, State) ->
|
||||||
no_call ->
|
no_call ->
|
||||||
State2 = State#state{
|
State2 = State#state{
|
||||||
content_types_p=[{{<<"text">>, <<"html">>, '*'}, to_html}]},
|
content_types_p=[{{<<"text">>, <<"html">>, '*'}, to_html}]},
|
||||||
case cowboy_req:parse_header(<<"accept">>, Req) of
|
try cowboy_req:parse_header(<<"accept">>, Req) of
|
||||||
{error, badarg} ->
|
undefined ->
|
||||||
respond(Req, State2, 400);
|
|
||||||
{ok, undefined, Req2} ->
|
|
||||||
languages_provided(
|
languages_provided(
|
||||||
cowboy_req:set_meta(media_type, {<<"text">>, <<"html">>, []}, Req2),
|
cowboy_req:set_meta(media_type, {<<"text">>, <<"html">>, []}, Req),
|
||||||
State2#state{content_type_a={{<<"text">>, <<"html">>, []}, to_html}});
|
State2#state{content_type_a={{<<"text">>, <<"html">>, []}, to_html}});
|
||||||
{ok, Accept, Req2} ->
|
Accept ->
|
||||||
Accept2 = prioritize_accept(Accept),
|
choose_media_type(Req, State2, prioritize_accept(Accept))
|
||||||
choose_media_type(Req2, State2, Accept2)
|
catch _:_ ->
|
||||||
|
respond(Req, State2, 400)
|
||||||
end;
|
end;
|
||||||
{halt, Req2, HandlerState} ->
|
{halt, Req2, HandlerState} ->
|
||||||
terminate(Req2, State#state{handler_state=HandlerState});
|
terminate(Req2, State#state{handler_state=HandlerState});
|
||||||
|
@ -234,17 +233,16 @@ content_types_provided(Req, State) ->
|
||||||
CTP2 = [normalize_content_types(P) || P <- CTP],
|
CTP2 = [normalize_content_types(P) || P <- CTP],
|
||||||
State2 = State#state{
|
State2 = State#state{
|
||||||
handler_state=HandlerState, content_types_p=CTP2},
|
handler_state=HandlerState, content_types_p=CTP2},
|
||||||
case cowboy_req:parse_header(<<"accept">>, Req2) of
|
try cowboy_req:parse_header(<<"accept">>, Req2) of
|
||||||
{error, badarg} ->
|
undefined ->
|
||||||
respond(Req2, State2, 400);
|
|
||||||
{ok, undefined, Req3} ->
|
|
||||||
{PMT, _Fun} = HeadCTP = hd(CTP2),
|
{PMT, _Fun} = HeadCTP = hd(CTP2),
|
||||||
languages_provided(
|
languages_provided(
|
||||||
cowboy_req:set_meta(media_type, PMT, Req3),
|
cowboy_req:set_meta(media_type, PMT, Req2),
|
||||||
State2#state{content_type_a=HeadCTP});
|
State2#state{content_type_a=HeadCTP});
|
||||||
{ok, Accept, Req3} ->
|
Accept ->
|
||||||
Accept2 = prioritize_accept(Accept),
|
choose_media_type(Req2, State2, prioritize_accept(Accept))
|
||||||
choose_media_type(Req3, State2, Accept2)
|
catch _:_ ->
|
||||||
|
respond(Req2, State2, 400)
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -335,14 +333,12 @@ languages_provided(Req, State) ->
|
||||||
not_acceptable(Req2, State#state{handler_state=HandlerState});
|
not_acceptable(Req2, State#state{handler_state=HandlerState});
|
||||||
{LP, Req2, HandlerState} ->
|
{LP, Req2, HandlerState} ->
|
||||||
State2 = State#state{handler_state=HandlerState, languages_p=LP},
|
State2 = State#state{handler_state=HandlerState, languages_p=LP},
|
||||||
{ok, AcceptLanguage, Req3} =
|
case cowboy_req:parse_header(<<"accept-language">>, Req2) of
|
||||||
cowboy_req:parse_header(<<"accept-language">>, Req2),
|
|
||||||
case AcceptLanguage of
|
|
||||||
undefined ->
|
undefined ->
|
||||||
set_language(Req3, State2#state{language_a=hd(LP)});
|
set_language(Req2, State2#state{language_a=hd(LP)});
|
||||||
AcceptLanguage ->
|
AcceptLanguage ->
|
||||||
AcceptLanguage2 = prioritize_languages(AcceptLanguage),
|
AcceptLanguage2 = prioritize_languages(AcceptLanguage),
|
||||||
choose_language(Req3, State2, AcceptLanguage2)
|
choose_language(Req2, State2, AcceptLanguage2)
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -397,14 +393,12 @@ charsets_provided(Req, State) ->
|
||||||
not_acceptable(Req2, State#state{handler_state=HandlerState});
|
not_acceptable(Req2, State#state{handler_state=HandlerState});
|
||||||
{CP, Req2, HandlerState} ->
|
{CP, Req2, HandlerState} ->
|
||||||
State2 = State#state{handler_state=HandlerState, charsets_p=CP},
|
State2 = State#state{handler_state=HandlerState, charsets_p=CP},
|
||||||
{ok, AcceptCharset, Req3} =
|
case cowboy_req:parse_header(<<"accept-charset">>, Req2) of
|
||||||
cowboy_req:parse_header(<<"accept-charset">>, Req2),
|
|
||||||
case AcceptCharset of
|
|
||||||
undefined ->
|
undefined ->
|
||||||
set_content_type(Req3, State2#state{charset_a=hd(CP)});
|
set_content_type(Req2, State2#state{charset_a=hd(CP)});
|
||||||
AcceptCharset ->
|
AcceptCharset ->
|
||||||
AcceptCharset2 = prioritize_charsets(AcceptCharset),
|
AcceptCharset2 = prioritize_charsets(AcceptCharset),
|
||||||
choose_charset(Req3, State2, AcceptCharset2)
|
choose_charset(Req2, State2, AcceptCharset2)
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -524,12 +518,12 @@ resource_exists(Req, State) ->
|
||||||
if_match_exists(Req, State) ->
|
if_match_exists(Req, State) ->
|
||||||
State2 = State#state{exists=true},
|
State2 = State#state{exists=true},
|
||||||
case cowboy_req:parse_header(<<"if-match">>, Req) of
|
case cowboy_req:parse_header(<<"if-match">>, Req) of
|
||||||
{ok, undefined, Req2} ->
|
undefined ->
|
||||||
if_unmodified_since_exists(Req2, State2);
|
if_unmodified_since_exists(Req, State2);
|
||||||
{ok, '*', Req2} ->
|
'*' ->
|
||||||
if_unmodified_since_exists(Req2, State2);
|
if_unmodified_since_exists(Req, State2);
|
||||||
{ok, ETagsList, Req2} ->
|
ETagsList ->
|
||||||
if_match(Req2, State2, ETagsList)
|
if_match(Req, State2, ETagsList)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
if_match(Req, State, EtagsList) ->
|
if_match(Req, State, EtagsList) ->
|
||||||
|
@ -546,18 +540,18 @@ if_match(Req, State, EtagsList) ->
|
||||||
|
|
||||||
if_match_must_not_exist(Req, State) ->
|
if_match_must_not_exist(Req, State) ->
|
||||||
case cowboy_req:header(<<"if-match">>, Req) of
|
case cowboy_req:header(<<"if-match">>, Req) of
|
||||||
{undefined, Req2} -> is_put_to_missing_resource(Req2, State);
|
undefined -> is_put_to_missing_resource(Req, State);
|
||||||
{_Any, Req2} -> precondition_failed(Req2, State)
|
_ -> precondition_failed(Req, State)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
if_unmodified_since_exists(Req, State) ->
|
if_unmodified_since_exists(Req, State) ->
|
||||||
case cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of
|
try cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of
|
||||||
{ok, undefined, Req2} ->
|
undefined ->
|
||||||
if_none_match_exists(Req2, State);
|
if_none_match_exists(Req, State);
|
||||||
{ok, IfUnmodifiedSince, Req2} ->
|
IfUnmodifiedSince ->
|
||||||
if_unmodified_since(Req2, State, IfUnmodifiedSince);
|
if_unmodified_since(Req, State, IfUnmodifiedSince)
|
||||||
{error, badarg} ->
|
catch _:_ ->
|
||||||
if_none_match_exists(Req, State)
|
if_none_match_exists(Req, State)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% If LastModified is the atom 'no_call', we continue.
|
%% If LastModified is the atom 'no_call', we continue.
|
||||||
|
@ -574,12 +568,12 @@ if_unmodified_since(Req, State, IfUnmodifiedSince) ->
|
||||||
|
|
||||||
if_none_match_exists(Req, State) ->
|
if_none_match_exists(Req, State) ->
|
||||||
case cowboy_req:parse_header(<<"if-none-match">>, Req) of
|
case cowboy_req:parse_header(<<"if-none-match">>, Req) of
|
||||||
{ok, undefined, Req2} ->
|
undefined ->
|
||||||
if_modified_since_exists(Req2, State);
|
if_modified_since_exists(Req, State);
|
||||||
{ok, '*', Req2} ->
|
'*' ->
|
||||||
precondition_is_head_get(Req2, State);
|
precondition_is_head_get(Req, State);
|
||||||
{ok, EtagsList, Req2} ->
|
EtagsList ->
|
||||||
if_none_match(Req2, State, EtagsList)
|
if_none_match(Req, State, EtagsList)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
if_none_match(Req, State, EtagsList) ->
|
if_none_match(Req, State, EtagsList) ->
|
||||||
|
@ -605,13 +599,13 @@ precondition_is_head_get(Req, State) ->
|
||||||
precondition_failed(Req, State).
|
precondition_failed(Req, State).
|
||||||
|
|
||||||
if_modified_since_exists(Req, State) ->
|
if_modified_since_exists(Req, State) ->
|
||||||
case cowboy_req:parse_header(<<"if-modified-since">>, Req) of
|
try cowboy_req:parse_header(<<"if-modified-since">>, Req) of
|
||||||
{ok, undefined, Req2} ->
|
undefined ->
|
||||||
method(Req2, State);
|
method(Req, State);
|
||||||
{ok, IfModifiedSince, Req2} ->
|
IfModifiedSince ->
|
||||||
if_modified_since_now(Req2, State, IfModifiedSince);
|
if_modified_since_now(Req, State, IfModifiedSince)
|
||||||
{error, badarg} ->
|
catch _:_ ->
|
||||||
method(Req, State)
|
method(Req, State)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
if_modified_since_now(Req, State, IfModifiedSince) ->
|
if_modified_since_now(Req, State, IfModifiedSince) ->
|
||||||
|
@ -741,11 +735,11 @@ accept_resource(Req, State) ->
|
||||||
{CTA, Req2, HandlerState} ->
|
{CTA, Req2, HandlerState} ->
|
||||||
CTA2 = [normalize_content_types(P) || P <- CTA],
|
CTA2 = [normalize_content_types(P) || P <- CTA],
|
||||||
State2 = State#state{handler_state=HandlerState},
|
State2 = State#state{handler_state=HandlerState},
|
||||||
case cowboy_req:parse_header(<<"content-type">>, Req2) of
|
try cowboy_req:parse_header(<<"content-type">>, Req2) of
|
||||||
{ok, ContentType, Req3} ->
|
ContentType ->
|
||||||
choose_content_type(Req3, State2, ContentType, CTA2);
|
choose_content_type(Req2, State2, ContentType, CTA2)
|
||||||
{error, badarg} ->
|
catch _:_ ->
|
||||||
respond(Req2, State2, 415)
|
respond(Req2, State2, 415)
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -990,8 +984,7 @@ next(Req, State, StatusCode) when is_integer(StatusCode) ->
|
||||||
respond(Req, State, StatusCode).
|
respond(Req, State, StatusCode).
|
||||||
|
|
||||||
respond(Req, State, StatusCode) ->
|
respond(Req, State, StatusCode) ->
|
||||||
{ok, Req2} = cowboy_req:reply(StatusCode, Req),
|
terminate(cowboy_req:reply(StatusCode, Req), State).
|
||||||
terminate(Req2, State).
|
|
||||||
|
|
||||||
terminate(Req, State=#state{env=Env}) ->
|
terminate(Req, State=#state{env=Env}) ->
|
||||||
rest_terminate(Req, State),
|
rest_terminate(Req, State),
|
||||||
|
|
|
@ -165,7 +165,8 @@ compile_brackets_split(<< C, Rest/binary >>, Acc, N) ->
|
||||||
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
|
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
|
||||||
execute(Req, Env) ->
|
execute(Req, Env) ->
|
||||||
{_, Dispatch} = lists:keyfind(dispatch, 1, Env),
|
{_, Dispatch} = lists:keyfind(dispatch, 1, Env),
|
||||||
[Host, Path] = cowboy_req:get([host, path], Req),
|
Host = cowboy_req:host(Req),
|
||||||
|
Path = cowboy_req:path(Req),
|
||||||
case match(Dispatch, Host, Path) of
|
case match(Dispatch, Host, Path) of
|
||||||
{ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} ->
|
{ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} ->
|
||||||
Req2 = cowboy_req:set_bindings(HostInfo, PathInfo, Bindings, Req),
|
Req2 = cowboy_req:set_bindings(HostInfo, PathInfo, Bindings, Req),
|
||||||
|
@ -316,7 +317,7 @@ split_host(Host, Acc) ->
|
||||||
%% Following RFC2396, this function may return path segments containing any
|
%% Following RFC2396, this function may return path segments containing any
|
||||||
%% character, including <em>/</em> if, and only if, a <em>/</em> was escaped
|
%% character, including <em>/</em> if, and only if, a <em>/</em> was escaped
|
||||||
%% and part of a path segment.
|
%% and part of a path segment.
|
||||||
-spec split_path(binary()) -> tokens().
|
-spec split_path(binary()) -> tokens() | badrequest.
|
||||||
split_path(<< $/, Path/bits >>) ->
|
split_path(<< $/, Path/bits >>) ->
|
||||||
split_path(Path, []);
|
split_path(Path, []);
|
||||||
split_path(_) ->
|
split_path(_) ->
|
||||||
|
|
|
@ -87,14 +87,14 @@ rest_init_dir(Req, Path, Extra) when is_list(Path) ->
|
||||||
rest_init_dir(Req, list_to_binary(Path), Extra);
|
rest_init_dir(Req, list_to_binary(Path), Extra);
|
||||||
rest_init_dir(Req, Path, Extra) ->
|
rest_init_dir(Req, Path, Extra) ->
|
||||||
Dir = fullpath(filename:absname(Path)),
|
Dir = fullpath(filename:absname(Path)),
|
||||||
{PathInfo, Req2} = cowboy_req:path_info(Req),
|
PathInfo = cowboy_req:path_info(Req),
|
||||||
Filepath = filename:join([Dir|PathInfo]),
|
Filepath = filename:join([Dir|PathInfo]),
|
||||||
Len = byte_size(Dir),
|
Len = byte_size(Dir),
|
||||||
case fullpath(Filepath) of
|
case fullpath(Filepath) of
|
||||||
<< Dir:Len/binary, $/, _/binary >> ->
|
<< Dir:Len/binary, $/, _/binary >> ->
|
||||||
rest_init_info(Req2, Filepath, Extra);
|
rest_init_info(Req, Filepath, Extra);
|
||||||
_ ->
|
_ ->
|
||||||
{ok, Req2, error}
|
{ok, Req, error}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
fullpath(Path) ->
|
fullpath(Path) ->
|
||||||
|
|
|
@ -78,26 +78,26 @@ upgrade(Req, Env, Handler, HandlerOpts) ->
|
||||||
-spec websocket_upgrade(#state{}, Req)
|
-spec websocket_upgrade(#state{}, Req)
|
||||||
-> {ok, #state{}, Req} when Req::cowboy_req:req().
|
-> {ok, #state{}, Req} when Req::cowboy_req:req().
|
||||||
websocket_upgrade(State, Req) ->
|
websocket_upgrade(State, Req) ->
|
||||||
{ok, ConnTokens, Req2}
|
ConnTokens = cowboy_req:parse_header(<<"connection">>, Req),
|
||||||
= cowboy_req:parse_header(<<"connection">>, Req),
|
|
||||||
true = lists:member(<<"upgrade">>, ConnTokens),
|
true = lists:member(<<"upgrade">>, ConnTokens),
|
||||||
%% @todo Should probably send a 426 if the Upgrade header is missing.
|
%% @todo Should probably send a 426 if the Upgrade header is missing.
|
||||||
{ok, [<<"websocket">>], Req3}
|
[<<"websocket">>] = cowboy_req:parse_header(<<"upgrade">>, Req),
|
||||||
= cowboy_req:parse_header(<<"upgrade">>, Req2),
|
Version = cowboy_req:header(<<"sec-websocket-version">>, Req),
|
||||||
{Version, Req4} = cowboy_req:header(<<"sec-websocket-version">>, Req3),
|
|
||||||
IntVersion = list_to_integer(binary_to_list(Version)),
|
IntVersion = list_to_integer(binary_to_list(Version)),
|
||||||
true = (IntVersion =:= 7) orelse (IntVersion =:= 8)
|
true = (IntVersion =:= 7) orelse (IntVersion =:= 8)
|
||||||
orelse (IntVersion =:= 13),
|
orelse (IntVersion =:= 13),
|
||||||
{Key, Req5} = cowboy_req:header(<<"sec-websocket-key">>, Req4),
|
Key = cowboy_req:header(<<"sec-websocket-key">>, Req),
|
||||||
false = Key =:= undefined,
|
false = Key =:= undefined,
|
||||||
websocket_extensions(State#state{key=Key},
|
websocket_extensions(State#state{key=Key},
|
||||||
cowboy_req:set_meta(websocket_version, IntVersion, Req5)).
|
cowboy_req:set_meta(websocket_version, IntVersion, Req)).
|
||||||
|
|
||||||
-spec websocket_extensions(#state{}, Req)
|
-spec websocket_extensions(#state{}, Req)
|
||||||
-> {ok, #state{}, Req} when Req::cowboy_req:req().
|
-> {ok, #state{}, Req} when Req::cowboy_req:req().
|
||||||
websocket_extensions(State, Req) ->
|
websocket_extensions(State, Req) ->
|
||||||
case cowboy_req:parse_header(<<"sec-websocket-extensions">>, Req) of
|
case cowboy_req:parse_header(<<"sec-websocket-extensions">>, Req) of
|
||||||
{ok, Extensions, Req2} when Extensions =/= undefined ->
|
undefined ->
|
||||||
|
{ok, State, cowboy_req:set_meta(websocket_compress, false, Req)};
|
||||||
|
Extensions ->
|
||||||
[Compress] = cowboy_req:get([resp_compress], Req),
|
[Compress] = cowboy_req:get([resp_compress], Req),
|
||||||
case lists:keyfind(<<"x-webkit-deflate-frame">>, 1, Extensions) of
|
case lists:keyfind(<<"x-webkit-deflate-frame">>, 1, Extensions) of
|
||||||
{<<"x-webkit-deflate-frame">>, []} when Compress =:= true ->
|
{<<"x-webkit-deflate-frame">>, []} when Compress =:= true ->
|
||||||
|
@ -115,12 +115,10 @@ websocket_extensions(State, Req) ->
|
||||||
deflate_frame = true,
|
deflate_frame = true,
|
||||||
inflate_state = Inflate,
|
inflate_state = Inflate,
|
||||||
deflate_state = Deflate
|
deflate_state = Deflate
|
||||||
}, cowboy_req:set_meta(websocket_compress, true, Req2)};
|
}, cowboy_req:set_meta(websocket_compress, true, Req)};
|
||||||
_ ->
|
_ ->
|
||||||
{ok, State, cowboy_req:set_meta(websocket_compress, false, Req2)}
|
{ok, State, cowboy_req:set_meta(websocket_compress, false, Req)}
|
||||||
end;
|
end
|
||||||
_ ->
|
|
||||||
{ok, State, cowboy_req:set_meta(websocket_compress, false, Req)}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec handler_init(#state{}, Req, any())
|
-spec handler_init(#state{}, Req, any())
|
||||||
|
@ -168,12 +166,8 @@ websocket_handshake(State=#state{
|
||||||
false -> [];
|
false -> [];
|
||||||
true -> [{<<"sec-websocket-extensions">>, <<"x-webkit-deflate-frame">>}]
|
true -> [{<<"sec-websocket-extensions">>, <<"x-webkit-deflate-frame">>}]
|
||||||
end,
|
end,
|
||||||
{ok, Req2} = cowboy_req:upgrade_reply(
|
Req2 = cowboy_req:upgrade_reply(101, [{<<"upgrade">>, <<"websocket">>},
|
||||||
101,
|
{<<"sec-websocket-accept">>, Challenge}|Extensions], Req),
|
||||||
[{<<"upgrade">>, <<"websocket">>},
|
|
||||||
{<<"sec-websocket-accept">>, Challenge}|
|
|
||||||
Extensions],
|
|
||||||
Req),
|
|
||||||
%% Flush the resp_sent message before moving on.
|
%% Flush the resp_sent message before moving on.
|
||||||
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
|
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
|
||||||
State2 = handler_loop_timeout(State),
|
State2 = handler_loop_timeout(State),
|
||||||
|
|
|
@ -15,8 +15,7 @@ init(_, Req, _) ->
|
||||||
{loop, Req, 2, 5000, hibernate}.
|
{loop, Req, 2, 5000, hibernate}.
|
||||||
|
|
||||||
info(timeout, Req, 0) ->
|
info(timeout, Req, 0) ->
|
||||||
{ok, Req2} = cowboy_req:reply(102, Req),
|
{ok, cowboy_req:reply(102, Req), 0};
|
||||||
{ok, Req2, 0};
|
|
||||||
info(timeout, Req, Count) ->
|
info(timeout, Req, Count) ->
|
||||||
erlang:send_after(200, self(), timeout),
|
erlang:send_after(200, self(), timeout),
|
||||||
{loop, Req, Count - 1, hibernate}.
|
{loop, Req, Count - 1, hibernate}.
|
||||||
|
|
|
@ -17,8 +17,7 @@ init(_, Req, _) ->
|
||||||
info(timeout, Req, State) ->
|
info(timeout, Req, State) ->
|
||||||
{ok, Body, Req2} = cowboy_req:body(Req),
|
{ok, Body, Req2} = cowboy_req:body(Req),
|
||||||
100000 = byte_size(Body),
|
100000 = byte_size(Body),
|
||||||
{ok, Req3} = cowboy_req:reply(200, Req2),
|
{ok, cowboy_req:reply(200, Req2), State}.
|
||||||
{ok, Req3, State}.
|
|
||||||
|
|
||||||
terminate({normal, shutdown}, _, _) ->
|
terminate({normal, shutdown}, _, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -16,8 +16,7 @@ init(_, Req, _) ->
|
||||||
{loop, Req, undefined, 200, hibernate}.
|
{loop, Req, undefined, 200, hibernate}.
|
||||||
|
|
||||||
info(timeout, Req, State) ->
|
info(timeout, Req, State) ->
|
||||||
{ok, Req2} = cowboy_req:reply(500, Req),
|
{ok, cowboy_req:reply(500, Req), State}.
|
||||||
{ok, Req2, State}.
|
|
||||||
|
|
||||||
terminate({normal, timeout}, _, _) ->
|
terminate({normal, timeout}, _, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -592,13 +592,11 @@ onrequest_reply(Config) ->
|
||||||
|
|
||||||
%% Hook for the above onrequest tests.
|
%% Hook for the above onrequest tests.
|
||||||
do_onrequest_hook(Req) ->
|
do_onrequest_hook(Req) ->
|
||||||
case cowboy_req:qs_val(<<"reply">>, Req) of
|
case cowboy_req:match_qs(Req, [{reply, [], noreply}]) of
|
||||||
{undefined, Req2} ->
|
#{reply := noreply} ->
|
||||||
cowboy_req:set_resp_header(<<"server">>, <<"Serenity">>, Req2);
|
cowboy_req:set_resp_header(<<"server">>, <<"Serenity">>, Req);
|
||||||
{_, Req2} ->
|
_ ->
|
||||||
{ok, Req3} = cowboy_req:reply(
|
cowboy_req:reply(200, [], <<"replied!">>, Req)
|
||||||
200, [], <<"replied!">>, Req2),
|
|
||||||
Req3
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
onresponse_capitalize(Config) ->
|
onresponse_capitalize(Config) ->
|
||||||
|
@ -612,8 +610,7 @@ onresponse_capitalize(Config) ->
|
||||||
do_onresponse_capitalize_hook(Status, Headers, Body, Req) ->
|
do_onresponse_capitalize_hook(Status, Headers, Body, Req) ->
|
||||||
Headers2 = [{cowboy_bstr:capitalize_token(N), V}
|
Headers2 = [{cowboy_bstr:capitalize_token(N), V}
|
||||||
|| {N, V} <- Headers],
|
|| {N, V} <- Headers],
|
||||||
{ok, Req2} = cowboy_req:reply(Status, Headers2, Body, Req),
|
cowboy_req:reply(Status, Headers2, Body, Req).
|
||||||
Req2.
|
|
||||||
|
|
||||||
onresponse_crash(Config) ->
|
onresponse_crash(Config) ->
|
||||||
ConnPid = gun_open(Config),
|
ConnPid = gun_open(Config),
|
||||||
|
@ -630,9 +627,7 @@ onresponse_reply(Config) ->
|
||||||
|
|
||||||
%% Hook for the above onresponse tests.
|
%% Hook for the above onresponse tests.
|
||||||
do_onresponse_hook(_, Headers, _, Req) ->
|
do_onresponse_hook(_, Headers, _, Req) ->
|
||||||
{ok, Req2} = cowboy_req:reply(
|
cowboy_req:reply(<<"777 Lucky">>, [{<<"x-hook">>, <<"onresponse">>}|Headers], Req).
|
||||||
<<"777 Lucky">>, [{<<"x-hook">>, <<"onresponse">>}|Headers], Req),
|
|
||||||
Req2.
|
|
||||||
|
|
||||||
parse_host(Config) ->
|
parse_host(Config) ->
|
||||||
ConnPid = gun_open(Config),
|
ConnPid = gun_open(Config),
|
||||||
|
|
|
@ -8,10 +8,9 @@ init({_, http}, Req, _) ->
|
||||||
{ok, Req, undefined}.
|
{ok, Req, undefined}.
|
||||||
|
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
{Method, Req2} = cowboy_req:method(Req),
|
Method = cowboy_req:method(Req),
|
||||||
HasBody = cowboy_req:has_body(Req2),
|
HasBody = cowboy_req:has_body(Req),
|
||||||
{ok, Req3} = maybe_echo(Method, HasBody, Req2),
|
{ok, maybe_echo(Method, HasBody, Req), State}.
|
||||||
{ok, Req3, State}.
|
|
||||||
|
|
||||||
maybe_echo(<<"POST">>, true, Req) ->
|
maybe_echo(<<"POST">>, true, Req) ->
|
||||||
case cowboy_req:body_qs(Req) of
|
case cowboy_req:body_qs(Req) of
|
||||||
|
@ -20,7 +19,6 @@ maybe_echo(<<"POST">>, true, Req) ->
|
||||||
{ok, PostVals, Req2} ->
|
{ok, PostVals, Req2} ->
|
||||||
echo(proplists:get_value(<<"echo">>, PostVals), Req2)
|
echo(proplists:get_value(<<"echo">>, PostVals), Req2)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
maybe_echo(<<"POST">>, false, Req) ->
|
maybe_echo(<<"POST">>, false, Req) ->
|
||||||
cowboy_req:reply(400, [], <<"Missing body.">>, Req);
|
cowboy_req:reply(400, [], <<"Missing body.">>, Req);
|
||||||
maybe_echo(_, _, Req) ->
|
maybe_echo(_, _, Req) ->
|
||||||
|
|
|
@ -8,7 +8,7 @@ init({_Transport, http}, Req, _Opts) ->
|
||||||
{ok, Req, undefined}.
|
{ok, Req, undefined}.
|
||||||
|
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
{ok, Req2} = cowboy_req:chunked_reply(200, Req),
|
Req2 = cowboy_req:chunked_reply(200, Req),
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
cowboy_req:chunk("chunked_handler\r\n", Req2),
|
cowboy_req:chunk("chunked_handler\r\n", Req2),
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
|
|
|
@ -9,21 +9,19 @@ init({_, http}, Req, _) ->
|
||||||
|
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
true = cowboy_req:has_body(Req),
|
true = cowboy_req:has_body(Req),
|
||||||
{ok, Req3} = case cowboy_req:body(Req, [{length, 1000000}]) of
|
Req3 = case cowboy_req:body(Req, [{length, 1000000}]) of
|
||||||
{ok, Body, Req2} -> handle_body(Req2, Body);
|
{ok, Body, Req2} -> handle_body(Req2, Body);
|
||||||
{more, _, Req2} -> handle_badlength(Req2)
|
{more, _, Req2} -> handle_badlength(Req2)
|
||||||
end,
|
end,
|
||||||
{ok, Req3, State}.
|
{ok, Req3, State}.
|
||||||
|
|
||||||
handle_badlength(Req) ->
|
handle_badlength(Req) ->
|
||||||
{ok, Req2} = cowboy_req:reply(413, [], <<"Request entity too large">>, Req),
|
cowboy_req:reply(413, [], <<"Request entity too large">>, Req).
|
||||||
{ok, Req2}.
|
|
||||||
|
|
||||||
handle_body(Req, Body) ->
|
handle_body(Req, Body) ->
|
||||||
{Size, Req2} = cowboy_req:body_length(Req),
|
Size = cowboy_req:body_length(Req),
|
||||||
Size = byte_size(Body),
|
Size = byte_size(Body),
|
||||||
{ok, Req3} = cowboy_req:reply(200, [], Body, Req2),
|
cowboy_req:reply(200, [], Body, Req).
|
||||||
{ok, Req3}.
|
|
||||||
|
|
||||||
terminate(_, _, _) ->
|
terminate(_, _, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -5,18 +5,18 @@
|
||||||
-export([init/3, handle/2, terminate/3]).
|
-export([init/3, handle/2, terminate/3]).
|
||||||
|
|
||||||
init({_Transport, http}, Req, _Opts) ->
|
init({_Transport, http}, Req, _Opts) ->
|
||||||
{Case, Req1} = cowboy_req:qs_val(<<"case">>, Req),
|
#{'case' := Case} = cowboy_req:match_qs(Req, ['case']),
|
||||||
case_init(Case, Req1).
|
case_init(Case, Req).
|
||||||
|
|
||||||
case_init(<<"init_before_reply">> = Case, _Req) ->
|
case_init(<<"init_before_reply">> = Case, _Req) ->
|
||||||
cowboy_error_h:ignore(?MODULE, case_init, 2),
|
cowboy_error_h:ignore(?MODULE, case_init, 2),
|
||||||
erlang:error(Case);
|
erlang:error(Case);
|
||||||
case_init(<<"init_after_reply">> = Case, Req) ->
|
case_init(<<"init_after_reply">> = Case, Req) ->
|
||||||
cowboy_error_h:ignore(?MODULE, case_init, 2),
|
cowboy_error_h:ignore(?MODULE, case_init, 2),
|
||||||
{ok, _Req1} = cowboy_req:reply(200, [], "http_handler_crashes", Req),
|
_ = cowboy_req:reply(200, [], "http_handler_crashes", Req),
|
||||||
erlang:error(Case);
|
erlang:error(Case);
|
||||||
case_init(<<"init_reply_handle_error">> = Case, Req) ->
|
case_init(<<"init_reply_handle_error">> = Case, Req) ->
|
||||||
{ok, Req1} = cowboy_req:reply(200, [], "http_handler_crashes", Req),
|
Req1 = cowboy_req:reply(200, [], "http_handler_crashes", Req),
|
||||||
{ok, Req1, Case};
|
{ok, Req1, Case};
|
||||||
case_init(<<"handle_before_reply">> = Case, Req) ->
|
case_init(<<"handle_before_reply">> = Case, Req) ->
|
||||||
{ok, Req, Case};
|
{ok, Req, Case};
|
||||||
|
@ -31,7 +31,7 @@ handle(_Req, <<"handle_before_reply">> = Case) ->
|
||||||
erlang:error(Case);
|
erlang:error(Case);
|
||||||
handle(Req, <<"handle_after_reply">> = Case) ->
|
handle(Req, <<"handle_after_reply">> = Case) ->
|
||||||
cowboy_error_h:ignore(?MODULE, handle, 2),
|
cowboy_error_h:ignore(?MODULE, handle, 2),
|
||||||
{ok, _Req1} = cowboy_req:reply(200, [], "http_handler_crashes", Req),
|
_ = cowboy_req:reply(200, [], "http_handler_crashes", Req),
|
||||||
erlang:error(Case).
|
erlang:error(Case).
|
||||||
|
|
||||||
terminate(_, _, _) ->
|
terminate(_, _, _) ->
|
||||||
|
|
|
@ -12,8 +12,7 @@ init({_Transport, http}, Req, Opts) ->
|
||||||
{ok, Req, #state{headers=Headers, body=Body}}.
|
{ok, Req, #state{headers=Headers, body=Body}}.
|
||||||
|
|
||||||
handle(Req, State=#state{headers=Headers, body=Body}) ->
|
handle(Req, State=#state{headers=Headers, body=Body}) ->
|
||||||
{ok, Req2} = cowboy_req:reply(200, Headers, Body, Req),
|
{ok, cowboy_req:reply(200, Headers, Body, Req), State}.
|
||||||
{ok, Req2, State}.
|
|
||||||
|
|
||||||
terminate(_, _, _) ->
|
terminate(_, _, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
-export([init/3, handle/2, terminate/3]).
|
-export([init/3, handle/2, terminate/3]).
|
||||||
|
|
||||||
init({_Transport, http}, Req, _Opts) ->
|
init({_Transport, http}, Req, _Opts) ->
|
||||||
{ok, Req2} = cowboy_req:reply(<<"666 Init Shutdown Testing">>,
|
Req2 = cowboy_req:reply(<<"666 Init Shutdown Testing">>,
|
||||||
[{<<"connection">>, <<"close">>}], Req),
|
[{<<"connection">>, <<"close">>}], Req),
|
||||||
{shutdown, Req2, undefined}.
|
{shutdown, Req2, undefined}.
|
||||||
|
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
{ok, Req2} = cowboy_req:reply(200, [], "Hello world!", Req),
|
Req2 = cowboy_req:reply(200, [], "Hello world!", Req),
|
||||||
{ok, Req2, State}.
|
{ok, Req2, State}.
|
||||||
|
|
||||||
terminate(_, _, _) ->
|
terminate(_, _, _) ->
|
||||||
|
|
|
@ -16,8 +16,7 @@ info(stream, Req, undefined) ->
|
||||||
stream(Req, ID, Acc) ->
|
stream(Req, ID, Acc) ->
|
||||||
case cowboy_req:body(Req) of
|
case cowboy_req:body(Req) of
|
||||||
{ok, <<>>, Req2} ->
|
{ok, <<>>, Req2} ->
|
||||||
{ok, Req3} = cowboy_req:reply(200, Req2),
|
{ok, cowboy_req:reply(200, Req2), undefined};
|
||||||
{ok, Req3, undefined};
|
|
||||||
{_, Data, Req2} ->
|
{_, Data, Req2} ->
|
||||||
parse_id(Req2, ID, << Acc/binary, Data/binary >>)
|
parse_id(Req2, ID, << Acc/binary, Data/binary >>)
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -9,8 +9,7 @@ init({_Transport, http}, Req, []) ->
|
||||||
|
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
{Result, Req2} = acc_multipart(Req, []),
|
{Result, Req2} = acc_multipart(Req, []),
|
||||||
{ok, Req3} = cowboy_req:reply(200, [], term_to_binary(Result), Req2),
|
{ok, cowboy_req:reply(200, [], term_to_binary(Result), Req2), State}.
|
||||||
{ok, Req3, State}.
|
|
||||||
|
|
||||||
terminate(_, _, _) ->
|
terminate(_, _, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -9,8 +9,7 @@ init(_, Req, []) ->
|
||||||
|
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
Req2 = multipart(Req),
|
Req2 = multipart(Req),
|
||||||
{ok, Req3} = cowboy_req:reply(200, Req2),
|
{ok, cowboy_req:reply(200, Req2), State}.
|
||||||
{ok, Req3, State}.
|
|
||||||
|
|
||||||
terminate(_, _, _) ->
|
terminate(_, _, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -5,15 +5,14 @@
|
||||||
-export([init/3, handle/2, terminate/3]).
|
-export([init/3, handle/2, terminate/3]).
|
||||||
|
|
||||||
init({_, http}, Req, _) ->
|
init({_, http}, Req, _) ->
|
||||||
{Attr, Req2} = cowboy_req:qs_val(<<"attr">>, Req),
|
#{attr := Attr} = cowboy_req:match_qs(Req, [attr]),
|
||||||
{ok, Req2, Attr}.
|
{ok, Req, Attr}.
|
||||||
|
|
||||||
handle(Req, <<"host_and_port">> = Attr) ->
|
handle(Req, <<"host_and_port">> = Attr) ->
|
||||||
{Host, Req2} = cowboy_req:host(Req),
|
Host = cowboy_req:host(Req),
|
||||||
{Port, Req3} = cowboy_req:port(Req2),
|
Port = cowboy_req:port(Req),
|
||||||
Value = [Host, "\n", integer_to_list(Port)],
|
Value = [Host, "\n", integer_to_list(Port)],
|
||||||
{ok, Req4} = cowboy_req:reply(200, [], Value, Req3),
|
{ok, cowboy_req:reply(200, [], Value, Req), Attr}.
|
||||||
{ok, Req4, Attr}.
|
|
||||||
|
|
||||||
terminate(_, _, _) ->
|
terminate(_, _, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -20,10 +20,10 @@ handle(Req, State) ->
|
||||||
false -> {ok, Req, State};
|
false -> {ok, Req, State};
|
||||||
true ->
|
true ->
|
||||||
case cowboy_req:has_resp_body(Req) of
|
case cowboy_req:has_resp_body(Req) of
|
||||||
false -> {ok, Req, State};
|
false ->
|
||||||
|
{ok, Req, State};
|
||||||
true ->
|
true ->
|
||||||
{ok, Req2} = cowboy_req:reply(200, Req),
|
{ok, cowboy_req:reply(200, Req), State}
|
||||||
{ok, Req2, State}
|
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,7 @@ handle(Req, State=#state{headers=_Headers, body=Body, reply=Reply}) ->
|
||||||
SFun2 = fun(SendFun) -> lists:foreach(SendFun, Body) end,
|
SFun2 = fun(SendFun) -> lists:foreach(SendFun, Body) end,
|
||||||
cowboy_req:set_resp_body_fun(chunked, SFun2, Req)
|
cowboy_req:set_resp_body_fun(chunked, SFun2, Req)
|
||||||
end,
|
end,
|
||||||
{ok, Req3} = cowboy_req:reply(200, Req2),
|
{ok, cowboy_req:reply(200, Req2), State}.
|
||||||
{ok, Req3, State}.
|
|
||||||
|
|
||||||
terminate(_, _, _) ->
|
terminate(_, _, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -9,7 +9,7 @@ init({_Transport, http}, Req, _Opts) ->
|
||||||
|
|
||||||
handle(Req, State) ->
|
handle(Req, State) ->
|
||||||
Req2 = cowboy_req:set([{resp_state, waiting_stream}], Req),
|
Req2 = cowboy_req:set([{resp_state, waiting_stream}], Req),
|
||||||
{ok, Req3} = cowboy_req:chunked_reply(200, Req2),
|
Req3 = cowboy_req:chunked_reply(200, Req2),
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
cowboy_req:chunk("streamed_handler\r\n", Req3),
|
cowboy_req:chunk("streamed_handler\r\n", Req3),
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
|
|
|
@ -27,5 +27,4 @@ to_text(Req, State) ->
|
||||||
{<<"This is REST!">>, Req, State}.
|
{<<"This is REST!">>, Req, State}.
|
||||||
|
|
||||||
from_text(Req, State) ->
|
from_text(Req, State) ->
|
||||||
{Path, Req2} = cowboy_req:path(Req),
|
{{true, cowboy_req:path(Req)}, Req, State}.
|
||||||
{{true, Path}, Req2, State}.
|
|
||||||
|
|
|
@ -17,17 +17,16 @@ content_types_provided(Req, State) ->
|
||||||
{[{{<<"text">>, <<"plain">>, '*'}, get_text_plain}], Req, State}.
|
{[{{<<"text">>, <<"plain">>, '*'}, get_text_plain}], Req, State}.
|
||||||
|
|
||||||
get_text_plain(Req, State) ->
|
get_text_plain(Req, State) ->
|
||||||
{{_, _, Param}, Req2} =
|
{_, _, Param} = cowboy_req:meta(media_type, Req, {{<<"text">>, <<"plain">>}, []}),
|
||||||
cowboy_req:meta(media_type, Req, {{<<"text">>, <<"plain">>}, []}),
|
|
||||||
Body = if
|
Body = if
|
||||||
Param == '*' ->
|
Param == '*' ->
|
||||||
<<"'*'">>;
|
<<"'*'">>;
|
||||||
Param == [] ->
|
Param == [] ->
|
||||||
<<"[]">>;
|
<<"[]">>;
|
||||||
Param /= [] ->
|
Param /= [] ->
|
||||||
iolist_to_binary([[Key, $=, Value] || {Key, Value} <- Param])
|
iolist_to_binary([[Key, $=, Value] || {Key, Value} <- Param])
|
||||||
end,
|
end,
|
||||||
{Body, Req2, State}.
|
{Body, Req, State}.
|
||||||
|
|
||||||
content_types_accepted(Req, State) ->
|
content_types_accepted(Req, State) ->
|
||||||
{[{{<<"text">>, <<"plain">>, '*'}, put_text_plain}], Req, State}.
|
{[{{<<"text">>, <<"plain">>, '*'}, put_text_plain}], Req, State}.
|
||||||
|
|
|
@ -16,17 +16,16 @@ get_text_plain(Req, State) ->
|
||||||
|
|
||||||
content_types_accepted(Req, State) ->
|
content_types_accepted(Req, State) ->
|
||||||
case cowboy_req:method(Req) of
|
case cowboy_req:method(Req) of
|
||||||
{<<"PATCH">>, Req0} ->
|
<<"PATCH">> ->
|
||||||
{[{{<<"text">>, <<"plain">>, []}, patch_text_plain}], Req0, State};
|
{[{{<<"text">>, <<"plain">>, []}, patch_text_plain}], Req, State};
|
||||||
{_, Req0} ->
|
_ ->
|
||||||
{[], Req0, State}
|
{[], Req, State}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
patch_text_plain(Req, State) ->
|
patch_text_plain(Req, State) ->
|
||||||
case cowboy_req:body(Req) of
|
case cowboy_req:body(Req) of
|
||||||
{ok, <<"halt">>, Req0} ->
|
{ok, <<"halt">>, Req0} ->
|
||||||
{ok, Req1} = cowboy_req:reply(400, Req0),
|
{halt, cowboy_req:reply(400, Req0), State};
|
||||||
{halt, Req1, State};
|
|
||||||
{ok, <<"false">>, Req0} ->
|
{ok, <<"false">>, Req0} ->
|
||||||
{false, Req0, State};
|
{false, Req0, State};
|
||||||
{ok, _Body, Req0} ->
|
{ok, _Body, Req0} ->
|
||||||
|
|
|
@ -5,24 +5,25 @@ init(_Transport, _Req, _Opts) ->
|
||||||
{upgrade, protocol, cowboy_rest}.
|
{upgrade, protocol, cowboy_rest}.
|
||||||
|
|
||||||
generate_etag(Req, State) ->
|
generate_etag(Req, State) ->
|
||||||
case cowboy_req:qs_val(<<"type">>, Req) of
|
#{type := Type} = cowboy_req:match_qs(Req, [type]),
|
||||||
|
case Type of
|
||||||
%% Correct return values from generate_etag/2.
|
%% Correct return values from generate_etag/2.
|
||||||
{<<"tuple-weak">>, Req2} ->
|
<<"tuple-weak">> ->
|
||||||
{{weak, <<"etag-header-value">>}, Req2, State};
|
{{weak, <<"etag-header-value">>}, Req, State};
|
||||||
{<<"tuple-strong">>, Req2} ->
|
<<"tuple-strong">> ->
|
||||||
{{strong, <<"etag-header-value">>}, Req2, State};
|
{{strong, <<"etag-header-value">>}, Req, State};
|
||||||
%% Backwards compatible return values from generate_etag/2.
|
%% Backwards compatible return values from generate_etag/2.
|
||||||
{<<"binary-weak-quoted">>, Req2} ->
|
<<"binary-weak-quoted">> ->
|
||||||
{<<"W/\"etag-header-value\"">>, Req2, State};
|
{<<"W/\"etag-header-value\"">>, Req, State};
|
||||||
{<<"binary-strong-quoted">>, Req2} ->
|
<<"binary-strong-quoted">> ->
|
||||||
{<<"\"etag-header-value\"">>, Req2, State};
|
{<<"\"etag-header-value\"">>, Req, State};
|
||||||
%% Invalid return values from generate_etag/2.
|
%% Invalid return values from generate_etag/2.
|
||||||
{<<"binary-strong-unquoted">>, Req2} ->
|
<<"binary-strong-unquoted">> ->
|
||||||
cowboy_error_h:ignore(cowboy_http, quoted_string, 2),
|
cowboy_error_h:ignore(cowboy_http, quoted_string, 2),
|
||||||
{<<"etag-header-value">>, Req2, State};
|
{<<"etag-header-value">>, Req, State};
|
||||||
{<<"binary-weak-unquoted">>, Req2} ->
|
<<"binary-weak-unquoted">> ->
|
||||||
cowboy_error_h:ignore(cowboy_http, quoted_string, 2),
|
cowboy_error_h:ignore(cowboy_http, quoted_string, 2),
|
||||||
{<<"W/etag-header-value">>, Req2, State}
|
{<<"W/etag-header-value">>, Req, State}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
content_types_provided(Req, State) ->
|
content_types_provided(Req, State) ->
|
||||||
|
|
|
@ -10,8 +10,7 @@ init(_Any, _Req, _Opts) ->
|
||||||
{upgrade, protocol, cowboy_websocket}.
|
{upgrade, protocol, cowboy_websocket}.
|
||||||
|
|
||||||
websocket_init(_TransportName, Req, _Opts) ->
|
websocket_init(_TransportName, Req, _Opts) ->
|
||||||
{ok, Req2} = cowboy_req:reply(403, Req),
|
{shutdown, cowboy_req:reply(403, Req)}.
|
||||||
{shutdown, Req2}.
|
|
||||||
|
|
||||||
websocket_handle(_Frame, _Req, _State) ->
|
websocket_handle(_Frame, _Req, _State) ->
|
||||||
exit(badarg).
|
exit(badarg).
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue