aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS7
-rw-r--r--CHANGELOG.md55
-rw-r--r--Makefile2
-rw-r--r--examples/elixir_hello_world/mix.exs2
-rw-r--r--examples/rest_pastebin/src/toppage_handler.erl15
-rw-r--r--guide/middlewares.md2
-rw-r--r--guide/req.md7
-rw-r--r--guide/resources.md17
-rw-r--r--guide/rest_handlers.md84
-rw-r--r--guide/toc.md4
-rw-r--r--rebar.config2
-rw-r--r--src/cowboy.app.src2
-rw-r--r--src/cowboy_protocol.erl14
-rw-r--r--src/cowboy_req.erl130
-rw-r--r--src/cowboy_rest.erl120
-rw-r--r--src/cowboy_websocket.erl4
-rw-r--r--test/http_SUITE.erl15
-rw-r--r--test/rest_created_path_resource.erl35
-rw-r--r--test/rest_forbidden_resource.erl15
19 files changed, 265 insertions, 267 deletions
diff --git a/AUTHORS b/AUTHORS
index 3b847f2..5eae10e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -3,19 +3,19 @@ Cowboy is available thanks to the work of:
Loïc Hoguin
Magnus Klaar
Anthony Ramine
-Tom Burdick
Adam Cammack
+Tom Burdick
Ali Sabil
Paul Oliver
+Slava Yurin
Yurii Rashkovskii
Andrew Majorov
+James Fish
Josh Toft
-Slava Yurin
Steven Gravell
Andrew Thompson
Hunter Morris
Ivan Lisenkov
-James Fish
Vladimir Dronnikov
0x00F6
0xAX
@@ -52,4 +52,5 @@ Unix1
alisdair sullivan
dbmercer
derdesign
+rambocoder
serge
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fa81c74..825d820 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,61 @@
CHANGELOG
=========
+0.8.3
+-----
+
+ * Remove init_stream/5, add stream_body/2
+
+ It's better to allow configuring the streamed chunk size on
+ a per chunk basis. Also easier to use.
+
+ * Update Ranch to 0.8.0
+
+ Much faster. Also improved stability.
+
+0.8.2
+-----
+
+ * Add error_hook and ssl_hello_world example
+
+ * Greatly improve the performance of body reading operations
+
+ The streamed chunk size is now configurable through the new
+ function cowboy_req:init_stream/5.
+
+ * Add cowboy_req:body/2 and cowboy_req:body_qs/2
+
+ These functions take an additional argument indicating the
+ maximum size of the body. They will return {error, badlength}
+ if the size is too large, or {error, chunked} if the body
+ was sent using the chunked Transfer-Encoding and its size
+ cannot be determined.
+
+ The function body/1 is now an alias to body/2 with a maximum
+ body size of 8MB. Likewise, the function body_qs/1 is an alias
+ of body_qs/2 with a maximum body size of 16KB.
+
+ * Properly handle explicit identity Transfer-Encoding in body_length/1
+
+ * Small but noticeable performance improvement in the critical path
+
+ We stopped using binary:match/2 in favor of custom functions.
+ This makes Cowboy 0.5ms faster per request.
+
+ * Prevent loop handlers from awakening after sending a response
+
+ * Optimize cowboy_static initialization code
+
+ * Make path checks in cowboy_static cross-platform
+
+ * Allow '*' for REST content types parameters in content_types_provided
+
+ * Fix cowboy_router types
+
+ * Update Ranch to 0.6.2; adds support for two new SSL options
+
+ * Improve documentation
+
0.8.1
-----
diff --git a/Makefile b/Makefile
index 91f547c..0bfc1c7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
# See LICENSE for licensing information.
PROJECT = cowboy
-RANCH_VSN = 0.6.2
+RANCH_VSN = 0.8.0
ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \
+warn_shadow_vars +warn_obsolete_guard # +bin_opt_info +warn_missing_spec
diff --git a/examples/elixir_hello_world/mix.exs b/examples/elixir_hello_world/mix.exs
index 065024d..9f113a6 100644
--- a/examples/elixir_hello_world/mix.exs
+++ b/examples/elixir_hello_world/mix.exs
@@ -14,7 +14,7 @@ defmodule ElixirHelloWorld.Mixfile do
end
defp deps do
- [ {:ranch, github: "extend/ranch", tag: "0.6.2"},
+ [ {:ranch, github: "extend/ranch", tag: "0.8.0"},
{:cowboy, github: "extend/cowboy"} ]
end
end
diff --git a/examples/rest_pastebin/src/toppage_handler.erl b/examples/rest_pastebin/src/toppage_handler.erl
index 5e904d9..a6a0829 100644
--- a/examples/rest_pastebin/src/toppage_handler.erl
+++ b/examples/rest_pastebin/src/toppage_handler.erl
@@ -9,8 +9,6 @@
-export([content_types_provided/2]).
-export([content_types_accepted/2]).
-export([resource_exists/2]).
--export([post_is_create/2]).
--export([create_path/2]).
%% Callback Callbacks
-export([create_paste/2]).
@@ -47,17 +45,16 @@ resource_exists(Req, _State) ->
end
end.
-post_is_create(Req, State) ->
- {true, Req, State}.
-
-create_path(Req, State) ->
- {<<$/, (new_paste_id())/binary>>, Req, State}.
-
create_paste(Req, State) ->
{<<$/, PasteID/binary>>, Req2} = cowboy_req:meta(put_path, Req),
{ok, [{<<"paste">>, Paste}], Req3} = cowboy_req:body_qs(Req2),
ok = file:write_file(full_path(PasteID), Paste),
- {true, Req3, State}.
+ case cowboy_req:method(Req3) of
+ {<<"POST">>, Req4} ->
+ {<<$/, (new_paste_id())/binary>>, Req4, State};
+ {_, Req4} ->
+ {true, Req4, State}
+ end.
paste_html(Req, index) ->
{read_file("index.html"), Req, index};
diff --git a/guide/middlewares.md b/guide/middlewares.md
index b5f055c..a1f1f8d 100644
--- a/guide/middlewares.md
+++ b/guide/middlewares.md
@@ -48,7 +48,7 @@ the routing information. It is a list of tuples with the first
element being an atom and the second any Erlang term.
Two values in the environment are reserved:
- * `listener` contains the pid of the listener process
+ * `listener` contains the name of the listener
* `result` contains the result of the processing
The `listener` value is always defined. The `result` value can be
diff --git a/guide/req.md b/guide/req.md
index f627737..aa3bf4b 100644
--- a/guide/req.md
+++ b/guide/req.md
@@ -33,7 +33,6 @@ The following access functions are defined in `cowboy_req`:
* `method/1`: the request method (`<<"GET">>`, `<<"POST">>`...)
* `version/1`: the HTTP version (`{1,0}` or `{1,1}`)
* `peer/1`: the peer address and port number
- * `peer_addr/1`: the peer address guessed using the request headers
* `host/1`: the hostname requested
* `host_info/1`: the result of the `[...]` match on the host
* `port/1`: the port number used for the connection
@@ -104,9 +103,9 @@ and `body_qs/2` will return `{error, badlength}`. If the request
contains chunked body, `body/1`, `body/2`, `body_qs/1`
and `body_qs/2` will return `{error, chunked}`.
If you get either of the above two errors, you will want to
-handle the body of the request using `stream_body/1` and
-`skip_body/1`, with the streaming process optionally
-initialized using `init_stream/4` or `init_stream/5`.
+handle the body of the request using `stream_body/1`,
+`stream_body/2` and `skip_body/1`, with the streaming process
+optionally initialized using `init_stream/4`.
Multipart request body
----------------------
diff --git a/guide/resources.md b/guide/resources.md
new file mode 100644
index 0000000..1d1b18a
--- /dev/null
+++ b/guide/resources.md
@@ -0,0 +1,17 @@
+Resources
+=========
+
+Frameworks
+----------
+
+ * Please send a pull request!
+
+Helper libraries
+----------------
+
+ * Please send a pull request!
+
+Articles
+--------
+
+ * Please send a pull request!
diff --git a/guide/rest_handlers.md b/guide/rest_handlers.md
index 62f0ba1..1eccc65 100644
--- a/guide/rest_handlers.md
+++ b/guide/rest_handlers.md
@@ -25,9 +25,89 @@ Not done yet. Feel free to use the one that is currently being worked on.
Callbacks
---------
-Please see the Webmachine documentation at this time.
+All callbacks are optional. Some may become mandatory depending
+on what other defined callbacks return. The flow diagram should
+be a pretty good resource to determine which callbacks you need.
+
+When the request starts being processed, Cowboy will call the
+`rest_init/2` function if it is defined, with the Req object
+and the handler options as arguments. This function must return
+`{ok, Req, State}` where `State` is the handler's state that all
+subsequent callbacks will receive.
+
+At the end of every request, the special callback `rest_terminate/2`
+will be called if it is defined. It cannot be used to send a reply,
+and must always return `ok`.
+
+All other callbacks are resource callbacks. They all take two
+arguments, the Req object and the State, and return a three-element
+tuple of the form `{Value, Req, State}`.
+
+The following table summarizes the callbacks and their default values.
+If the callback isn't defined, then the default value will be used.
+Please look at the flow diagram to find out the result of each return
+value.
+
+All callbacks can also return `{halt, Req, State}` to stop execution
+of the request, at which point `rest_terminate/2` will be called.
+
+In the following table, "skip" means the callback is entirely skipped
+if it is undefined, moving directly to the next step. Similarly, an
+empty column means there is no default value for this callback.
+
+| Callback name | Default value |
+| ---------------------- | ------------------------- |
+| allowed_methods | `[<<"GET">>, <<"HEAD">>]` |
+| allow_missing_post | `true` |
+| charsets_provided | skip |
+| content_types_accepted | |
+| content_types_provided | |
+| delete_completed | `true` |
+| delete_resource | `false` |
+| expires | `undefined` |
+| forbidden | `false` |
+| generate_etag | `undefined` |
+| is_authorized | `true` |
+| is_conflict | `false` |
+| known_content_type | `true` |
+| known_methods | `[<<"GET">>, <<"HEAD">>, <<"POST">>, <<"PUT">>, <<"PATCH">>, <<"DELETE">>, <<"OPTIONS">>]` |
+| languages_provided | skip |
+| last_modified | `undefined` |
+| malformed_request | `false` |
+| moved_permanently | `false` |
+| moved_temporarily | `false` |
+| multiple_choices | `false` |
+| options | |
+| previously_existed | `false` |
+| resource_exists | `true` |
+| service_available | `true` |
+| uri_too_long | `false` |
+| valid_content_headers | `true` |
+| valid_entity_length | `true` |
+| variances | `[]` |
+
+As you can see, Cowboy tries to move on with the request whenever
+possible by using well thought out default values.
+
+In addition to these, there can be any number of user-defined
+callbacks that are specified through `content_types_accepted/2`
+and `content_types_provided/2`. They can take any name, however
+it is recommended to use a separate prefix for the callbacks of
+each function. For example, `from_html` and `to_html` indicate
+in the first case that we're accepting a resource given as HTML,
+and in the second case that we send one as HTML.
Usage
-----
-Please see the examples at this time.
+Like Websocket, REST is a sub-protocol of HTTP. It therefore
+requires a protocol upgrade.
+
+``` erlang
+init({tcp, http}, Req, Opts) ->
+ {upgrade, protocol, cowboy_rest}.
+```
+
+Cowboy will then switch to the REST protocol and start executing
+the flow diagram, starting from `rest_init/2` if it's defined,
+and ending with `rest_terminate/2` also if defined.
diff --git a/guide/toc.md b/guide/toc.md
index 2f0d914..6909bed 100644
--- a/guide/toc.md
+++ b/guide/toc.md
@@ -58,3 +58,7 @@ Cowboy User Guide
* One process for many requests
* Lowercase header names
* Improving performance
+ * [Resources](resources.md)
+ * Frameworks
+ * Helper libraries
+ * Articles
diff --git a/rebar.config b/rebar.config
index 64f8d0e..0ddc628 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,3 +1,3 @@
{deps, [
- {ranch, ".*", {git, "git://github.com/extend/ranch.git", "0.6.2"}}
+ {ranch, ".*", {git, "git://github.com/extend/ranch.git", "0.8.0"}}
]}.
diff --git a/src/cowboy.app.src b/src/cowboy.app.src
index e6309f9..d224c5d 100644
--- a/src/cowboy.app.src
+++ b/src/cowboy.app.src
@@ -17,7 +17,7 @@
{description, "Small, fast, modular HTTP server."},
{sub_description, "Cowboy is also a socket acceptor pool, "
"able to accept connections for any kind of TCP protocol."},
- {vsn, "0.8.1"},
+ {vsn, "0.8.3"},
{modules, []},
{registered, [cowboy_clock, cowboy_sup]},
{applications, [
diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl
index 78e3b78..be351b7 100644
--- a/src/cowboy_protocol.erl
+++ b/src/cowboy_protocol.erl
@@ -84,9 +84,9 @@
%% API.
%% @doc Start an HTTP protocol process.
--spec start_link(pid(), inet:socket(), module(), any()) -> {ok, pid()}.
-start_link(ListenerPid, Socket, Transport, Opts) ->
- Pid = spawn_link(?MODULE, init, [ListenerPid, Socket, Transport, Opts]),
+-spec start_link(any(), inet:socket(), module(), any()) -> {ok, pid()}.
+start_link(Ref, Socket, Transport, Opts) ->
+ Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
{ok, Pid}.
%% Internal.
@@ -100,8 +100,8 @@ get_value(Key, Opts, Default) ->
end.
%% @private
--spec init(pid(), inet:socket(), module(), any()) -> ok.
-init(ListenerPid, Socket, Transport, Opts) ->
+-spec init(any(), inet:socket(), module(), any()) -> ok.
+init(Ref, Socket, Transport, Opts) ->
Compress = get_value(compress, Opts, false),
MaxEmptyLines = get_value(max_empty_lines, Opts, 5),
MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64),
@@ -110,11 +110,11 @@ init(ListenerPid, Socket, Transport, Opts) ->
MaxKeepalive = get_value(max_keepalive, Opts, 100),
MaxRequestLineLength = get_value(max_request_line_length, Opts, 4096),
Middlewares = get_value(middlewares, Opts, [cowboy_router, cowboy_handler]),
- Env = [{listener, ListenerPid}|get_value(env, Opts, [])],
+ Env = [{listener, Ref}|get_value(env, Opts, [])],
OnRequest = get_value(onrequest, Opts, undefined),
OnResponse = get_value(onresponse, Opts, undefined),
Timeout = get_value(timeout, Opts, 5000),
- ok = ranch:accept_ack(ListenerPid),
+ ok = ranch:accept_ack(Ref),
wait_request(<<>>, #state{socket=Socket, transport=Transport,
middlewares=Middlewares, compress=Compress, env=Env,
max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive,
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 966e463..4ec42f9 100644
--- a/src/cowboy_req.erl
+++ b/src/cowboy_req.erl
@@ -46,7 +46,6 @@
-export([method/1]).
-export([version/1]).
-export([peer/1]).
--export([peer_addr/1]).
-export([host/1]).
-export([host_info/1]).
-export([port/1]).
@@ -78,8 +77,8 @@
-export([has_body/1]).
-export([body_length/1]).
-export([init_stream/4]).
--export([init_stream/5]).
-export([stream_body/1]).
+-export([stream_body/2]).
-export([skip_body/1]).
-export([body/1]).
-export([body/2]).
@@ -155,8 +154,8 @@
meta = [] :: [{atom(), any()}],
%% Request body.
- body_state = waiting :: waiting | done | {stream,
- non_neg_integer(), non_neg_integer(), fun(), any(), fun()},
+ body_state = waiting :: waiting | done
+ | {stream, non_neg_integer(), fun(), any(), fun()},
multipart = undefined :: undefined | {non_neg_integer(), fun()},
buffer = <<>> :: binary(),
@@ -230,29 +229,6 @@ version(Req) ->
peer(Req) ->
{Req#http_req.peer, Req}.
-%% @doc Returns the peer address calculated from headers.
--spec peer_addr(Req) -> {inet:ip_address(), Req} when Req::req().
-peer_addr(Req = #http_req{}) ->
- {RealIp, Req1} = header(<<"x-real-ip">>, Req),
- {ForwardedForRaw, Req2} = header(<<"x-forwarded-for">>, Req1),
- {{PeerIp, _PeerPort}, Req3} = peer(Req2),
- ForwardedFor = case ForwardedForRaw of
- undefined ->
- undefined;
- ForwardedForRaw ->
- case re:run(ForwardedForRaw, "^(?<first_ip>[^\\,]+)",
- [{capture, [first_ip], binary}]) of
- {match, [FirstIp]} -> FirstIp;
- _Any -> undefined
- end
- end,
- {ok, PeerAddr} = if
- is_binary(RealIp) -> inet_parse:address(binary_to_list(RealIp));
- is_binary(ForwardedFor) -> inet_parse:address(binary_to_list(ForwardedFor));
- true -> {ok, PeerIp}
- end,
- {PeerAddr, Req3}.
-
%% @doc Return the host binary string.
-spec host(Req) -> {binary(), Req} when Req::req().
host(Req) ->
@@ -426,61 +402,61 @@ 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, Req, Default) when Name =:= <<"accept">> ->
+parse_header(Name = <<"accept">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:list(Value, fun cowboy_http:media_range/2)
end);
-parse_header(Name, Req, Default) when Name =:= <<"accept-charset">> ->
+parse_header(Name = <<"accept-charset">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:conneg/2)
end);
-parse_header(Name, Req, Default) when Name =:= <<"accept-encoding">> ->
+parse_header(Name = <<"accept-encoding">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:list(Value, fun cowboy_http:conneg/2)
end);
-parse_header(Name, Req, Default) when Name =:= <<"accept-language">> ->
+parse_header(Name = <<"accept-language">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2)
end);
-parse_header(Name, Req, Default) when Name =:= <<"authorization">> ->
+parse_header(Name = <<"authorization">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:token_ci(Value, fun cowboy_http:authorization/2)
end);
-parse_header(Name, Req, Default) when Name =:= <<"content-length">> ->
+parse_header(Name = <<"content-length">>, Req, Default) ->
parse_header(Name, Req, Default, fun cowboy_http:digits/1);
-parse_header(Name, Req, Default) when Name =:= <<"content-type">> ->
+parse_header(Name = <<"content-type">>, Req, Default) ->
parse_header(Name, Req, Default, fun cowboy_http:content_type/1);
parse_header(Name = <<"cookie">>, Req, Default) ->
parse_header(Name, Req, Default, fun cowboy_http:cookie_list/1);
-parse_header(Name, Req, Default) when Name =:= <<"expect">> ->
+parse_header(Name = <<"expect">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:expectation/2)
end);
parse_header(Name, Req, Default)
- when Name =:= <<"if-match">>; Name =:= <<"if-none-match">> ->
+ when Name =:= <<"if-match">>;
+ Name =:= <<"if-none-match">> ->
parse_header(Name, Req, Default, fun cowboy_http:entity_tag_match/1);
parse_header(Name, Req, Default)
when Name =:= <<"if-modified-since">>;
Name =:= <<"if-unmodified-since">> ->
parse_header(Name, Req, Default, fun cowboy_http:http_date/1);
-parse_header(Name, Req, Default) when Name =:= <<"sec-websocket-protocol">> ->
+parse_header(Name, Req, Default)
+ when Name =:= <<"sec-websocket-protocol">>;
+ Name =:= <<"x-forwarded-for">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:token/2)
end);
%% @todo Extension parameters.
-parse_header(Name, Req, Default) when Name =:= <<"transfer-encoding">> ->
- parse_header(Name, Req, Default,
- fun (Value) ->
- cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
- end);
-parse_header(Name, Req, Default) when Name =:= <<"upgrade">> ->
+parse_header(Name, Req, Default)
+ when Name =:= <<"transfer-encoding">>;
+ Name =:= <<"upgrade">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
@@ -566,7 +542,7 @@ set_meta(Name, Value, Req=#http_req{meta=Meta}) ->
%% Request Body API.
%% @doc Return whether the request message has a body.
--spec has_body(cowboy_req:req()) -> boolean().
+-spec has_body(req()) -> boolean().
has_body(Req) ->
case lists:keyfind(<<"content-length">>, 1, Req#http_req.headers) of
{_, <<"0">>} ->
@@ -591,17 +567,11 @@ body_length(Req) ->
{undefined, Req2}
end.
-%% @equiv init_stream(1000000, TransferDecode, TransferState, ContentDecode, Req)
--spec init_stream(fun(), any(), fun(), Req)
- -> {ok, Req} when Req::req().
-init_stream(TransferDecode, TransferState, ContentDecode, Req) ->
- init_stream(1000000, TransferDecode, TransferState, ContentDecode, Req).
-
%% @doc Initialize body streaming and set custom decoding functions.
%%
%% Calling this function is optional. It should only be used if you
%% need to override the default behavior of Cowboy. Otherwise you
-%% should call stream_body/1 directly.
+%% should call stream_body/{1,2} directly.
%%
%% Two decodings happen. First a decoding function is applied to the
%% transferred data, and then another is applied to the actual content.
@@ -613,27 +583,36 @@ init_stream(TransferDecode, TransferState, ContentDecode, Req) ->
%% Content encoding is generally used for compression.
%%
%% Standard encodings can be found in cowboy_http.
--spec init_stream(non_neg_integer(), fun(), any(), fun(), Req)
+-spec init_stream(fun(), any(), fun(), Req)
-> {ok, Req} when Req::req().
-init_stream(MaxLength, TransferDecode, TransferState, ContentDecode, Req) ->
+init_stream(TransferDecode, TransferState, ContentDecode, Req) ->
{ok, Req#http_req{body_state=
- {stream, 0, MaxLength, TransferDecode, TransferState, ContentDecode}}}.
+ {stream, 0, TransferDecode, TransferState, ContentDecode}}}.
+
+%% @equiv stream_body(1000000, Req)
+-spec stream_body(Req) -> {ok, binary(), Req}
+ | {done, Req} | {error, atom()} when Req::req().
+stream_body(Req) ->
+ stream_body(1000000, Req).
%% @doc Stream the request's body.
%%
%% This is the most low level function to read the request body.
%%
-%% In most cases, if they weren't defined before using stream_body/4,
+%% In most cases, if they weren't defined before using init_stream/4,
%% this function will guess which transfer and content encodings were
%% used for building the request body, and configure the decoding
%% functions that will be used when streaming.
%%
%% It then starts streaming the body, returning {ok, Data, Req}
%% for each streamed part, and {done, Req} when it's finished streaming.
--spec stream_body(Req) -> {ok, binary(), Req}
+%%
+%% You can limit the size of the chunks being returned by using the
+%% second argument which is the size in bytes. It defaults to 1000000 bytes.
+-spec stream_body(non_neg_integer(), Req) -> {ok, binary(), Req}
| {done, Req} | {error, atom()} when Req::req().
-stream_body(Req=#http_req{body_state=waiting,
- version=Version, transport=Transport, socket=Socket}) ->
+stream_body(MaxLength, Req=#http_req{body_state=waiting, version=Version,
+ transport=Transport, socket=Socket}) ->
{ok, ExpectHeader, Req1} = parse_header(<<"expect">>, Req),
case ExpectHeader of
[<<"100-continue">>] ->
@@ -645,8 +624,8 @@ stream_body(Req=#http_req{body_state=waiting,
end,
case parse_header(<<"transfer-encoding">>, Req1) of
{ok, [<<"chunked">>], Req2} ->
- stream_body(Req2#http_req{body_state=
- {stream, 0, 1000000,
+ stream_body(MaxLength, Req2#http_req{body_state=
+ {stream, 0,
fun cowboy_http:te_chunked/2, {0, 0},
fun cowboy_http:ce_identity/1}});
{ok, [<<"identity">>], Req2} ->
@@ -655,25 +634,25 @@ stream_body(Req=#http_req{body_state=waiting,
0 ->
{done, Req3#http_req{body_state=done}};
Length ->
- stream_body(Req3#http_req{body_state=
- {stream, Length, 1000000,
+ stream_body(MaxLength, Req3#http_req{body_state=
+ {stream, Length,
fun cowboy_http:te_identity/2, {0, Length},
fun cowboy_http:ce_identity/1}})
end
end;
-stream_body(Req=#http_req{body_state=done}) ->
+stream_body(_, Req=#http_req{body_state=done}) ->
{done, Req};
-stream_body(Req=#http_req{buffer=Buffer})
+stream_body(_, Req=#http_req{buffer=Buffer})
when Buffer =/= <<>> ->
transfer_decode(Buffer, Req#http_req{buffer= <<>>});
-stream_body(Req) ->
- stream_body_recv(Req).
+stream_body(MaxLength, Req) ->
+ stream_body_recv(MaxLength, Req).
--spec stream_body_recv(Req)
+-spec stream_body_recv(non_neg_integer(), Req)
-> {ok, binary(), Req} | {error, atom()} when Req::req().
-stream_body_recv(Req=#http_req{
+stream_body_recv(MaxLength, Req=#http_req{
transport=Transport, socket=Socket, buffer=Buffer,
- body_state={stream, Length, MaxLength, _, _, _}}) ->
+ body_state={stream, Length, _, _, _}}) ->
%% @todo Allow configuring the timeout.
case Transport:recv(Socket, min(Length, MaxLength), 5000) of
{ok, Data} -> transfer_decode(<< Buffer/binary, Data/binary >>,
@@ -683,20 +662,20 @@ stream_body_recv(Req=#http_req{
-spec transfer_decode(binary(), Req)
-> {ok, binary(), Req} | {error, atom()} when Req::req().
-transfer_decode(Data, Req=#http_req{body_state={stream, _, MaxLength,
+transfer_decode(Data, Req=#http_req{body_state={stream, _,
TransferDecode, TransferState, ContentDecode}}) ->
case TransferDecode(Data, TransferState) of
{ok, Data2, Rest, TransferState2} ->
content_decode(ContentDecode, Data2,
- Req#http_req{buffer=Rest, body_state={stream, 0, MaxLength,
+ Req#http_req{buffer=Rest, body_state={stream, 0,
TransferDecode, TransferState2, ContentDecode}});
%% @todo {header(s) for chunked
more ->
- stream_body_recv(Req#http_req{buffer=Data, body_state={stream,
- 0, MaxLength, TransferDecode, TransferState, ContentDecode}});
+ stream_body_recv(0, Req#http_req{buffer=Data, body_state={stream,
+ 0, TransferDecode, TransferState, ContentDecode}});
{more, Length, Data2, TransferState2} ->
content_decode(ContentDecode, Data2,
- Req#http_req{body_state={stream, Length, MaxLength,
+ Req#http_req{body_state={stream, Length,
TransferDecode, TransferState2, ContentDecode}});
{done, Length, Rest} ->
Req2 = transfer_decode_done(Length, Rest, Req),
@@ -1010,8 +989,7 @@ reply(Status, Headers, Body, Req=#http_req{
reply_may_compress(Status, Headers, Body, Req,
RespHeaders, HTTP11Headers, Method) ->
BodySize = iolist_size(Body),
- {ok, Encodings, Req2}
- = cowboy_req:parse_header(<<"accept-encoding">>, Req),
+ {ok, Encodings, Req2} = parse_header(<<"accept-encoding">>, Req),
CanGzip = (BodySize > 300)
andalso (false =:= lists:keyfind(<<"content-encoding">>,
1, Headers))
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index cb4fffb..526f102 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -96,9 +96,8 @@ known_methods(Req, State=#state{method=Method}) ->
case call(Req, State, known_methods) of
no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>;
Method =:= <<"POST">>; Method =:= <<"PUT">>;
- Method =:= <<"DELETE">>; Method =:= <<"TRACE">>;
- Method =:= <<"CONNECT">>; Method =:= <<"OPTIONS">>;
- Method =:= <<"PATCH">> ->
+ Method =:= <<"PATCH">>; Method =:= <<"DELETE">>;
+ Method =:= <<"OPTIONS">> ->
next(Req, State, fun uri_too_long/2);
no_call ->
next(Req, State, 501);
@@ -236,8 +235,8 @@ content_types_provided(Req, State) ->
normalize_content_types({ContentType, Callback})
when is_binary(ContentType) ->
{cowboy_http:content_type(ContentType), Callback};
-normalize_content_types(Provided) ->
- Provided.
+normalize_content_types(Normalized) ->
+ Normalized.
prioritize_accept(Accept) ->
lists:sort(
@@ -728,16 +727,18 @@ is_post_to_missing_resource(Req, State, OnFalse) ->
respond(Req, State, OnFalse).
allow_missing_post(Req, State, OnFalse) ->
- expect(Req, State, allow_missing_post, true, fun post_is_create/2, OnFalse).
+ expect(Req, State, allow_missing_post, true, fun post_resource/2, OnFalse).
+
+post_resource(Req, State) ->
+ accept_resource(Req, State, 204).
method(Req, State=#state{method= <<"DELETE">>}) ->
delete_resource(Req, State);
-method(Req, State=#state{method= <<"POST">>}) ->
- post_is_create(Req, State);
method(Req, State=#state{method= <<"PUT">>}) ->
is_conflict(Req, State);
-method(Req, State=#state{method= <<"PATCH">>}) ->
- patch_resource(Req, State);
+method(Req, State=#state{method=Method})
+ when Method =:= <<"POST">>; Method =:= <<"PATCH">> ->
+ accept_resource(Req, State, 204);
method(Req, State=#state{method=Method})
when Method =:= <<"GET">>; Method =:= <<"HEAD">> ->
set_resp_body_etag(Req, State);
@@ -752,79 +753,21 @@ delete_resource(Req, State) ->
delete_completed(Req, State) ->
expect(Req, State, delete_completed, true, fun has_resp_body/2, 202).
-%% post_is_create/2 indicates whether the POST method can create new resources.
-post_is_create(Req, State) ->
- expect(Req, State, post_is_create, false, fun process_post/2, fun create_path/2).
-
-%% When the POST method can create new resources, create_path/2 will be called
-%% and is expected to return the full path to the new resource
-%% (including the leading /).
-create_path(Req, State) ->
- case call(Req, State, create_path) of
- no_call ->
- put_resource(Req, State, fun created_path/2);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {Path, Req2, HandlerState} ->
- {HostURL, Req3} = cowboy_req:host_url(Req2),
- State2 = State#state{handler_state=HandlerState},
- Req4 = cowboy_req:set_resp_header(
- <<"location">>, << HostURL/binary, Path/binary >>, Req3),
- put_resource(cowboy_req:set_meta(put_path, Path, Req4),
- State2, 303)
- end.
-
-%% Called after content_types_accepted is called for POST methods
-%% when create_path did not exist. Expects the full path to
-%% be returned and MUST exist in the case that create_path
-%% does not.
-created_path(Req, State) ->
- case call(Req, State, created_path) of
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {Path, Req2, HandlerState} ->
- {HostURL, Req3} = cowboy_req:host_url(Req2),
- State2 = State#state{handler_state=HandlerState},
- Req4 = cowboy_req:set_resp_header(
- <<"location">>, << HostURL/binary, Path/binary >>, Req3),
- respond(cowboy_req:set_meta(put_path, Path, Req4),
- State2, 303)
- end.
-
-%% process_post should return true when the POST body could be processed
-%% and false when it hasn't, in which case a 500 error is sent.
-process_post(Req, State) ->
- case call(Req, State, process_post) of
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {true, Req2, HandlerState} ->
- State2 = State#state{handler_state=HandlerState},
- next(Req2, State2, fun is_new_resource/2);
- {false, Req2, HandlerState} ->
- State2 = State#state{handler_state=HandlerState},
- respond(Req2, State2, 500)
- end.
-
is_conflict(Req, State) ->
expect(Req, State, is_conflict, false, fun put_resource/2, 409).
put_resource(Req, State) ->
- Path = cowboy_req:get(path, Req),
- put_resource(cowboy_req:set_meta(put_path, Path, Req),
- State, fun is_new_resource/2).
+ accept_resource(Req, State, fun is_new_resource/2).
%% content_types_accepted should return a list of media types and their
%% associated callback functions in the same format as content_types_provided.
%%
%% The callback will then be called and is expected to process the content
-%% pushed to the resource in the request body. The path to the new resource
-%% may be different from the request path, and is stored as request metadata.
-%% It is always defined past this point. It can be retrieved as demonstrated:
-%% {PutPath, Req2} = cowboy_req:meta(put_path, Req)
+%% pushed to the resource in the request body.
%%
-%%content_types_accepted SHOULD return a different list
+%% content_types_accepted SHOULD return a different list
%% for each HTTP method.
-put_resource(Req, State, OnTrue) ->
+accept_resource(Req, State, OnTrue) ->
case call(Req, State, content_types_accepted) of
no_call ->
respond(Req, State, 415);
@@ -838,27 +781,6 @@ put_resource(Req, State, OnTrue) ->
choose_content_type(Req3, State2, OnTrue, ContentType, CTA2)
end.
-%% content_types_accepted should return a list of media types and their
-%% associated callback functions in the same format as content_types_provided.
-%%
-%% The callback will then be called and is expected to process the content
-%% pushed to the resource in the request body.
-%%
-%% content_types_accepted SHOULD return a different list
-%% for each HTTP method.
-patch_resource(Req, State) ->
- case call(Req, State, content_types_accepted) of
- no_call ->
- respond(Req, State, 415);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {CTM, Req2, HandlerState} ->
- State2 = State#state{handler_state=HandlerState},
- {ok, ContentType, Req3}
- = cowboy_req:parse_header(<<"content-type">>, Req2),
- choose_content_type(Req3, State2, 204, ContentType, CTM)
- end.
-
%% The special content type '*' will always match. It can be used as a
%% catch-all content type for accepting any kind of request content.
%% Note that because it will always match, it should be the last of the
@@ -880,9 +802,8 @@ choose_content_type(Req, State, OnTrue,
choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) ->
choose_content_type(Req, State, OnTrue, ContentType, Tail).
-process_content_type(Req,
- State=#state{handler=Handler, handler_state=HandlerState},
- OnTrue, Fun) ->
+process_content_type(Req, State=#state{method=Method,
+ handler=Handler, handler_state=HandlerState}, OnTrue, Fun) ->
case call(Req, State, Fun) of
no_call ->
error_logger:error_msg(
@@ -898,7 +819,12 @@ process_content_type(Req,
next(Req2, State2, OnTrue);
{false, Req2, HandlerState2} ->
State2 = State#state{handler_state=HandlerState2},
- respond(Req2, State2, 422)
+ respond(Req2, State2, 422);
+ {ResURL, Req2, HandlerState2} when Method =:= <<"POST">> ->
+ State2 = State#state{handler_state=HandlerState2},
+ Req3 = cowboy_req:set_resp_header(
+ <<"location">>, ResURL, Req2),
+ respond(Req3, State2, 303)
end.
%% Whether we created a new resource, either through PUT or POST.
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index 7bb5e74..b5075c0 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -63,8 +63,8 @@
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
upgrade(Req, Env, Handler, HandlerOpts) ->
- {_, ListenerPid} = lists:keyfind(listener, 1, Env),
- ranch_listener:remove_connection(ListenerPid),
+ {_, Ref} = lists:keyfind(listener, 1, Env),
+ ranch:remove_connection(Ref),
[Socket, Transport] = cowboy_req:get([socket, transport], Req),
State = #state{env=Env, socket=Socket, transport=Transport,
handler=Handler, handler_opts=HandlerOpts},
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl
index 911efb8..e33e19a 100644
--- a/test/http_SUITE.erl
+++ b/test/http_SUITE.erl
@@ -54,7 +54,6 @@
-export([pipeline/1]).
-export([pipeline_long_polling/1]).
-export([rest_bad_accept/1]).
--export([rest_created_path/1]).
-export([rest_expires/1]).
-export([rest_keepalive/1]).
-export([rest_keepalive_post/1]).
@@ -124,7 +123,6 @@ groups() ->
pipeline,
pipeline_long_polling,
rest_bad_accept,
- rest_created_path,
rest_expires,
rest_keepalive,
rest_keepalive_post,
@@ -364,7 +362,6 @@ init_dispatch(Config) ->
{"/missing_put_callbacks", rest_missing_callbacks, []},
{"/nodelete", rest_nodelete_resource, []},
{"/patch", rest_patch_resource, []},
- {"/created_path", rest_created_path_resource, []},
{"/resetags", rest_resource_etags, []},
{"/rest_expires", rest_expires, []},
{"/loop_timeout", http_handler_loop_timeout, []},
@@ -882,18 +879,6 @@ rest_bad_accept(Config) ->
Client),
{ok, 400, _, _} = cowboy_client:response(Client2).
-rest_created_path(Config) ->
- Headers = [{<<"content-type">>, <<"text/plain">>}],
- Body = <<"Whatever">>,
- Client = ?config(client, Config),
- URL = build_url("/created_path", Config),
- {ok, Client2} = cowboy_client:request(<<"POST">>, URL, Headers,
- Body, Client),
- {ok, 303, ResHeaders, _} = cowboy_client:response(Client2),
- {<<"location">>, _Location} =
- lists:keyfind(<<"location">>, 1, ResHeaders),
- ok.
-
rest_expires(Config) ->
Client = ?config(client, Config),
{ok, Client2} = cowboy_client:request(<<"GET">>,
diff --git a/test/rest_created_path_resource.erl b/test/rest_created_path_resource.erl
deleted file mode 100644
index 5ad8cfc..0000000
--- a/test/rest_created_path_resource.erl
+++ /dev/null
@@ -1,35 +0,0 @@
--module(rest_created_path_resource).
--export([init/3]).
--export([allowed_methods/2]).
--export([content_types_provided/2]).
--export([get_text_plain/2]).
--export([post_is_create/2]).
--export([content_types_accepted/2]).
--export([post_text_plain/2]).
--export([created_path/2]).
-
-init(_Transport, _Req, _Opts) ->
- {upgrade, protocol, cowboy_rest}.
-
-allowed_methods(Req, State) ->
-{[<<"HEAD">>, <<"GET">>, <<"POST">>], Req, State}.
-
-content_types_provided(Req, State) ->
- {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}.
-
-get_text_plain(Req, State) ->
- {<<"This is REST!">>, Req, State}.
-
-post_is_create(Req, State) ->
- {true, Req, State}.
-
-content_types_accepted(Req, State) ->
- {[{{<<"text">>, <<"plain">>, []}, post_text_plain}], Req, State}.
-
-post_text_plain(Req, State) ->
- {true, Req, State}.
-
-created_path(Req, State) ->
- {<<"/created">>, Req, State}.
-
-
diff --git a/test/rest_forbidden_resource.erl b/test/rest_forbidden_resource.erl
index 63aac7e..287ff62 100644
--- a/test/rest_forbidden_resource.erl
+++ b/test/rest_forbidden_resource.erl
@@ -1,7 +1,7 @@
-module(rest_forbidden_resource).
-export([init/3, rest_init/2, allowed_methods/2, forbidden/2,
content_types_provided/2, content_types_accepted/2,
- post_is_create/2, create_path/2, to_text/2, from_text/2]).
+ to_text/2, from_text/2]).
init(_Transport, _Req, _Opts) ->
{upgrade, protocol, cowboy_rest}.
@@ -23,18 +23,9 @@ content_types_provided(Req, State) ->
content_types_accepted(Req, State) ->
{[{{<<"text">>, <<"plain">>, []}, from_text}], Req, State}.
-post_is_create(Req, State) ->
- {true, Req, State}.
-
-create_path(Req, State) ->
- {Path, Req2} = cowboy_req:path(Req),
- {Path, Req2, State}.
-
to_text(Req, State) ->
{<<"This is REST!">>, Req, State}.
from_text(Req, State) ->
- {true, Req, State}.
-
-
-
+ {Path, Req2} = cowboy_req:path(Req),
+ {Path, Req2, State}.