From 0dc063ab7d94edb37c61f821b5d8e4c2da7f8ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Tue, 30 Sep 2014 20:12:13 +0300 Subject: Improve handler interface and documentation This change simplifies a little more the sub protocols mechanism. Aliases have been removed. The renaming of loop handlers as long polling handlers has been reverted. Plain HTTP handlers now simply do their work in the init/2 callback. There is no specific code for them. Loop handlers now follow the same return value as Websocket, they use ok to continue and shutdown to stop. Terminate reasons for all handler types have been documented. The terminate callback is now appropriately called in all cases (or should be). Behaviors for all handler types have been moved in the module that implement them. This means that cowboy_handler replaces the cowboy_http_handler behavior, and similarly cowboy_loop replaces cowboy_loop_handler, cowboy_websocket replaces cowboy_websocket_handler. Finally cowboy_rest now has the start of a behavior in it and will have the full list of optional callbacks defined once Erlang 18.0 gets released. The guide has been reorganized and should be easier to follow. --- doc/src/guide/erlang_beginners.ezdoc | 5 - doc/src/guide/getting_started.ezdoc | 6 +- doc/src/guide/handlers.ezdoc | 99 ++++++++++ doc/src/guide/http_handlers.ezdoc | 132 ------------- doc/src/guide/http_req_life.ezdoc | 147 --------------- doc/src/guide/index.ezdoc | 37 ++-- doc/src/guide/introduction.ezdoc | 11 +- doc/src/guide/loop_handlers.ezdoc | 28 +-- doc/src/guide/multipart.ezdoc | 164 +++++++++++++++++ doc/src/guide/multipart_intro.ezdoc | 50 ----- doc/src/guide/multipart_req.ezdoc | 115 ------------ doc/src/guide/overview.ezdoc | 147 +++++++++++++++ doc/src/guide/rest_handlers.ezdoc | 10 +- doc/src/guide/static_files.ezdoc | 168 +++++++++++++++++ doc/src/guide/static_handlers.ezdoc | 167 ----------------- doc/src/guide/sub_protocols.ezdoc | 64 +++++++ doc/src/guide/upgrade_protocol.ezdoc | 61 ------ doc/src/guide/ws_handlers.ezdoc | 6 +- doc/src/guide/ws_protocol.ezdoc | 2 +- doc/src/manual/cowboy_handler.ezdoc | 88 ++++++++- doc/src/manual/cowboy_http_handler.ezdoc | 57 ------ doc/src/manual/cowboy_loop.ezdoc | 100 ++++++++++ doc/src/manual/cowboy_loop_handler.ezdoc | 91 --------- doc/src/manual/cowboy_rest.ezdoc | 56 ++---- doc/src/manual/cowboy_websocket.ezdoc | 133 ++++++++++++- doc/src/manual/cowboy_websocket_handler.ezdoc | 133 ------------- doc/src/manual/index.ezdoc | 4 +- .../chunked_hello_world/src/toppage_handler.erl | 6 +- examples/compress_response/src/toppage_handler.erl | 6 +- examples/cookie/src/toppage_handler.erl | 6 +- examples/echo_get/src/toppage_handler.erl | 6 +- examples/echo_post/src/toppage_handler.erl | 6 +- examples/eventsource/src/eventsource_handler.erl | 4 +- examples/hello_world/src/toppage_handler.erl | 6 +- examples/rest_basic_auth/src/toppage_handler.erl | 2 +- examples/rest_hello_world/src/toppage_handler.erl | 2 +- examples/rest_pastebin/src/toppage_handler.erl | 2 +- .../rest_stream_response/src/toppage_handler.erl | 2 +- examples/ssl_hello_world/src/toppage_handler.erl | 6 +- examples/upload/src/upload_handler.erl | 6 +- examples/web_server/src/directory_handler.erl | 2 +- examples/websocket/src/ws_handler.erl | 2 +- src/cowboy_handler.erl | 62 +++---- src/cowboy_http_handler.erl | 36 ---- src/cowboy_long_polling.erl | 191 ------------------- src/cowboy_loop.erl | 205 +++++++++++++++++++++ src/cowboy_loop_handler.erl | 39 ---- src/cowboy_rest.erl | 16 +- src/cowboy_static.erl | 6 +- src/cowboy_websocket.erl | 58 ++++-- src/cowboy_websocket_handler.erl | 51 ----- test/handlers/long_polling_h.erl | 8 +- test/handlers/loop_handler_body_h.erl | 6 +- test/handlers/loop_handler_timeout_h.erl | 8 +- test/http_SUITE.erl | 33 +--- test/http_SUITE_data/http_body_qs.erl | 6 +- test/http_SUITE_data/http_chunked.erl | 6 +- test/http_SUITE_data/http_echo_body.erl | 6 +- test/http_SUITE_data/http_errors.erl | 19 -- test/http_SUITE_data/http_handler.erl | 10 +- test/http_SUITE_data/http_init_shutdown.erl | 10 - test/http_SUITE_data/http_loop_stream_recv.erl | 6 +- test/http_SUITE_data/http_multipart.erl | 6 +- test/http_SUITE_data/http_multipart_stream.erl | 6 +- test/http_SUITE_data/http_req_attr.erl | 6 +- test/http_SUITE_data/http_set_resp.erl | 14 +- test/http_SUITE_data/http_stream_body.erl | 10 +- test/http_SUITE_data/http_streamed.erl | 6 +- test/http_SUITE_data/rest_empty_resource.erl | 2 +- test/http_SUITE_data/rest_expires.erl | 2 +- test/http_SUITE_data/rest_expires_binary.erl | 2 +- test/http_SUITE_data/rest_forbidden_resource.erl | 2 +- test/http_SUITE_data/rest_missing_callbacks.erl | 2 +- test/http_SUITE_data/rest_nodelete_resource.erl | 2 +- test/http_SUITE_data/rest_param_all.erl | 2 +- test/http_SUITE_data/rest_patch_resource.erl | 2 +- .../http_SUITE_data/rest_post_charset_resource.erl | 2 +- test/http_SUITE_data/rest_postonly_resource.erl | 2 +- test/http_SUITE_data/rest_resource_etags.erl | 2 +- test/http_SUITE_data/rest_simple_resource.erl | 2 +- test/ws_SUITE_data/ws_echo.erl | 2 +- test/ws_SUITE_data/ws_echo_timer.erl | 2 +- test/ws_SUITE_data/ws_init_shutdown.erl | 4 +- test/ws_SUITE_data/ws_send_many.erl | 2 +- test/ws_SUITE_data/ws_timeout_cancel.erl | 2 +- test/ws_SUITE_data/ws_timeout_hibernate.erl | 2 +- 86 files changed, 1377 insertions(+), 1636 deletions(-) create mode 100644 doc/src/guide/handlers.ezdoc delete mode 100644 doc/src/guide/http_handlers.ezdoc delete mode 100644 doc/src/guide/http_req_life.ezdoc create mode 100644 doc/src/guide/multipart.ezdoc delete mode 100644 doc/src/guide/multipart_intro.ezdoc delete mode 100644 doc/src/guide/multipart_req.ezdoc create mode 100644 doc/src/guide/overview.ezdoc create mode 100644 doc/src/guide/static_files.ezdoc delete mode 100644 doc/src/guide/static_handlers.ezdoc create mode 100644 doc/src/guide/sub_protocols.ezdoc delete mode 100644 doc/src/guide/upgrade_protocol.ezdoc delete mode 100644 doc/src/manual/cowboy_http_handler.ezdoc create mode 100644 doc/src/manual/cowboy_loop.ezdoc delete mode 100644 doc/src/manual/cowboy_loop_handler.ezdoc delete mode 100644 doc/src/manual/cowboy_websocket_handler.ezdoc delete mode 100644 src/cowboy_http_handler.erl delete mode 100644 src/cowboy_long_polling.erl create mode 100644 src/cowboy_loop.erl delete mode 100644 src/cowboy_loop_handler.erl delete mode 100644 src/cowboy_websocket_handler.erl delete mode 100644 test/http_SUITE_data/http_init_shutdown.erl diff --git a/doc/src/guide/erlang_beginners.ezdoc b/doc/src/guide/erlang_beginners.ezdoc index f62543f..74d3470 100644 --- a/doc/src/guide/erlang_beginners.ezdoc +++ b/doc/src/guide/erlang_beginners.ezdoc @@ -33,8 +33,3 @@ Instead of going into every single details of the language, Joe focuses on the central concepts behind Erlang, and shows you how they can be used to write a variety of different applications. - -At the time of writing, the 2nd edition of the book is in beta, -and includes a few details about upcoming Erlang features that -cannot be used today. Choose the edition you want, then get -reading! diff --git a/doc/src/guide/getting_started.ezdoc b/doc/src/guide/getting_started.ezdoc index 34f02dc..a959b45 100644 --- a/doc/src/guide/getting_started.ezdoc +++ b/doc/src/guide/getting_started.ezdoc @@ -150,15 +150,15 @@ $ make new t=cowboy_http n=hello_handler ``` You can then open the `src/hello_handler.erl` file and modify -the `handle/2` function like this to send a reply. +the `init/2` function like this to send a reply. ``` erlang -handle(Req, State=#state{}) -> +init(Req, Opts) -> Req2 = cowboy_req:reply(200, [{<<"content-type">>, <<"text/plain">>}], <<"Hello Erlang!">>, Req), - {ok, Req2, State}. + {ok, Req2, Opts}. ``` What the above code does is send a `200 OK` reply, with the diff --git a/doc/src/guide/handlers.ezdoc b/doc/src/guide/handlers.ezdoc new file mode 100644 index 0000000..c0fb97e --- /dev/null +++ b/doc/src/guide/handlers.ezdoc @@ -0,0 +1,99 @@ +::: Handlers + +Handlers are Erlang modules that handle HTTP requests. + +:: Plain HTTP handlers + +The most basic handler in Cowboy implements the mandatory +`init/2` callback, manipulates the request, optionally +sends a response and then returns. + +This callback receives the ^"Req object^req and the options +defined during the ^"router configuration^routing^. + +A handler that does nothing would look like this: + +``` erlang +init(Req, Opts) -> + {ok, Req, Opts}. +``` + +Despite sending no reply, a `204 No Content` reply will be +sent to the client, as Cowboy makes sure that a reply is +sent for every request. + +We need to use the Req object for sending a reply. + +``` erlang +init(Req, Opts) -> + Req2 = cowboy_req:reply(200, [ + {<<"content-type">>, <<"text/plain">>} + ], <<"Hello World!">>, Req), + {ok, Req2, Opts}. +``` + +As you can see we return a 3-tuple. `ok` means that the +handler ran successfully. The Req object is returned as +it may have been modified as is the case here: replying +returns a modified Req object that you need to return +back to Cowboy for proper operations. + +The last value of the tuple is a state that will be used +in every subsequent callbacks to this handler. Plain HTTP +handlers only have one additional callback, the optional +`terminate/3`. + +:: Other handlers + +The `init/2` callback can also be used to inform Cowboy +that this is a different kind of handler and that Cowboy +should switch to it. To do this you simply need to return +the module name of the handler type you want to switch to. + +Cowboy comes with three handler types you can switch to: +^"cowboy_rest^rest_handlers^, ^"cowboy_websocket^ws_handlers^ +and ^"cowboy_loop^loop_handlers^. In addition to those you +can define your own handler types. + +Switching is simple. Instead of returning `ok`, you simply +return the name of the handler type you want to use. The +following snippet switches to a Websocket handler: + +``` erlang +init(Req, Opts) -> + {cowboy_websocket, Req, Opts}. +``` + +You can also switch to your own custom handler type: + +``` erlang +init(Req, Opts) -> + {my_handler_type, Req, Opts}. +``` + +How to implement a custom handler type is described in the +^"Sub protocols^sub_protocols chapter. + +:: Cleaning up + +All handlers coming with Cowboy allow the use of the optional +`terminate/3` callback. + +``` erlang +terminate(_Reason, Req, State) -> + ok. +``` + +This callback is strictly reserved for any required cleanup. +You cannot send a response from this function. There is no +other return value. + +If you used the process dictionary, timers, monitors or may +be receiving messages, then you can use this function to clean +them up, as Cowboy might reuse the process for the next +keep-alive request. + +Note that while this function may be called in a Websocket +handler, it is generally not useful to do any clean up as +the process terminates immediately after calling this callback +when using Websocket. diff --git a/doc/src/guide/http_handlers.ezdoc b/doc/src/guide/http_handlers.ezdoc deleted file mode 100644 index d846ffe..0000000 --- a/doc/src/guide/http_handlers.ezdoc +++ /dev/null @@ -1,132 +0,0 @@ -::: Handling plain HTTP requests - -The simplest way to handle a request is by writing a -plain HTTP handler. It is modeled after Erlang/OTP's -gen_server behaviour, although simplified, as Cowboy -will simply call the three callbacks sequentially. - -:: Initialization - -The first callback, `init/2`, is common to all handlers, -as it is used to identify the type of handler. Plain -HTTP handlers just return `http`. - -``` erlang -init(Req, Opts) -> - {http, Req, Opts}. -``` - -This function receives the options associated with -this route that you configured previously. - -You do not need to validate the options unless they -are user configured. If they are, and there's a -configuration error, you may choose to crash. For -example, this will crash if the required `lang` -option is not found. - -``` erlang -init(Req, Opts) -> - {_, Lang} = lists:keyfind(lang, 1, Opts), - {http, Req, Lang}. -``` - -If your users are unlikely to figure out the issue -without explanations, then you should send a more -meaningful error back to the user. Since we already -replied to the user, there's no need for us to -continue with the handler code, so we use the -`shutdown` return value to stop early. - -``` erlang -init(Req, Opts) -> - case lists:keyfind(lang, 1, Opts) of - false -> - Req2 = cowboy_req:reply(500, [ - {<<"content-type">>, <<"text/plain">>} - ], "Missing option 'lang'.", Req), - {shutdown, Req2, undefined}; - {_, Lang} -> - {http, Req, Lang} - end. -``` - -Once the options have been validated, we can use them -safely. So we need to pass them onward to the rest of -the handler. That's what the third element of the return -tuple, the state, is for. - -You may use a state record for this. The record will make -your handler code clearer and will allow Dialyzer to better -type check your code. - -``` erlang --record(state, { - lang :: en | fr - %% More fields here. -}). - -init(Req, Opts) -> - {_, Lang} = lists:keyfind(lang, 1, Opts), - {http, Req, #state{lang=Lang}}. -``` - -You may also use a map. A map is interesting in that you -do not need to define it beforehand, but is a little less -efficient and not as well supported by Dialyzer. - -``` erlang -init(Req, Opts) -> - {_, Lang} = lists:keyfind(lang, 1, Opts), - {http, Req, #{lang => Lang}. -``` - -:: Handling the request - -The second callback, `handle/2`, is specific to plain HTTP -handlers. It's where you handle the request. - -A handle function that does nothing would look like this: - -``` erlang -handle(Req, State) -> - {ok, Req, State}. -``` - -There's no other return value. To obtain information about -the request, or send a response, you would use the Req object -here. The Req object is documented in its own chapter. - -The following handle function will send a fairly original response. - -``` erlang -handle(Req, State) -> - Req2 = cowboy_req:reply(200, [ - {<<"content-type">>, <<"text/plain">>} - ], <<"Hello World!">>, Req), - {ok, Req2, State}. -``` - -:: Cleaning up - -The third and last callback, `terminate/3`, is optional. - -``` erlang -terminate(_Reason, Req, State) -> - ok. -``` - -This callback is strictly reserved for any required cleanup. -You cannot send a response from this function. There is no -other return value. - -If you used the process dictionary, timers, monitors or may -be receiving messages, then you can use this function to clean -them up, as Cowboy might reuse the process for the next -keep-alive request. - -The chances of any of this happening in your handler are pretty -thin however. The use of the process dictionary is discouraged -in Erlang code in general. And if you need to use timers, monitors -or to receive messages, you are better off with a loop handler, -a different kind of handler meant specifically for this use. diff --git a/doc/src/guide/http_req_life.ezdoc b/doc/src/guide/http_req_life.ezdoc deleted file mode 100644 index ffe5dfa..0000000 --- a/doc/src/guide/http_req_life.ezdoc +++ /dev/null @@ -1,147 +0,0 @@ -::: The life of a request - -This chapter explains the different steps a request -goes through until a response is sent, along with -details of the Cowboy implementation. - -:: Request/response - -As you already know, HTTP clients connect to the server and -send a request for a resource; the server then sends a -response containing the resource if it could obtain it. - -Before the server can send the resource, however, it -needs to perform many different operations to read the -request, find the resource, prepare the response being -sent and often other related operations the user can -add like writing logs. - -Requests take the following route in Cowboy: - -^"HTTP request/response flowchart^!http_req_resp.png - -This shows the default middlewares, but they may be -configured differently in your setup. The dark green -indicates the points where you can hook your own code, -the light green is the Cowboy code that you can of -course configure as needed. - -The `acceptor` is the part of the server that accepts -the connection and create an Erlang process to handle -it. The `parser` then starts reading from the socket -and handling requests as they come until the socket -is closed. - -A response may be sent at many different points in the -life of the request. If Cowboy can't parse the request, -it gives up with an error response. If the router can't -find the resource, it sends a not found error. Your -own code can of course send a response at any time. - -When a response is sent, you can optionally modify it -or act upon it by enabling the `onresponse` hook. By -default the response is sent directly to the client. - -:: And then? - -Behavior depends on what protocol is in use. - -HTTP/1.0 can only process one request per connection, -so Cowboy will close the connection immediately after -it sends the response. - -HTTP/1.1 allows the client to request that the server -keeps the connection alive. This mechanism is described -in the next section. - -SPDY is designed to allow sending multiple requests -asynchronously on the same connection. Details on what -this means for your application is described in this -chapter. - -:: Keep-alive (HTTP/1.1) - -With HTTP/1.1, the connection may be left open for -subsequent requests to come. This mechanism is called -`keep-alive`. - -When the client sends a request to the server, it includes -a header indicating whether it would like to leave the -socket open. The server may or may not accept, indicating -its choice by sending the same header in the response. - -Cowboy will include this header automatically in all -responses to HTTP/1.1 requests. You can however force -the closing of the socket if you want. When Cowboy sees -you want to send a `connection: close` header, it will -not override it and will close the connection as soon -as the reply is sent. - -This snippet will force Cowboy to close the connection. - -``` erlang -Req2 = cowboy_req:reply(200, [ - {<<"connection">>, <<"close">>}, -], <<"Closing the socket in 3.. 2.. 1..">>, Req). -``` - -Cowboy will only accept a certain number of new requests -on the same connection. By default it will run up to 100 -requests. This number can be changed by setting the -`max_keepalive` configuration value when starting an -HTTP listener. - -``` erlang -cowboy:start_http(my_http_listener, 100, [{port, 8080}], [ - {env, [{dispatch, Dispatch}]}, - {max_keepalive, 5} -]). -``` - -Cowboy implements the keep-alive mechanism by reusing -the same process for all requests. This allows Cowboy -to save memory. This works well because most code will -not have any side effect impacting subsequent requests. -But it also means you need to clean up if you do have -code with side effects. The `terminate/3` function can -be used for this purpose. - -:: Pipelining (HTTP/1.1) - -While HTTP is designed as a sequential protocol, with -the client sending a request and then waiting for the -response from the server, nothing prevents the client -from sending more requests to the server without waiting -for the response, due to how sockets work. The server -still handles the requests sequentially and sends the -responses in the same order. - -This mechanism is called pipelining. It allows reducing -latency when a client needs to request many resources -at the same time. This is used by browsers when requesting -static files for example. - -This is handled automatically by the server. - -:: Asynchronous requests (SPDY) - -In SPDY, the client can send a request at any time. -And the server can send a response at any time too. - -This means for example that the client does not need -to wait for a request to be fully sent to send another, -it is possible to interleave a request with the request -body of another request. The same is true with responses. -Responses may also be sent in a different order. - -Because requests and responses are fully asynchronous, -Cowboy creates a new process for each request, and these -processes are managed by another process that handles the -connection itself. - -SPDY servers may also decide to send resources to the -client before the client requests them. This is especially -useful for sending static files associated with the HTML -page requested, as this reduces the latency of the overall -response. Cowboy does not support this particular mechanism -at this point, however. diff --git a/doc/src/guide/index.ezdoc b/doc/src/guide/index.ezdoc index 300cea8..dbe75ff 100644 --- a/doc/src/guide/index.ezdoc +++ b/doc/src/guide/index.ezdoc @@ -1,35 +1,34 @@ ::: Cowboy User Guide The Cowboy User Guide explores the modern Web and how to make -best use of Cowboy for writing powerful web applications. +best use of Cowboy for writing powerful Web applications. -:: Introducing Cowboy +:: Rationale -* ^"Introduction^introduction * ^"The modern Web^modern_web * ^"Erlang and the Web^erlang_web -* ^"Erlang for beginners^erlang_beginners + +:: Introduction + +* ^"Introduction^introduction * ^"Getting started^getting_started +* ^"Request overview^overview +* ^"Erlang for beginners^erlang_beginners -:: HTTP +:: Configuration -* ^"The life of a request^http_req_life * ^"Routing^routing * ^"Constraints^constraints -* ^"Handling plain HTTP requests^http_handlers +* ^"Static files^static_files + +:: Request and response + +* ^"Handlers^handlers * ^"The Req object^req * ^"Reading the request body^req_body * ^"Sending a response^resp * ^"Using cookies^cookies - -:: Multipart - -* ^"Introduction to multipart^multipart_intro -* ^"Multipart requests^multipart_req - -:: Static files - -* ^"Static handler^static_handlers +* ^"Multipart^multipart :: REST @@ -43,14 +42,14 @@ best use of Cowboy for writing powerful web applications. * ^"The Websocket protocol^ws_protocol * ^"Handling Websocket connections^ws_handlers -:: Server push +:: Push technology * ^"Loop handlers^loop_handlers -:: Pluggable interface +:: Extensions * ^"Middlewares^middlewares -* ^"Protocol upgrades^upgrade_protocol +* ^"Sub protocols^sub_protocols * ^"Hooks^hooks :: Internals diff --git a/doc/src/guide/introduction.ezdoc b/doc/src/guide/introduction.ezdoc index 18345df..e1d2e60 100644 --- a/doc/src/guide/introduction.ezdoc +++ b/doc/src/guide/introduction.ezdoc @@ -16,9 +16,7 @@ features both a Function Reference and a User Guide. :: Prerequisites -No Erlang knowledge is required for reading this guide. The reader will -be introduced to Erlang concepts and redirected to reference material -whenever necessary. +Beginner Erlang knowledge is recommended for reading this guide. Knowledge of the HTTP protocol is recommended but not required, as it will be detailed throughout the guide. @@ -32,12 +30,15 @@ guarantee that the experience will be safe and smooth. You are advised to perform the necessary testing and security audits prior to deploying on other platforms. -Cowboy is developed for Erlang/OTP R16B01, R16B02, R16B03-1, 17.0 and -17.1.2. +Cowboy is developed for Erlang/OTP 17.0, 17.1.2 and 17.3. By the time +this branch gets released the target version will probably be 18.0 and +above. Cowboy may be compiled on other Erlang versions with small source code modifications but there is no guarantee that it will work as expected. +Cowboy uses the maps data type which was introduced in Erlang 17.0. + :: Versioning Cowboy uses ^"Semantic Versioning 2.0.0^http://semver.org/^. diff --git a/doc/src/guide/loop_handlers.ezdoc b/doc/src/guide/loop_handlers.ezdoc index 445854c..8da2805 100644 --- a/doc/src/guide/loop_handlers.ezdoc +++ b/doc/src/guide/loop_handlers.ezdoc @@ -8,7 +8,7 @@ a response. Loop handlers are used for requests where a response might not be immediately available, but where you would like to keep the connection open for a while in case the response arrives. The -most known example of such practice is known as long-polling. +most known example of such practice is known as long polling. Loop handlers can also be used for requests where a response is partially available and you need to stream the response body @@ -21,12 +21,12 @@ and allow using built-in features like hibernation and timeouts. Loop handlers essentially wait for one or more Erlang messages and feed these messages to the `info/3` callback. It also features -the `init/3` and `terminate/3` callbacks which work the same as +the `init/2` and `terminate/3` callbacks which work the same as for plain HTTP handlers. :: Initialization -The `init/3` function must return a `loop` tuple to enable +The `init/2` function must return a `cowboy_loop` tuple to enable loop handler behavior. This tuple may optionally contain a timeout value and/or the atom `hibernate` to make the process enter hibernation until a message is received. @@ -35,7 +35,7 @@ This snippet enables the loop handler. ``` erlang init(Req, Opts) -> - {long_polling, Req, Opts}. + {cowboy_loop, Req, Opts}. ``` However it is largely recommended that you set a timeout @@ -44,7 +44,7 @@ also makes the process hibernate. ``` erlang init(Req, Opts) -> - {long_polling, Req, Opts, 30000, hibernate}. + {cowboy_loop, Req, Opts, 30000, hibernate}. ``` :: Receive loop @@ -61,9 +61,9 @@ message otherwise. ``` erlang info({reply, Body}, Req, State) -> Req2 = cowboy_req:reply(200, [], Body, Req), - {ok, Req2, State}; + {shutdown, Req2, State}; info(_Msg, Req, State) -> - {loop, Req, State, hibernate}. + {ok, Req, State, hibernate}. ``` Do note that the `reply` tuple here may be any message @@ -76,17 +76,17 @@ return a tuple indicating if more messages are to be expected. The callback may also choose to do nothing at all and just skip the message received. -If a reply is sent, then the `ok` tuple should be returned. +If a reply is sent, then the `shutdown` tuple should be returned. This will instruct Cowboy to end the request. -Otherwise a `loop` tuple should be returned. +Otherwise an `ok` tuple should be returned. :: Streaming loop Another common case well suited for loop handlers is streaming data received in the form of Erlang messages. This can be done by initiating a chunked reply in the -`init/3` callback and then using `cowboy_req:chunk/2` +`init/2` callback and then using `cowboy_req:chunk/2` every time a message is received. The following snippet does exactly that. As you can see @@ -96,15 +96,15 @@ and the loop is stopped by sending an `eof` message. ``` erlang init(Req, Opts) -> Req2 = cowboy_req:chunked_reply(200, [], Req), - {long_polling, Req2, Opts}. + {cowboy_loop, Req2, Opts}. info(eof, Req, State) -> - {ok, Req, State}; + {shutdown, Req, State}; info({chunk, Chunk}, Req, State) -> cowboy_req:chunk(Chunk, Req), - {loop, Req, State}; + {ok, Req, State}; info(_Msg, Req, State) -> - {loop, Req, State}. + {ok, Req, State}. ``` :: Cleaning up diff --git a/doc/src/guide/multipart.ezdoc b/doc/src/guide/multipart.ezdoc new file mode 100644 index 0000000..d0b2e40 --- /dev/null +++ b/doc/src/guide/multipart.ezdoc @@ -0,0 +1,164 @@ +::: Multipart requests + +Multipart originates from MIME, an Internet standard that +extends the format of emails. Multipart messages are a +container for parts of any content-type. + +For example, a multipart message may have a part +containing text and a second part containing an +image. This is what allows you to attach files +to emails. + +In the context of HTTP, multipart is most often used +with the `multipart/form-data` content-type. This is +the content-type you have to use when you want browsers +to be allowed to upload files through HTML forms. + +Multipart is of course not required for uploading +files, it is only required when you want to do so +through HTML forms. + +You can read and parse multipart messages using the +Req object directly. + +Cowboy defines two functions that allows you to get +information about each part and read their contents. + +:: Structure + +A multipart message is a list of parts. Parts may +contain either a multipart message or a non-multipart +content-type. This allows parts to be arranged in a +tree structure, although this is a rare case as far +as the Web is concerned. + +:: Form-data + +In the normal case, when a form is submitted, the +browser will use the `application/x-www-form-urlencoded` +content-type. This type is just a list of keys and +values and is therefore not fit for uploading files. + +That's where the `multipart/form-data` content-type +comes in. When the form is configured to use this +content-type, the browser will use one part of the +message for each form field. This means that a file +input field will be sent in its own part, but the +same applies to all other kinds of fields. + +A form with a text input, a file input and a select +choice box will result in a multipart message with +three parts, one for each field. + +The browser does its best to determine the content-type +of the files it sends this way, but you should not +rely on it for determining the contents of the file. +Proper investigation of the contents is recommended. + +:: Checking the content-type + +While there is a variety of multipart messages, the +most common on the Web is `multipart/form-data`. It's +the type of message being sent when an HTML form +allows uploading files. + +You can quickly figure out if a multipart message +has been sent by parsing the `content-type` header. + +``` erlang +{<<"multipart">>, <<"form-data">>, _} + = cowboy_req:parse_header(<<"content-type">>, Req). +``` + +:: Reading a multipart message + +To read a message you have to iterate over all its +parts. Then, for each part, you can inspect its headers +and read its body. + +``` erlang +multipart(Req) -> + case cowboy_req:part(Req) of + {ok, _Headers, Req2} -> + {ok, _Body, Req3} = cowboy_req:part_body(Req2), + multipart(Req3); + {done, Req2} -> + Req2 + end. +``` + +Parts do not have a size limit. When a part body is +too big, Cowboy will return what it read so far and +allow you to continue if you wish to do so. + +The function `cow_multipart:form_data/1` can be used +to quickly obtain information about a part from a +`multipart/form-data` message. This function will +tell you if the part is for a normal field or if it +is a file being uploaded. + +This can be used for example to allow large part bodies +for files but crash when a normal field is too large. + +``` erlang +multipart(Req) -> + case cowboy_req:part(Req) of + {ok, Headers, Req2} -> + Req4 = case cow_multipart:form_data(Headers) of + {data, _FieldName} -> + {ok, _Body, Req3} = cowboy_req:part_body(Req2), + Req3; + {file, _FieldName, _Filename, _CType, _CTransferEncoding} -> + stream_file(Req2) + end, + multipart(Req4); + {done, Req2} -> + Req2 + end. + +stream_file(Req) -> + case cowboy_req:part_body(Req) of + {ok, _Body, Req2} -> + Req2; + {more, _Body, Req2} -> + stream_file(Req2) + end. +``` + +By default the body chunk Cowboy will return is limited +to 8MB. This can of course be overriden. Both functions +can take a second argument, the same list of options that +will be passed to `cowboy_req:body/2` function. + +:: Skipping unwanted parts + +If you do not want to read a part's body, you can skip it. +Skipping is easy. If you do not call the function to read +the part's body, Cowboy will automatically skip it when +you request the next part. + +The following snippet reads all part headers and skips +all bodies: + +``` erlang +multipart(Req) -> + case cowboy_req:part(Req) of + {ok, _Headers, Req2} -> + multipart(Req2); + {done, Req2} -> + Req2 + end. +``` + +Similarly, if you start reading the body and it ends up +being too big, you can simply continue with the next part, +Cowboy will automatically skip what remains. + +Note that the skipping rate may not be adequate for your +application. If you observe poor performance when skipping, +you might want to consider manually skipping by calling +the `cowboy_req:part_body/1` function directly. + +And if you started reading the message but decide that you +do not need the remaining parts, you can simply stop reading +entirely and Cowboy will automatically figure out what to do. diff --git a/doc/src/guide/multipart_intro.ezdoc b/doc/src/guide/multipart_intro.ezdoc deleted file mode 100644 index b9a7fa9..0000000 --- a/doc/src/guide/multipart_intro.ezdoc +++ /dev/null @@ -1,50 +0,0 @@ -::: Introduction to multipart - -Multipart originates from MIME, an Internet standard that -extends the format of emails. Multipart messages are a -container for parts of any content-type. - -For example, a multipart message may have a part -containing text and a second part containing an -image. This is what allows you to attach files -to emails. - -In the context of HTTP, multipart is most often used -with the `multipart/form-data` content-type. This is -the content-type you have to use when you want browsers -to be allowed to upload files through HTML forms. - -Multipart is of course not required for uploading -files, it is only required when you want to do so -through HTML forms. - -:: Structure - -A multipart message is a list of parts. Parts may -contain either a multipart message or a non-multipart -content-type. This allows parts to be arranged in a -tree structure, although this is a rare case as far -as the Web is concerned. - -:: Form-data - -In the normal case, when a form is submitted, the -browser will use the `application/x-www-form-urlencoded` -content-type. This type is just a list of keys and -values and is therefore not fit for uploading files. - -That's where the `multipart/form-data` content-type -comes in. When the form is configured to use this -content-type, the browser will use one part of the -message for each form field. This means that a file -input field will be sent in its own part, but the -same applies to all other kinds of fields. - -A form with a text input, a file input and a select -choice box will result in a multipart message with -three parts, one for each field. - -The browser does its best to determine the content-type -of the files it sends this way, but you should not -rely on it for determining the contents of the file. -Proper investigation of the contents is recommended. diff --git a/doc/src/guide/multipart_req.ezdoc b/doc/src/guide/multipart_req.ezdoc deleted file mode 100644 index 21762f6..0000000 --- a/doc/src/guide/multipart_req.ezdoc +++ /dev/null @@ -1,115 +0,0 @@ -::: Multipart requests - -You can read and parse multipart messages using the -Req object directly. - -Cowboy defines two functions that allows you to get -information about each part and read their contents. - -:: Checking the content-type - -While there is a variety of multipart messages, the -most common on the Web is `multipart/form-data`. It's -the type of message being sent when an HTML form -allows uploading files. - -You can quickly figure out if a multipart message -has been sent by parsing the `content-type` header. - -``` erlang -{<<"multipart">>, <<"form-data">>, _} - = cowboy_req:parse_header(<<"content-type">>, Req). -``` - -:: Reading a multipart message - -To read a message you have to iterate over all its -parts. Then, for each part, you can inspect its headers -and read its body. - -``` erlang -multipart(Req) -> - case cowboy_req:part(Req) of - {ok, _Headers, Req2} -> - {ok, _Body, Req3} = cowboy_req:part_body(Req2), - multipart(Req3); - {done, Req2} -> - Req2 - end. -``` - -Parts do not have a size limit. When a part body is -too big, Cowboy will return what it read so far and -allow you to continue if you wish to do so. - -The function `cow_multipart:form_data/1` can be used -to quickly obtain information about a part from a -`multipart/form-data` message. This function will -tell you if the part is for a normal field or if it -is a file being uploaded. - -This can be used for example to allow large part bodies -for files but crash when a normal field is too large. - -``` erlang -multipart(Req) -> - case cowboy_req:part(Req) of - {ok, Headers, Req2} -> - Req4 = case cow_multipart:form_data(Headers) of - {data, _FieldName} -> - {ok, _Body, Req3} = cowboy_req:part_body(Req2), - Req3; - {file, _FieldName, _Filename, _CType, _CTransferEncoding} -> - stream_file(Req2) - end, - multipart(Req4); - {done, Req2} -> - Req2 - end. - -stream_file(Req) -> - case cowboy_req:part_body(Req) of - {ok, _Body, Req2} -> - Req2; - {more, _Body, Req2} -> - stream_file(Req2) - end. -``` - -By default the body chunk Cowboy will return is limited -to 8MB. This can of course be overriden. Both functions -can take a second argument, the same list of options that -will be passed to `cowboy_req:body/2` function. - -:: Skipping unwanted parts - -If you do not want to read a part's body, you can skip it. -Skipping is easy. If you do not call the function to read -the part's body, Cowboy will automatically skip it when -you request the next part. - -The following snippet reads all part headers and skips -all bodies: - -``` erlang -multipart(Req) -> - case cowboy_req:part(Req) of - {ok, _Headers, Req2} -> - multipart(Req2); - {done, Req2} -> - Req2 - end. -``` - -Similarly, if you start reading the body and it ends up -being too big, you can simply continue with the next part, -Cowboy will automatically skip what remains. - -Note that the skipping rate may not be adequate for your -application. If you observe poor performance when skipping, -you might want to consider manually skipping by calling -the `cowboy_req:part_body/1` function directly. - -And if you started reading the message but decide that you -do not need the remaining parts, you can simply stop reading -entirely and Cowboy will automatically figure out what to do. diff --git a/doc/src/guide/overview.ezdoc b/doc/src/guide/overview.ezdoc new file mode 100644 index 0000000..725ae4e --- /dev/null +++ b/doc/src/guide/overview.ezdoc @@ -0,0 +1,147 @@ +::: Request overview + +This chapter explains the different steps a request +goes through until a response is sent, along with +details of the Cowboy implementation. + +:: Request/response + +As you already know, HTTP clients connect to the server and +send a request for a resource; the server then sends a +response containing the resource if it could obtain it. + +Before the server can send the resource, however, it +needs to perform many different operations to read the +request, find the resource, prepare the response being +sent and often other related operations the user can +add like writing logs. + +Requests take the following route in Cowboy: + +^"HTTP request/response flowchart^!http_req_resp.png + +This shows the default middlewares, but they may be +configured differently in your setup. The dark green +indicates the points where you can hook your own code, +the light green is the Cowboy code that you can of +course configure as needed. + +The `acceptor` is the part of the server that accepts +the connection and create an Erlang process to handle +it. The `parser` then starts reading from the socket +and handling requests as they come until the socket +is closed. + +A response may be sent at many different points in the +life of the request. If Cowboy can't parse the request, +it gives up with an error response. If the router can't +find the resource, it sends a not found error. Your +own code can of course send a response at any time. + +When a response is sent, you can optionally modify it +or act upon it by enabling the `onresponse` hook. By +default the response is sent directly to the client. + +:: And then? + +Behavior depends on what protocol is in use. + +HTTP/1.0 can only process one request per connection, +so Cowboy will close the connection immediately after +it sends the response. + +HTTP/1.1 allows the client to request that the server +keeps the connection alive. This mechanism is described +in the next section. + +SPDY is designed to allow sending multiple requests +asynchronously on the same connection. Details on what +this means for your application is described in this +chapter. + +:: Keep-alive (HTTP/1.1) + +With HTTP/1.1, the connection may be left open for +subsequent requests to come. This mechanism is called +`keep-alive`. + +When the client sends a request to the server, it includes +a header indicating whether it would like to leave the +socket open. The server may or may not accept, indicating +its choice by sending the same header in the response. + +Cowboy will include this header automatically in all +responses to HTTP/1.1 requests. You can however force +the closing of the socket if you want. When Cowboy sees +you want to send a `connection: close` header, it will +not override it and will close the connection as soon +as the reply is sent. + +This snippet will force Cowboy to close the connection. + +``` erlang +Req2 = cowboy_req:reply(200, [ + {<<"connection">>, <<"close">>}, +], <<"Closing the socket in 3.. 2.. 1..">>, Req). +``` + +Cowboy will only accept a certain number of new requests +on the same connection. By default it will run up to 100 +requests. This number can be changed by setting the +`max_keepalive` configuration value when starting an +HTTP listener. + +``` erlang +cowboy:start_http(my_http_listener, 100, [{port, 8080}], [ + {env, [{dispatch, Dispatch}]}, + {max_keepalive, 5} +]). +``` + +Cowboy implements the keep-alive mechanism by reusing +the same process for all requests. This allows Cowboy +to save memory. This works well because most code will +not have any side effect impacting subsequent requests. +But it also means you need to clean up if you do have +code with side effects. The `terminate/3` function can +be used for this purpose. + +:: Pipelining (HTTP/1.1) + +While HTTP is designed as a sequential protocol, with +the client sending a request and then waiting for the +response from the server, nothing prevents the client +from sending more requests to the server without waiting +for the response, due to how sockets work. The server +still handles the requests sequentially and sends the +responses in the same order. + +This mechanism is called pipelining. It allows reducing +latency when a client needs to request many resources +at the same time. This is used by browsers when requesting +static files for example. + +This is handled automatically by the server. + +:: Asynchronous requests (SPDY) + +In SPDY, the client can send a request at any time. +And the server can send a response at any time too. + +This means for example that the client does not need +to wait for a request to be fully sent to send another, +it is possible to interleave a request with the request +body of another request. The same is true with responses. +Responses may also be sent in a different order. + +Because requests and responses are fully asynchronous, +Cowboy creates a new process for each request, and these +processes are managed by another process that handles the +connection itself. + +SPDY servers may also decide to send resources to the +client before the client requests them. This is especially +useful for sending static files associated with the HTML +page requested, as this reduces the latency of the overall +response. Cowboy does not support this particular mechanism +at this point, however. diff --git a/doc/src/guide/rest_handlers.ezdoc b/doc/src/guide/rest_handlers.ezdoc index 294392a..8cdd12e 100644 --- a/doc/src/guide/rest_handlers.ezdoc +++ b/doc/src/guide/rest_handlers.ezdoc @@ -1,20 +1,20 @@ ::: REST handlers -REST is implemented in Cowboy as a protocol upgrade. Once upgraded, -the request is handled as a state machine with many optional callbacks +REST is implemented in Cowboy as a sub protocol. The request +is handled as a state machine with many optional callbacks describing the resource and modifying the machine's behavior. -The REST handler is the recommended way to handle requests. +The REST handler is the recommended way to handle HTTP requests. :: Initialization First, the `init/2` callback is called. This callback is common to all handlers. To use REST for the current request, this function -must return a `rest` tuple. +must return a `cowboy_rest` tuple. ``` erlang init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. ``` Cowboy will then switch to the REST protocol and start executing diff --git a/doc/src/guide/static_files.ezdoc b/doc/src/guide/static_files.ezdoc new file mode 100644 index 0000000..5a289d0 --- /dev/null +++ b/doc/src/guide/static_files.ezdoc @@ -0,0 +1,168 @@ +::: Static files + +Cowboy comes with a special handler built as a REST handler +and designed specifically for serving static files. It is +provided as a convenience and provides a quick solution for +serving files during development. + +For systems in production, consider using one of the many +Content Distribution Network (CDN) available on the market, +as they are the best solution for serving files. They are +covered in the next chapter. If you decide against using a +CDN solution, then please look at the chapter after that, +as it explains how to efficiently serve static files on +your own. + +The static handler can serve either one file or all files +from a given directory. It can also send etag headers for +client-side caching. + +To use the static file handler, simply add routes for it +with the appropriate options. + +:: Serve one file + +You can use the static handler to serve one specific file +from an application's private directory. This is particularly +useful to serve an `index.html` file when the client requests +the `/` path, for example. The path configured is relative +to the given application's private directory. + +The following rule will serve the file `static/index.html` +from the application `my_app`'s priv directory whenever the +path `/` is accessed. + +``` erlang +{"/", cowboy_static, {priv_file, my_app, "static/index.html"}} +``` + +You can also specify the absolute path to a file, or the +path to the file relative to the current directory. + +``` erlang +{"/", cowboy_static, {file, "/var/www/index.html"}} +``` + +:: Serve all files from a directory + +You can also use the static handler to serve all files that +can be found in the configured directory. The handler will +use the `path_info` information to resolve the file location, +which means that your route must end with a `[...]` pattern +for it to work. All files are served, including the ones that +may be found in subfolders. + +You can specify the directory relative to an application's +private directory. + +The following rule will serve any file found in the application +`my_app`'s priv directory inside the `static/assets` folder +whenever the requested path begins with `/assets/`. + +``` erlang +{"/assets/[...]", cowboy_static, {priv_dir, my_app, "static/assets"}} +``` + +You can also specify the absolute path to the directory or +set it relative to the current directory. + +``` erlang +{"/assets/[...]", cowboy_static, {dir, "/var/www/assets"}} +``` + +:: Customize the mimetype detection + +By default, Cowboy will attempt to recognize the mimetype +of your static files by looking at the extension. + +You can override the function that figures out the mimetype +of the static files. It can be useful when Cowboy is missing +a mimetype you need to handle, or when you want to reduce +the list to make lookups faster. You can also give a +hard-coded mimetype that will be used unconditionally. + +Cowboy comes with two functions built-in. The default +function only handles common file types used when building +Web applications. The other function is an extensive list +of hundreds of mimetypes that should cover almost any need +you may have. You can of course create your own function. + +To use the default function, you should not have to configure +anything, as it is the default. If you insist, though, the +following will do the job. + +``` erlang +{"/assets/[...]", cowboy_static, {priv_dir, my_app, "static/assets", + [{mimetypes, cow_mimetypes, web}]}} +``` + +As you can see, there is an optional field that may contain +a list of less used options, like mimetypes or etag. All option +types have this optional field. + +To use the function that will detect almost any mimetype, +the following configuration will do. + +``` erlang +{"/assets/[...]", cowboy_static, {priv_dir, my_app, "static/assets", + [{mimetypes, cow_mimetypes, all}]}} +``` + +You probably noticed the pattern by now. The configuration +expects a module and a function name, so you can use any +of your own functions instead. + +``` erlang +{"/assets/[...]", cowboy_static, {priv_dir, my_app, "static/assets", + [{mimetypes, Module, Function}]}} +``` + +The function that performs the mimetype detection receives +a single argument that is the path to the file on disk. It +is recommended to return the mimetype in tuple form, although +a binary string is also allowed (but will require extra +processing). If the function can't figure out the mimetype, +then it should return `{<<"application">>, <<"octet-stream">>, []}`. + +When the static handler fails to find the extension in the +list, it will send the file as `application/octet-stream`. +A browser receiving such file will attempt to download it +directly to disk. + +Finally, the mimetype can be hard-coded for all files. +This is especially useful in combination with the `file` +and `priv_file` options as it avoids needless computation. + +``` erlang +{"/", cowboy_static, {priv_file, my_app, "static/index.html", + [{mimetypes, {<<"text">>, <<"html">>, []}}]}} +``` + +:: Generate an etag + +By default, the static handler will generate an etag header +value based on the size and modified time. This solution +can not be applied to all systems though. It would perform +rather poorly over a cluster of nodes, for example, as the +file metadata will vary from server to server, giving a +different etag on each server. + +You can however change the way the etag is calculated. + +``` erlang +{"/assets/[...]", cowboy_static, {priv_dir, my_app, "static/assets", + [{etag, Module, Function}]}} +``` + +This function will receive three arguments: the path to the +file on disk, the size of the file and the last modification +time. In a distributed setup, you would typically use the +file path to retrieve an etag value that is identical across +all your servers. + +You can also completely disable etag handling. + +``` erlang +{"/assets/[...]", cowboy_static, {priv_dir, my_app, "static/assets", + [{etag, false}]}} +``` diff --git a/doc/src/guide/static_handlers.ezdoc b/doc/src/guide/static_handlers.ezdoc deleted file mode 100644 index f5eaac3..0000000 --- a/doc/src/guide/static_handlers.ezdoc +++ /dev/null @@ -1,167 +0,0 @@ -::: Static handler - -The static handler is a built-in REST handler for serving files. -It is available as a convenience and provides a quick solution -for serving files during development. - -For systems in production, consider using one of the many -Content Distribution Network (CDN) available on the market, -as they are the best solution for serving files. They are -covered in the next chapter. If you decide against using a -CDN solution, then please look at the chapter after that, -as it explains how to efficiently serve static files on -your own. - -The static handler can serve either one file or all files -from a given directory. It can also send etag headers for -client-side caching. - -To use the static file handler, simply add routes for it -with the appropriate options. - -:: Serve one file - -You can use the static handler to serve one specific file -from an application's private directory. This is particularly -useful to serve an `index.html` file when the client requests -the `/` path, for example. The path configured is relative -to the given application's private directory. - -The following rule will serve the file `static/index.html` -from the application `my_app`'s priv directory whenever the -path `/` is accessed. - -``` erlang -{"/", cowboy_static, {priv_file, my_app, "static/index.html"}} -``` - -You can also specify the absolute path to a file, or the -path to the file relative to the current directory. - -``` erlang -{"/", cowboy_static, {file, "/var/www/index.html"}} -``` - -:: Serve all files from a directory - -You can also use the static handler to serve all files that -can be found in the configured directory. The handler will -use the `path_info` information to resolve the file location, -which means that your route must end with a `[...]` pattern -for it to work. All files are served, including the ones that -may be found in subfolders. - -You can specify the directory relative to an application's -private directory. - -The following rule will serve any file found in the application -`my_app`'s priv directory inside the `static/assets` folder -whenever the requested path begins with `/assets/`. - -``` erlang -{"/assets/[...]", cowboy_static, {priv_dir, my_app, "static/assets"}} -``` - -You can also specify the absolute path to the directory or -set it relative to the current directory. - -``` erlang -{"/assets/[...]", cowboy_static, {dir, "/var/www/assets"}} -``` - -:: Customize the mimetype detection - -By default, Cowboy will attempt to recognize the mimetype -of your static files by looking at the extension. - -You can override the function that figures out the mimetype -of the static files. It can be useful when Cowboy is missing -a mimetype you need to handle, or when you want to reduce -the list to make lookups faster. You can also give a -hard-coded mimetype that will be used unconditionally. - -Cowboy comes with two functions built-in. The default -function only handles common file types used when building -Web applications. The other function is an extensive list -of hundreds of mimetypes that should cover almost any need -you may have. You can of course create your own function. - -To use the default function, you should not have to configure -anything, as it is the default. If you insist, though, the -following will do the job. - -``` erlang -{"/assets/[...]", cowboy_static, {priv_dir, my_app, "static/assets", - [{mimetypes, cow_mimetypes, web}]}} -``` - -As you can see, there is an optional field that may contain -a list of less used options, like mimetypes or etag. All option -types have this optional field. - -To use the function that will detect almost any mimetype, -the following configuration will do. - -``` erlang -{"/assets/[...]", cowboy_static, {priv_dir, my_app, "static/assets", - [{mimetypes, cow_mimetypes, all}]}} -``` - -You probably noticed the pattern by now. The configuration -expects a module and a function name, so you can use any -of your own functions instead. - -``` erlang -{"/assets/[...]", cowboy_static, {priv_dir, my_app, "static/assets", - [{mimetypes, Module, Function}]}} -``` - -The function that performs the mimetype detection receives -a single argument that is the path to the file on disk. It -is recommended to return the mimetype in tuple form, although -a binary string is also allowed (but will require extra -processing). If the function can't figure out the mimetype, -then it should return `{<<"application">>, <<"octet-stream">>, []}`. - -When the static handler fails to find the extension in the -list, it will send the file as `application/octet-stream`. -A browser receiving such file will attempt to download it -directly to disk. - -Finally, the mimetype can be hard-coded for all files. -This is especially useful in combination with the `file` -and `priv_file` options as it avoids needless computation. - -``` erlang -{"/", cowboy_static, {priv_file, my_app, "static/index.html", - [{mimetypes, {<<"text">>, <<"html">>, []}}]}} -``` - -:: Generate an etag - -By default, the static handler will generate an etag header -value based on the size and modified time. This solution -can not be applied to all systems though. It would perform -rather poorly over a cluster of nodes, for example, as the -file metadata will vary from server to server, giving a -different etag on each server. - -You can however change the way the etag is calculated. - -``` erlang -{"/assets/[...]", cowboy_static, {priv_dir, my_app, "static/assets", - [{etag, Module, Function}]}} -``` - -This function will receive three arguments: the path to the -file on disk, the size of the file and the last modification -time. In a distributed setup, you would typically use the -file path to retrieve an etag value that is identical across -all your servers. - -You can also completely disable etag handling. - -``` erlang -{"/assets/[...]", cowboy_static, {priv_dir, my_app, "static/assets", - [{etag, false}]}} -``` diff --git a/doc/src/guide/sub_protocols.ezdoc b/doc/src/guide/sub_protocols.ezdoc new file mode 100644 index 0000000..d34f21e --- /dev/null +++ b/doc/src/guide/sub_protocols.ezdoc @@ -0,0 +1,64 @@ +::: Sub protocols + +Sub protocols are used for creating new types of handlers that +provide extra functionality in a reusable way. Cowboy uses this +mechanism to provide its loop, REST and Websocket handlers. + +This chapter will explain how to create your own sub protocols +and handler types. + +:: Usage + +To switch to a sub protocol, the `init/2` callback must return +the name of the sub protocol module. Everything past this point +is handled by the sub protocol. + +``` erlang +init(Req, Opts) -> + {cowboy_websocket, Req, Opts}. +``` + +The return value may also have a `Timeout` value and/or the +atom `hibernate`. These options are useful for long living +connections. When they are not provided, the timeout value +defaults to `infinity` and the hibernate value to `run`. + +The following snippet switches to the `my_protocol` sub +protocol, sets the timeout value to 5 seconds and enables +hibernation: + +``` erlang +init(Req, Opts) -> + {my_protocol, Req, Opts, 5000, hibernate}. +``` + +If a sub protocol does not make use of these options, it should +crash if it receives anything other than the default values. + +:: Upgrade + +After the `init/2` function returns, Cowboy will then call the +`upgrade/6` function. This is the only callback defined by the +`cowboy_sub_protocol` behavior. + +The function is named `upgrade` because it mimics the mechanism +of HTTP protocol upgrades. For some sub protocols, like Websocket, +an actual upgrade is performed. For others, like REST, this is +only an upgrade at Cowboy's level and the client has nothing to +do about it. + +The upgrade callback receives the Req object, the middleware +environment, the handler and its options, and the aforementioned +timeout and hibernate values. + +``` erlang +upgrade(Req, Env, Handler, HandlerOpts, Timeout, Hibernate) -> + %% Sub protocol code here. +``` + +This callback is expected to behave like a middleware and to +return an updated environment and Req object. + +Sub protocols are expected to call the `cowboy_handler:terminate/4` +function when they terminate. This function will make sure that +the optional `terminate/3` callback is called, if present. diff --git a/doc/src/guide/upgrade_protocol.ezdoc b/doc/src/guide/upgrade_protocol.ezdoc deleted file mode 100644 index ad2d25a..0000000 --- a/doc/src/guide/upgrade_protocol.ezdoc +++ /dev/null @@ -1,61 +0,0 @@ -::: Protocol upgrades - -Cowboy features many different handlers, each for different purposes. -All handlers have a common entry point: the `init/2` function. -This function returns the name of the protocol that will be -used for processing the request, along with various options. - -Cowboy defines four built-in handler types. Three of them are -implemented as sub protocols. More can be implemented by -writing a custom sub protocol. - -The following table lists the built-in handler types. - -|| Alias Module Description -| -| http - Plain HTTP handler -| long_polling cowboy_long_polling Long-polling handler -| rest cowboy_rest REST handler -| ws cowboy_websocket Websocket handler - -Both the alias or the module name can be used to specify the -kind of handler. In addition, a user-defined module name can -be used. - -``` erlang -init(Req, Opts) -> - {my_protocol, Req, Opts}. -``` - -The `init/2` function can also return some extra options for -handlers that are meant to be long running, for example the -`long_polling` and `ws` handler types. These options can also -be passed on to custom sub protocols. For example the following -`init/2` function defines both a timeout value and enables -process hibernation: - -``` erlang -init(Req, Opts) -> - {my_protocol, Req, Opts, 5000, hibernate}. -``` - -It is up to the sub protocol to implement these (or reject -them if they are not supported). - -The `cowboy_sub_protocol` behavior only requires one callback, -`upgrade/6`. It receives the Req object, the middleware environment, -the handler and options for this request, and the timeout and -hibernate values. The default timeout value is `infinity` and -the default hibernate value is `run`. - -``` erlang -upgrade(Req, Env, Handler, HandlerOpts, Timeout, Hibernate) -> - %% ... -``` - -This callback is expected to behave like a middleware. Please -see the corresponding chapter for more information. - -Sub protocols are expected to call the `cowboy_handler:terminate/5` -function when they terminate. This function will make sure that -the optional `terminate/3` callback will be called, if present. diff --git a/doc/src/guide/ws_handlers.ezdoc b/doc/src/guide/ws_handlers.ezdoc index 893624b..cb30511 100644 --- a/doc/src/guide/ws_handlers.ezdoc +++ b/doc/src/guide/ws_handlers.ezdoc @@ -17,7 +17,7 @@ must return a `ws` tuple. ``` erlang init(Req, Opts) -> - {ws, Req, Opts}. + {cowboy_websocket, Req, Opts}. ``` Upon receiving this tuple, Cowboy will switch to the code @@ -61,7 +61,7 @@ or enabling timers. init(Req, _Opts) -> self() ! post_init, %% Register process here... - {ws, Req, #state{}}. + {cowboy_websocket, Req, #state{}}. websocket_info(post_init, Req, State) -> %% Perform post_init initialization here... @@ -161,7 +161,7 @@ A good timeout value is 60 seconds. ``` erlang init(Req, _Opts) -> - {ws, Req, #state{}, 60000}. + {cowboy_websocket, Req, #state{}, 60000}. ``` This value cannot be changed once it is set. It defaults to diff --git a/doc/src/guide/ws_protocol.ezdoc b/doc/src/guide/ws_protocol.ezdoc index 15aea2c..d283ae3 100644 --- a/doc/src/guide/ws_protocol.ezdoc +++ b/doc/src/guide/ws_protocol.ezdoc @@ -25,7 +25,7 @@ excluding the initial flawed draft sometimes known as :: Implementation Cowboy implements Websocket as a protocol upgrade. Once the -upgrade is performed from the `init/3` callback, Cowboy +upgrade is performed from the `init/2` callback, Cowboy switches to Websocket. Please consult the next chapter for more information on initiating and handling Websocket connections. diff --git a/doc/src/manual/cowboy_handler.ezdoc b/doc/src/manual/cowboy_handler.ezdoc index 0495f28..b440b60 100644 --- a/doc/src/manual/cowboy_handler.ezdoc +++ b/doc/src/manual/cowboy_handler.ezdoc @@ -15,10 +15,96 @@ Environment output: * result = ok +This module also defines the `cowboy_handler` behaviour that +defines the basic interface for handlers. All Cowboy handlers +implement at least the `init/2` callback, and may implement +the `terminate/3` callback optionally. + :: Types None. +:: Terminate reasons + +The following values may be received as the terminate reason +in the optional `terminate/3` callback. Different handler types +may define additional terminate reasons. + +: normal + +The connection was closed normally. + +: {crash, Class, Reason} + +A crash occurred in the handler. `Class` and `Reason` can be +used to obtain more information about the crash. The function +`erlang:get_stacktrace/0` can also be called to obtain the +stacktrace of the process when the crash occurred. + +:: Callbacks + +: init(Req, Opts) + -> {ok, Req, State} + | {Module, Req, State} + | {Module, Req, State, hibernate} + | {Module, Req, State, Timeout} + | {Module, Req, State, Timeout, hibernate} + +Types: + +* Req = cowboy_req:req() +* Opts = any() +* State = any() +* Module = module() +* Timeout = timeout() + +Process the request. + +This function can be used to switch to an alternate handler +type by returning the name of the module to be used, along +with a few options. + +For basic handlers this is the function where the response +should be sent. If no response is sent, Cowboy will ensure +that a `204 No Content` response is sent. + +A crash in this callback will result in `terminate/3` being +called if it is defined, with the `State` argument set to +the value of `Opts` originally given to the `init/2` callback. + +: terminate(Reason, Req, State) -> ok + +Types: + +* Reason = any() +* Req = cowboy_req:req() +* State = any() + +Perform any necessary cleanup of the state. + +This callback should release any resource currently in use, +clear any active timer and reset the process to its original +state, as it might be reused for future requests sent on the +same connection. Typical plain HTTP handlers rarely need to +use it. + +A crash in this callback or an invalid return value will +result in the closing of the connection and the termination +of the process. + :: Exports -None. +: terminate(Reason, Req, State, Handler) -> ok + +Types: + +* Reason = any() +* Req = cowboy_req:req() +* State = any() +* Handler = module() + +Call the optional `terminate/3` callback if it exists. + +This function should always be called at the end of the execution +of a handler, to give it a chance to clean up or perform +miscellaneous operations. diff --git a/doc/src/manual/cowboy_http_handler.ezdoc b/doc/src/manual/cowboy_http_handler.ezdoc deleted file mode 100644 index 6776598..0000000 --- a/doc/src/manual/cowboy_http_handler.ezdoc +++ /dev/null @@ -1,57 +0,0 @@ -::: cowboy_http_handler - -The `cowboy_http_handler` behaviour defines the interface used -by plain HTTP handlers. - -Unless noted otherwise, the callbacks will be executed sequentially. - -:: Types - -None. - -:: Callbacks - -: init({TransportName, ProtocolName}, Req, Opts) - -> {ok, Req, State} | {shutdown, Req, State} - -Types: - -* TransportName = tcp | ssl | atom() -* ProtocolName = http | atom() -* Req = cowboy_req:req() -* Opts = any() -* State = any() - -Initialize the state for this request. - -The `shutdown` return value can be used to skip the `handle/2` -call entirely. - -: handle(Req, State) -> {ok, Req, State} - -Types: - -* Req = cowboy_req:req() -* State = any() - -Handle the request. - -This callback is where the request is handled and a response -should be sent. If a response is not sent, Cowboy will send -a `204 No Content` response automatically. - -: terminate(Reason, Req, State) -> ok - -Types: - -* Reason = {normal, shutdown} | {error, atom()} -* Req = cowboy_req:req() -* State = any() - -Perform any necessary cleanup of the state. - -This callback should release any resource currently in use, -clear any active timer and reset the process to its original -state, as it might be reused for future requests sent on the -same connection. Typical plain HTTP handlers rarely need to -use it. diff --git a/doc/src/manual/cowboy_loop.ezdoc b/doc/src/manual/cowboy_loop.ezdoc new file mode 100644 index 0000000..1f3ab9e --- /dev/null +++ b/doc/src/manual/cowboy_loop.ezdoc @@ -0,0 +1,100 @@ +::: cowboy_loop + +The `cowboy_loop` module implements a handler interface for +long running HTTP connections. It is the recommended interface +for long polling and server-sent events, amongst others. + +This module is a sub protocol that defines three callbacks to +be implemented by handlers. The `init/2` and `terminate/3` +callbacks are common to all handler types and are documented +in the manual for the ^cowboy_handler module. + +The `info/3` callback is specific to loop handlers and will be +called as many times as necessary until a reply is sent. + +It is highly recommended to return a timeout value from the +`init/2` callback to ensure that the process is terminated +when no data has been received during that timespan. The +default timeout is `infinity`, which should only be used if +you have alternate means of ending inactive connections. + +:: Types + +None. + +:: Terminate reasons + +The following values may be received as the terminate reason +in the optional `terminate/3` callback. + +: normal + +The connection was closed normally before switching to the +loop sub protocol. This typically happens if an `ok` tuple is +returned from the `init/2` callback. + +: shutdown + +The handler requested to close the connection by returning +a `shutdown` tuple. + +: timeout + +The connection has been closed due to inactivity. The timeout +value can be configured from `init/2`. The response sent when +this happens is a `204 No Content`. + +: {crash, Class, Reason} + +A crash occurred in the handler. `Class` and `Reason` can be +used to obtain more information about the crash. The function +`erlang:get_stacktrace/0` can also be called to obtain the +stacktrace of the process when the crash occurred. + +: {error, overflow} + +The connection is being closed and the process terminated +because the buffer Cowboy uses to keep data sent by the +client has reached its maximum. The buffer size can be +configured through the environment value `loop_max_buffer` +and defaults to 5000 bytes. + +If the long running request comes with a body it is recommended +to process this body before switching to the loop sub protocol. + +: {error, closed} + +The socket has been closed brutally without a close frame being +received first. + +: {error, Reason} + +A socket error ocurred. + +:: Callbacks + +: info(Info, Req, State) + -> {ok, Req, State} + | {ok, Req, State, hibernate} + | {shutdown, Req, State} + +Types: + +* Info = any() +* Req = cowboy_req:req() +* State = any() + +Handle the Erlang message received. + +This function will be called every time an Erlang message +has been received. The message can be any Erlang term. + +The `shutdown` return value can be used to stop the receive loop, +typically because a response has been sent. + +The `hibernate` option will hibernate the process until +it receives another message. + +:: Exports + +None. diff --git a/doc/src/manual/cowboy_loop_handler.ezdoc b/doc/src/manual/cowboy_loop_handler.ezdoc deleted file mode 100644 index 0811a9a..0000000 --- a/doc/src/manual/cowboy_loop_handler.ezdoc +++ /dev/null @@ -1,91 +0,0 @@ -::: cowboy_loop_handler - -The `cowboy_loop_handler` behaviour defines the interface used -by HTTP handlers that do not send a response directly, instead -requiring a receive loop to process Erlang messages. - -This interface is best fit for long-polling types of requests. - -The `init/3` callback will always be called, followed by zero -or more calls to `info/3`. The `terminate/3` callback will -always be called last. - -:: Types - -None. - -:: Callbacks - -: init({TransportName, ProtocolName}, Req, Opts) - -> {loop, Req, State} - | {loop, Req, State, hibernate} - | {loop, Req, State, Timeout} - | {loop, Req, State, Timeout, hibernate} - | {shutdown, Req, State} - -Types: - -* TransportName = tcp | ssl | atom() -* ProtocolName = http | atom() -* Req = cowboy_req:req() -* Opts = any() -* State = any() -* Timeout = timeout() - -Initialize the state for this request. - -This callback will typically be used to register this process -to an event manager or a message queue in order to receive -the messages the handler wants to process. - -The receive loop will run for a duration of up to `Timeout` -milliseconds after it last received data from the socket, -at which point it will stop and send a `204 No Content` reply. -By default this value is set to `infinity`. It is recommended -to either set this value or ensure by any other mechanism -that the handler will be closed after a certain period of -inactivity. - -The `hibernate` option will hibernate the process until it -starts receiving messages. - -The `shutdown` return value can be used to skip the receive -loop entirely. - -: info(Info, Req, State) -> {ok, Req, State} | {loop, Req, State} - | {loop, Req, State, hibernate} - -Types: - -* Info = any() -* Req = cowboy_req:req() -* State = any() - -Handle the Erlang message received. - -This function will be called every time an Erlang message -has been received. The message can be any Erlang term. - -The `ok` return value can be used to stop the receive loop, -typically because a response has been sent. - -The `hibernate` option will hibernate the process until -it receives another message. - -: terminate(Reason, Req, State) -> ok - -Types: - -* Reason = {normal, shutdown} | {normal, timeout} | {error, closed} | {error, overflow} | {error, atom()} -* Req = cowboy_req:req() -* State = any() - -Perform any necessary cleanup of the state. - -This callback will typically unregister from any event manager -or message queue it registered to in `init/3`. - -This callback should release any resource currently in use, -clear any active timer and reset the process to its original -state, as it might be reused for future requests sent on the -same connection. diff --git a/doc/src/manual/cowboy_rest.ezdoc b/doc/src/manual/cowboy_rest.ezdoc index 9fe5c77..f128a22 100644 --- a/doc/src/manual/cowboy_rest.ezdoc +++ b/doc/src/manual/cowboy_rest.ezdoc @@ -3,12 +3,13 @@ The `cowboy_rest` module implements REST semantics on top of the HTTP protocol. -This module cannot be described as a behaviour due to most of -the callbacks it defines being optional. It has the same -semantics as a behaviour otherwise. +This module is a sub protocol that defines many callbacks +be implemented by handlers. The `init/2` and `terminate/3` +callbacks are common to all handler types and are documented +in the manual for the ^cowboy_handler module. -The only mandatory callback is `init/3`, needed to perform -the protocol upgrade. +All other callbacks are optional, though some may become +required depending on the return value of previous callbacks. :: Types @@ -43,46 +44,23 @@ The media-type is the content-type, excluding the charset. This value is always defined after the call to `content_types_provided/2`. -:: Callbacks - -: init({TransportName, ProtocolName}, Req, Opts) - -> {upgrade, protocol, cowboy_rest} - | {upgrade, protocol, cowboy_rest, Req, Opts} - -Types: - -* TransportName = tcp | ssl | atom() -* ProtocolName = http | atom() -* Req = cowboy_req:req() -* Opts = any() - -Upgrade the protocol to `cowboy_rest`. - -This is the only mandatory callback. - -: rest_init(Req, Opts) -> {ok, Req, State} +:: Terminate reasons -Types: +The following values may be received as the terminate reason +in the optional `terminate/3` callback. -* Req = cowboy_req:req() -* Opts = any() -* State = any() +: normal -Initialize the state for this request. +The connection was closed normally. -: rest_terminate(Req, State) -> ok +: {crash, Class, Reason} -Types: +A crash occurred in the handler. `Class` and `Reason` can be +used to obtain more information about the crash. The function +`erlang:get_stacktrace/0` can also be called to obtain the +stacktrace of the process when the crash occurred. -* Req = cowboy_req:req() -* State = any() - -Perform any necessary cleanup of the state. - -This callback should release any resource currently in use, -clear any active timer and reset the process to its original -state, as it might be reused for future requests sent on the -same connection. +:: Callbacks : Callback(Req, State) -> {Value, Req, State} | {halt, Req, State} diff --git a/doc/src/manual/cowboy_websocket.ezdoc b/doc/src/manual/cowboy_websocket.ezdoc index 59a6248..889ddd7 100644 --- a/doc/src/manual/cowboy_websocket.ezdoc +++ b/doc/src/manual/cowboy_websocket.ezdoc @@ -2,8 +2,25 @@ The `cowboy_websocket` module implements the Websocket protocol. -The callbacks for websocket handlers are defined in the manual -for the `cowboy_websocket_handler` behaviour. +This module is a sub protocol that defines four callbacks to +be implemented by handlers. The `init/2` and `terminate/3` +callbacks are common to all handler types and are documented +in the manual for the ^cowboy_handler module. + +The `websocket_handle/3` and `websocket_info/3` callbacks are +specific to Websocket handlers and will be called as many times +as necessary until the Websocket connection is closed. + +The `init/2` callback can be used to negotiate Websocket protocol +extensions with the client. It is highly recommended to return a +timeout value from this callback to ensure that the process is +terminated when no data has been received during that timespan. +The default timeout is `infinity`, which should only be used if +you have alternate means of ending inactive connections. + +Cowboy will terminate the process right after closing the +Websocket connection. This means that there is no real need to +perform any cleanup in the optional `terminate/3` callback. :: Types @@ -31,6 +48,118 @@ Type: 7 | 8 | 13 The version of the Websocket protocol being used. +:: Terminate reasons + +The following values may be received as the terminate reason +in the optional `terminate/3` callback. + +: normal + +The connection was closed normally before establishing a Websocket +connection. This typically happens if an `ok` tuple is returned +from the `init/2` callback. + +: remote + +The remote endpoint closed the connection without giving any +further details. + +: {remote, Code, Payload} + +The remote endpoint closed the connection with the given +`Code` and `Payload` as the reason. + +: shutdown + +The handler requested to close the connection, either by returning +a `shutdown` tuple or by sending a `close` frame. + +: timeout + +The connection has been closed due to inactivity. The timeout +value can be configured from `init/2`. + +: {crash, Class, Reason} + +A crash occurred in the handler. `Class` and `Reason` can be +used to obtain more information about the crash. The function +`erlang:get_stacktrace/0` can also be called to obtain the +stacktrace of the process when the crash occurred. + +: {error, badencoding} + +A text frame was sent by the client with invalid encoding. All +text frames must be valid UTF-8. + +: {error, badframe} + +A protocol error has been detected. + +: {error, closed} + +The socket has been closed brutally without a close frame being +received first. + +: {error, Reason} + +A socket error ocurred. + +:: Callbacks + +: websocket_handle(InFrame, Req, State) + -> {ok, Req, State} + | {ok, Req, State, hibernate} + | {reply, OutFrame | [OutFrame], Req, State} + | {reply, OutFrame | [OutFrame], Req, State, hibernate} + | {shutdown, Req, State} + +Types: + +* InFrame = {text | binary | ping | pong, binary()} +* Req = cowboy_req:req() +* State = any() +* OutFrame = frame() + +Handle the data received from the Websocket connection. + +This function will be called every time data is received +from the Websocket connection. + +The `shutdown` return value can be used to close the +connection. A close reply will also result in the connection +being closed. + +The `hibernate` option will hibernate the process until +it receives new data from the Websocket connection or an +Erlang message. + +: websocket_info(Info, Req, State) + -> {ok, Req, State} + | {ok, Req, State, hibernate} + | {reply, OutFrame | [OutFrame], Req, State} + | {reply, OutFrame | [OutFrame], Req, State, hibernate} + | {shutdown, Req, State} + +Types: + +* Info = any() +* Req = cowboy_req:req() +* State = any() +* OutFrame = frame() + +Handle the Erlang message received. + +This function will be called every time an Erlang message +has been received. The message can be any Erlang term. + +The `shutdown` return value can be used to close the +connection. A close reply will also result in the connection +being closed. + +The `hibernate` option will hibernate the process until +it receives another message or new data from the Websocket +connection. + :: Exports None. diff --git a/doc/src/manual/cowboy_websocket_handler.ezdoc b/doc/src/manual/cowboy_websocket_handler.ezdoc deleted file mode 100644 index 0d31a54..0000000 --- a/doc/src/manual/cowboy_websocket_handler.ezdoc +++ /dev/null @@ -1,133 +0,0 @@ -::: cowboy_websocket_handler - -The `cowboy_websocket_handler` behaviour defines the interface used -by Websocket handlers. - -The `init/3` and `websocket_init/3` callbacks will always be called, -followed by zero or more calls to `websocket_handle/3` and -`websocket_info/3`. The `websocket_terminate/3` will always -be called last. - -:: Types - -None. - -:: Callbacks - -: init({TransportName, ProtocolName}, Req, Opts) - -> {upgrade, protocol, cowboy_websocket} - | {upgrade, protocol, cowboy_websocket, Req, Opts} - -Types: - -* TransportName = tcp | ssl | atom() -* ProtocolName = http | atom() -* Req = cowboy_req:req() -* Opts = any() - -Upgrade the protocol to `cowboy_websocket`. - -: websocket_init(TransportName, Req, Opts) - -> {ok, Req, State} - | {ok, Req, State, hibernate} - | {ok, Req, State, Timeout} - | {ok, Req, State, Timeout, hibernate} - | {shutdown, Req} - -Types: - -* TransportName = tcp | ssl | atom() -* Req = cowboy_req:req() -* Opts = any() -* State = any() -* Timeout = timeout() - -Initialize the state for this session. - -This function is called before the upgrade to Websocket occurs. -It can be used to negotiate Websocket protocol extensions -with the client. It will typically be used to register this process -to an event manager or a message queue in order to receive -the messages the handler wants to process. - -The connection will stay up for a duration of up to `Timeout` -milliseconds after it last received data from the socket, -at which point it will stop and close the connection. -By default this value is set to `infinity`. It is recommended -to either set this value or ensure by any other mechanism -that the handler will be closed after a certain period of -inactivity. - -The `hibernate` option will hibernate the process until it -starts receiving either data from the Websocket connection -or Erlang messages. - -The `shutdown` return value can be used to close the connection -before upgrading to Websocket. - -: websocket_handle(InFrame, Req, State) - -> {ok, Req, State} - | {ok, Req, State, hibernate} - | {reply, OutFrame | [OutFrame], Req, State} - | {reply, OutFrame | [OutFrame], Req, State, hibernate} - | {shutdown, Req, State} - -Types: - -* InFrame = {text | binary | ping | pong, binary()} -* Req = cowboy_req:req() -* State = any() -* OutFrame = cowboy_websocket:frame() - -Handle the data received from the Websocket connection. - -This function will be called every time data is received -from the Websocket connection. - -The `shutdown` return value can be used to close the -connection. A close reply will also result in the connection -being closed. - -The `hibernate` option will hibernate the process until -it receives new data from the Websocket connection or an -Erlang message. - -: websocket_info(Info, Req, State) - -> {ok, Req, State} - | {ok, Req, State, hibernate} - | {reply, OutFrame | [OutFrame], Req, State} - | {reply, OutFrame | [OutFrame], Req, State, hibernate} - | {shutdown, Req, State} - -Types: - -* Info = any() -* Req = cowboy_req:req() -* State = any() -* OutFrame = cowboy_websocket:frame() - -Handle the Erlang message received. - -This function will be called every time an Erlang message -has been received. The message can be any Erlang term. - -The `shutdown` return value can be used to close the -connection. A close reply will also result in the connection -being closed. - -The `hibernate` option will hibernate the process until -it receives another message or new data from the Websocket -connection. - -: websocket_terminate(Reason, Req, State) -> ok - -Types: - -* Reason = {normal, shutdown | timeout} | {remote, closed} | {remote, cowboy_websocket:close_code(), binary()} | {error, badencoding | badframe | closed | atom()} -* Req = cowboy_req:req() -* State = any() - -Perform any necessary cleanup of the state. - -The connection will be closed and the process stopped right -after this call. diff --git a/doc/src/manual/index.ezdoc b/doc/src/manual/index.ezdoc index e364e90..133a341 100644 --- a/doc/src/manual/index.ezdoc +++ b/doc/src/manual/index.ezdoc @@ -5,8 +5,7 @@ The function reference documents the public interface of Cowboy. * ^"The Cowboy Application^cowboy_app * ^cowboy * ^cowboy_handler -* ^cowboy_http_handler -* ^cowboy_loop_handler +* ^cowboy_loop * ^cowboy_middleware * ^cowboy_protocol * ^cowboy_req @@ -16,5 +15,4 @@ The function reference documents the public interface of Cowboy. * ^cowboy_static * ^cowboy_sub_protocol * ^cowboy_websocket -* ^cowboy_websocket_handler * ^"HTTP status codes^http_status_codes diff --git a/examples/chunked_hello_world/src/toppage_handler.erl b/examples/chunked_hello_world/src/toppage_handler.erl index 6b47156..cb1d130 100644 --- a/examples/chunked_hello_world/src/toppage_handler.erl +++ b/examples/chunked_hello_world/src/toppage_handler.erl @@ -4,16 +4,12 @@ -module(toppage_handler). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> Req2 = cowboy_req:chunked_reply(200, Req), cowboy_req:chunk("Hello\r\n", Req2), timer:sleep(1000), cowboy_req:chunk("World\r\n", Req2), timer:sleep(1000), cowboy_req:chunk("Chunked!\r\n", Req2), - {ok, Req2, State}. + {ok, Req2, Opts}. diff --git a/examples/compress_response/src/toppage_handler.erl b/examples/compress_response/src/toppage_handler.erl index 09c8689..4631f71 100644 --- a/examples/compress_response/src/toppage_handler.erl +++ b/examples/compress_response/src/toppage_handler.erl @@ -4,12 +4,8 @@ -module(toppage_handler). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> BigBody = <<"A cowboy is an animal herder who tends cattle on ranches in North America, traditionally on horseback, and often performs a multitude of other ranch- @@ -24,4 +20,4 @@ considerable respect for their achievements. There are also cattle handlers in many other parts of the world, particularly South America and Australia, who perform work similar to the cowboy in their respective nations.\n">>, Req2 = cowboy_req:reply(200, [], BigBody, Req), - {ok, Req2, State}. + {ok, Req2, Opts}. diff --git a/examples/cookie/src/toppage_handler.erl b/examples/cookie/src/toppage_handler.erl index d1a1126..52e155b 100644 --- a/examples/cookie/src/toppage_handler.erl +++ b/examples/cookie/src/toppage_handler.erl @@ -4,12 +4,8 @@ -module(toppage_handler). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> NewValue = integer_to_list(random:uniform(1000000)), Req2 = cowboy_req:set_resp_cookie( <<"server">>, NewValue, [{path, <<"/">>}], Req), @@ -22,4 +18,4 @@ handle(Req, State) -> Req3 = cowboy_req:reply(200, [{<<"content-type">>, <<"text/html">>}], Body, Req2), - {ok, Req3, State}. + {ok, Req3, Opts}. diff --git a/examples/echo_get/src/toppage_handler.erl b/examples/echo_get/src/toppage_handler.erl index be657c6..a7c8d7f 100644 --- a/examples/echo_get/src/toppage_handler.erl +++ b/examples/echo_get/src/toppage_handler.erl @@ -4,16 +4,12 @@ -module(toppage_handler). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> Method = cowboy_req:method(Req), #{echo := Echo} = cowboy_req:match_qs(Req, [echo]), Req2 = echo(Method, Echo, Req), - {ok, Req2, State}. + {ok, Req2, Opts}. echo(<<"GET">>, undefined, Req) -> cowboy_req:reply(400, [], <<"Missing echo parameter.">>, Req); diff --git a/examples/echo_post/src/toppage_handler.erl b/examples/echo_post/src/toppage_handler.erl index 2bafed0..de75e5e 100644 --- a/examples/echo_post/src/toppage_handler.erl +++ b/examples/echo_post/src/toppage_handler.erl @@ -4,16 +4,12 @@ -module(toppage_handler). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> Method = cowboy_req:method(Req), HasBody = cowboy_req:has_body(Req), Req2 = maybe_echo(Method, HasBody, Req), - {ok, Req2, State}. + {ok, Req2, Opts}. maybe_echo(<<"POST">>, true, Req) -> {ok, PostVals, Req2} = cowboy_req:body_qs(Req), diff --git a/examples/eventsource/src/eventsource_handler.erl b/examples/eventsource/src/eventsource_handler.erl index 3aa60e7..c896638 100644 --- a/examples/eventsource/src/eventsource_handler.erl +++ b/examples/eventsource/src/eventsource_handler.erl @@ -10,12 +10,12 @@ init(Req, Opts) -> Headers = [{<<"content-type">>, <<"text/event-stream">>}], Req2 = cowboy_req:chunked_reply(200, Headers, Req), erlang:send_after(1000, self(), {message, "Tick"}), - {long_polling, Req2, Opts, 5000}. + {cowboy_loop, Req2, Opts, 5000}. info({message, Msg}, Req, State) -> cowboy_req:chunk(["id: ", id(), "\ndata: ", Msg, "\n\n"], Req), erlang:send_after(1000, self(), {message, "Tick"}), - {loop, Req, State}. + {ok, Req, State}. id() -> {Mega, Sec, Micro} = erlang:now(), diff --git a/examples/hello_world/src/toppage_handler.erl b/examples/hello_world/src/toppage_handler.erl index 18a6343..e8e672e 100644 --- a/examples/hello_world/src/toppage_handler.erl +++ b/examples/hello_world/src/toppage_handler.erl @@ -4,13 +4,9 @@ -module(toppage_handler). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> Req2 = cowboy_req:reply(200, [ {<<"content-type">>, <<"text/plain">>} ], <<"Hello world!">>, Req), - {ok, Req2, State}. + {ok, Req2, Opts}. diff --git a/examples/rest_basic_auth/src/toppage_handler.erl b/examples/rest_basic_auth/src/toppage_handler.erl index 59c5888..18a8cae 100644 --- a/examples/rest_basic_auth/src/toppage_handler.erl +++ b/examples/rest_basic_auth/src/toppage_handler.erl @@ -9,7 +9,7 @@ -export([to_text/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. is_authorized(Req, State) -> case cowboy_req:parse_header(<<"authorization">>, Req) of diff --git a/examples/rest_hello_world/src/toppage_handler.erl b/examples/rest_hello_world/src/toppage_handler.erl index 5b0dfc8..7657281 100644 --- a/examples/rest_hello_world/src/toppage_handler.erl +++ b/examples/rest_hello_world/src/toppage_handler.erl @@ -10,7 +10,7 @@ -export([hello_to_text/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. content_types_provided(Req, State) -> {[ diff --git a/examples/rest_pastebin/src/toppage_handler.erl b/examples/rest_pastebin/src/toppage_handler.erl index 89fd786..80974fe 100644 --- a/examples/rest_pastebin/src/toppage_handler.erl +++ b/examples/rest_pastebin/src/toppage_handler.erl @@ -17,7 +17,7 @@ init(Req, Opts) -> random:seed(now()), - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. allowed_methods(Req, State) -> {[<<"GET">>, <<"POST">>], Req, State}. diff --git a/examples/rest_stream_response/src/toppage_handler.erl b/examples/rest_stream_response/src/toppage_handler.erl index 6c66d21..61f5ab9 100644 --- a/examples/rest_stream_response/src/toppage_handler.erl +++ b/examples/rest_stream_response/src/toppage_handler.erl @@ -8,7 +8,7 @@ -export([streaming_csv/2]). init(Req, Table) -> - {rest, Req, Table}. + {cowboy_rest, Req, Table}. content_types_provided(Req, State) -> {[ diff --git a/examples/ssl_hello_world/src/toppage_handler.erl b/examples/ssl_hello_world/src/toppage_handler.erl index 18a6343..e8e672e 100644 --- a/examples/ssl_hello_world/src/toppage_handler.erl +++ b/examples/ssl_hello_world/src/toppage_handler.erl @@ -4,13 +4,9 @@ -module(toppage_handler). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> Req2 = cowboy_req:reply(200, [ {<<"content-type">>, <<"text/plain">>} ], <<"Hello world!">>, Req), - {ok, Req2, State}. + {ok, Req2, Opts}. diff --git a/examples/upload/src/upload_handler.erl b/examples/upload/src/upload_handler.erl index 63cda96..dec634d 100644 --- a/examples/upload/src/upload_handler.erl +++ b/examples/upload/src/upload_handler.erl @@ -4,16 +4,12 @@ -module(upload_handler). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> {ok, Headers, Req2} = cowboy_req:part(Req), {ok, Data, Req3} = cowboy_req:part_body(Req2), {file, <<"inputfile">>, Filename, ContentType, _TE} = cow_multipart:form_data(Headers), io:format("Received file ~p of content-type ~p as follow:~n~p~n~n", [Filename, ContentType, Data]), - {ok, Req3, State}. + {ok, Req3, Opts}. diff --git a/examples/web_server/src/directory_handler.erl b/examples/web_server/src/directory_handler.erl index 5863bfa..7af67ba 100644 --- a/examples/web_server/src/directory_handler.erl +++ b/examples/web_server/src/directory_handler.erl @@ -14,7 +14,7 @@ -export([list_html/2]). init(Req, Paths) -> - {rest, Req, Paths}. + {cowboy_rest, Req, Paths}. allowed_methods(Req, State) -> {[<<"GET">>], Req, State}. diff --git a/examples/websocket/src/ws_handler.erl b/examples/websocket/src/ws_handler.erl index 18f9526..eaa9284 100644 --- a/examples/websocket/src/ws_handler.erl +++ b/examples/websocket/src/ws_handler.erl @@ -6,7 +6,7 @@ init(Req, Opts) -> erlang:start_timer(1000, self(), <<"Hello!">>), - {ws, Req, Opts}. + {cowboy_websocket, Req, Opts}. websocket_handle({text, Msg}, Req, State) -> {reply, {text, << "That's what she said! ", Msg/binary >>}, Req, State}; diff --git a/src/cowboy_handler.erl b/src/cowboy_handler.erl index a666714..e3faf66 100644 --- a/src/cowboy_handler.erl +++ b/src/cowboy_handler.erl @@ -21,7 +21,15 @@ -behaviour(cowboy_middleware). -export([execute/2]). --export([terminate/5]). +-export([terminate/4]). + +-callback init(Req, any()) + -> {ok | module(), Req, any()} + | {module(), Req, any(), hibernate} + | {module(), Req, any(), timeout()} + | {module(), Req, any(), timeout(), hibernate} + when Req::cowboy_req:req(). +%% @todo optional -callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. -spec execute(Req, Env) -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). @@ -29,21 +37,21 @@ execute(Req, Env) -> {_, Handler} = lists:keyfind(handler, 1, Env), {_, HandlerOpts} = lists:keyfind(handler_opts, 1, Env), try Handler:init(Req, HandlerOpts) of - {http, Req2, State} -> - handle(Req2, Env, Handler, State); - {shutdown, Req2, State} -> - terminate(Req2, Env, Handler, State, {normal, shutdown}); + {ok, Req2, State} -> + Result = terminate(normal, Req2, State, Handler), + {ok, Req2, [{result, Result}|Env]}; {Mod, Req2, State} -> - upgrade(Req2, Env, Handler, State, infinity, run, Mod); + Mod:upgrade(Req2, Env, Handler, State, infinity, run); {Mod, Req2, State, hibernate} -> - upgrade(Req2, Env, Handler, State, infinity, hibernate, Mod); + Mod:upgrade(Req2, Env, Handler, State, infinity, hibernate); {Mod, Req2, State, Timeout} -> - upgrade(Req2, Env, Handler, State, Timeout, run, Mod); + Mod:upgrade(Req2, Env, Handler, State, Timeout, run); {Mod, Req2, State, Timeout, hibernate} -> - upgrade(Req2, Env, Handler, State, Timeout, hibernate, Mod) + Mod:upgrade(Req2, Env, Handler, State, Timeout, hibernate) catch Class:Reason -> Stacktrace = erlang:get_stacktrace(), cowboy_req:maybe_reply(Stacktrace, Req), + terminate({crash, Class, Reason}, Req, HandlerOpts, Handler), erlang:Class([ {reason, Reason}, {mfa, {Handler, init, 2}}, @@ -53,36 +61,9 @@ execute(Req, Env) -> ]) end. -handle(Req, Env, Handler, State) -> - try Handler:handle(Req, State) of - {ok, Req2, State2} -> - terminate(Req2, Env, Handler, State2, {normal, shutdown}) - catch Class:Reason -> - Stacktrace = erlang:get_stacktrace(), - cowboy_req:maybe_reply(Stacktrace, Req), - _ = terminate(Req, Env, Handler, State, Reason), - erlang:Class([ - {reason, Reason}, - {mfa, {Handler, handle, 2}}, - {stacktrace, Stacktrace}, - {req, cowboy_req:to_list(Req)}, - {state, State} - ]) - end. - -upgrade(Req, Env, Handler, State, Timeout, Hibernate, Mod) -> - Mod2 = case Mod of - long_polling -> cowboy_long_polling; - rest -> cowboy_rest; - ws -> cowboy_websocket; - _ when Mod =/= http -> Mod - end, - Mod2:upgrade(Req, Env, Handler, State, Timeout, Hibernate). - --spec terminate(Req, Env, module(), any(), {normal, shutdown} | {error, atom()} | any()) - -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). -terminate(Req, Env, Handler, State, Reason) -> - Result = case erlang:function_exported(Handler, terminate, 3) of +-spec terminate(any(), Req, any(), module()) -> ok when Req::cowboy_req:req(). +terminate(Reason, Req, State, Handler) -> + case erlang:function_exported(Handler, terminate, 3) of true -> try Handler:terminate(Reason, cowboy_req:lock(Req), State) @@ -98,5 +79,4 @@ terminate(Req, Env, Handler, State, Reason) -> end; false -> ok - end, - {ok, Req, [{result, Result}|Env]}. + end. diff --git a/src/cowboy_http_handler.erl b/src/cowboy_http_handler.erl deleted file mode 100644 index 75b41d2..0000000 --- a/src/cowboy_http_handler.erl +++ /dev/null @@ -1,36 +0,0 @@ -%% Copyright (c) 2011-2014, Loïc Hoguin -%% -%% 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_http_handler). - --type opts() :: any(). --type state() :: any(). -%% @todo see terminate -%-type terminate_reason() :: {normal, shutdown} -% | {normal, timeout} %% Only occurs in loop handlers. -% | {error, closed} %% Only occurs in loop handlers. -% | {error, overflow} %% Only occurs in loop handlers. -% | {error, atom()}. - --callback init(Req, opts()) - -> {http, Req, state()} - | {long_polling | rest | ws | module(), Req, state()} - | {long_polling | rest | ws | module(), Req, state(), hibernate} - | {long_polling | rest | ws | module(), Req, state(), timeout()} - | {long_polling | rest | ws | module(), Req, state(), timeout(), hibernate} - | {shutdown, Req, state()} - when Req::cowboy_req:req(). --callback handle(Req, State) -> {ok, Req, State} - when Req::cowboy_req:req(), State::state(). -%% @todo optional -callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. diff --git a/src/cowboy_long_polling.erl b/src/cowboy_long_polling.erl deleted file mode 100644 index 6616a78..0000000 --- a/src/cowboy_long_polling.erl +++ /dev/null @@ -1,191 +0,0 @@ -%% Copyright (c) 2011-2014, Loïc Hoguin -%% -%% 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. - -%% When using loop handlers, we are receiving data from the socket because we -%% want to know when the socket gets closed. This is generally not an issue -%% because these kinds of requests are generally not pipelined, and don't have -%% a body. If they do have a body, this body is often read in the -%% init/2 callback and this is no problem. Otherwise, this data -%% accumulates in a buffer until we reach a certain threshold of 5000 bytes -%% by default. This can be configured through the loop_max_buffer -%% environment value. The request will be terminated with an -%% {error, overflow} reason if this threshold is reached. --module(cowboy_long_polling). --behaviour(cowboy_sub_protocol). - --export([upgrade/6]). --export([loop/4]). - --record(state, { - env :: cowboy_middleware:env(), - hibernate = false :: boolean(), - buffer_size = 0 :: non_neg_integer(), - max_buffer = 5000 :: non_neg_integer() | infinity, - timeout = infinity :: timeout(), - timeout_ref = undefined :: undefined | reference(), - resp_sent = false :: boolean() -}). - --spec upgrade(Req, Env, module(), any(), timeout(), run | hibernate) - -> {ok, Req, Env} | {suspend, module(), atom(), [any()]} - when Req::cowboy_req:req(), Env::cowboy_middleware:env(). -upgrade(Req, Env, Handler, HandlerState, Timeout, run) -> - State = #state{env=Env, max_buffer=get_max_buffer(Env), timeout=Timeout}, - State2 = timeout(State), - after_call(Req, State2, Handler, HandlerState); -upgrade(Req, Env, Handler, HandlerState, Timeout, hibernate) -> - State = #state{env=Env, max_buffer=get_max_buffer(Env), hibernate=true, timeout=Timeout}, - State2 = timeout(State), - after_call(Req, State2, Handler, HandlerState). - -get_max_buffer(Env) -> - case lists:keyfind(loop_max_buffer, 1, Env) of - false -> 5000; - {_, MaxBuffer} -> MaxBuffer - end. - -%% Update the state if the response was sent in the callback. -after_call(Req, State=#state{resp_sent=false}, Handler, - HandlerState) -> - receive - {cowboy_req, resp_sent} -> - before_loop(Req, State#state{resp_sent=true}, Handler, HandlerState) - after 0 -> - before_loop(Req, State, Handler, HandlerState) - end; -after_call(Req, State, Handler, HandlerState) -> - before_loop(Req, State, Handler, HandlerState). - -before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) -> - [Socket, Transport] = cowboy_req:get([socket, transport], Req), - Transport:setopts(Socket, [{active, once}]), - {suspend, ?MODULE, loop, [Req, State#state{hibernate=false}, Handler, HandlerState]}; -before_loop(Req, State, Handler, HandlerState) -> - [Socket, Transport] = cowboy_req:get([socket, transport], Req), - Transport:setopts(Socket, [{active, once}]), - loop(Req, State, Handler, HandlerState). - -%% Almost the same code can be found in cowboy_websocket. -timeout(State=#state{timeout=infinity}) -> - State#state{timeout_ref=undefined}; -timeout(State=#state{timeout=Timeout, - timeout_ref=PrevRef}) -> - _ = case PrevRef of - undefined -> ignore; - PrevRef -> erlang:cancel_timer(PrevRef) - end, - TRef = erlang:start_timer(Timeout, self(), ?MODULE), - State#state{timeout_ref=TRef}. - --spec loop(Req, #state{}, module(), any()) - -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} - when Req::cowboy_req:req(). -loop(Req, State=#state{env=Env, buffer_size=NbBytes, - max_buffer=Threshold, timeout_ref=TRef, - resp_sent=RespSent}, Handler, HandlerState) -> - [Socket, Transport] = cowboy_req:get([socket, transport], Req), - {OK, Closed, Error} = Transport:messages(), - receive - {OK, Socket, Data} -> - NbBytes2 = NbBytes + byte_size(Data), - if NbBytes2 > Threshold -> - _ = if RespSent -> ok; true -> - cowboy_req:reply(500, Req) - end, - _ = cowboy_handler:terminate(Req, Env, Handler, HandlerState, {error, overflow}), - exit(normal); - true -> - Req2 = cowboy_req:append_buffer(Data, Req), - State2 = timeout(State#state{buffer_size=NbBytes2}), - before_loop(Req2, State2, Handler, HandlerState) - end; - {Closed, Socket} -> - terminate(Req, State, Handler, HandlerState, {error, closed}); - {Error, Socket, Reason} -> - terminate(Req, State, Handler, HandlerState, {error, Reason}); - {timeout, TRef, ?MODULE} -> - after_loop(Req, State, Handler, HandlerState, {normal, timeout}); - {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) -> - loop(Req, State, Handler, HandlerState); - Message -> - %% We set the socket back to {active, false} mode in case - %% the handler is going to call recv. We also flush any - %% data received after that and put it into the buffer. - %% We do not check the size here, if data keeps coming - %% we'll error out on the next packet received. - Transport:setopts(Socket, [{active, false}]), - Req2 = receive {OK, Socket, Data} -> - cowboy_req:append_buffer(Data, Req) - after 0 -> - Req - end, - call(Req2, State, Handler, HandlerState, Message) - end. - -call(Req, State=#state{env=Env, resp_sent=RespSent}, - Handler, HandlerState, Message) -> - try Handler:info(Message, Req, HandlerState) of - {ok, Req2, HandlerState2} -> - after_loop(Req2, State, Handler, HandlerState2, {normal, shutdown}); - {loop, Req2, HandlerState2} -> - after_call(Req2, State, Handler, HandlerState2); - {loop, Req2, HandlerState2, hibernate} -> - after_call(Req2, State#state{hibernate=true}, Handler, HandlerState2) - catch Class:Reason -> - Stacktrace = erlang:get_stacktrace(), - if RespSent -> ok; true -> - cowboy_req:maybe_reply(Stacktrace, Req) - end, - _ = cowboy_handler:terminate(Req, Env, Handler, HandlerState, {error, overflow}), - erlang:Class([ - {reason, Reason}, - {mfa, {Handler, info, 3}}, - {stacktrace, Stacktrace}, - {req, cowboy_req:to_list(Req)}, - {state, HandlerState} - ]) - end. - -%% It is sometimes important to make a socket passive as it was initially -%% and as it is expected to be by cowboy_protocol, right after we're done -%% with loop handling. The browser may freely pipeline a bunch of requests -%% if previous one was, say, a JSONP long-polling request. -after_loop(Req, State, Handler, HandlerState, Reason) -> - [Socket, Transport] = cowboy_req:get([socket, transport], Req), - Transport:setopts(Socket, [{active, false}]), - {OK, _Closed, _Error} = Transport:messages(), - Req2 = receive - {OK, Socket, Data} -> - cowboy_req:append_buffer(Data, Req) - after 0 -> - Req - end, - terminate(Req2, State, Handler, HandlerState, Reason). - -terminate(Req, #state{env=Env, timeout_ref=TRef}, - Handler, HandlerState, Reason) -> - _ = case TRef of - undefined -> ignore; - TRef -> erlang:cancel_timer(TRef) - end, - flush_timeouts(), - cowboy_handler:terminate(Req, Env, Handler, HandlerState, Reason). - -flush_timeouts() -> - receive - {timeout, TRef, ?MODULE} when is_reference(TRef) -> - flush_timeouts() - after 0 -> - ok - end. diff --git a/src/cowboy_loop.erl b/src/cowboy_loop.erl new file mode 100644 index 0000000..b9eb8cd --- /dev/null +++ b/src/cowboy_loop.erl @@ -0,0 +1,205 @@ +%% Copyright (c) 2011-2014, Loïc Hoguin +%% +%% 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. + +%% When using loop handlers, we are receiving data from the socket because we +%% want to know when the socket gets closed. This is generally not an issue +%% because these kinds of requests are generally not pipelined, and don't have +%% a body. If they do have a body, this body is often read in the +%% init/2 callback and this is no problem. Otherwise, this data +%% accumulates in a buffer until we reach a certain threshold of 5000 bytes +%% by default. This can be configured through the loop_max_buffer +%% environment value. The request will be terminated with an +%% {error, overflow} reason if this threshold is reached. +-module(cowboy_loop). +-behaviour(cowboy_sub_protocol). + +-export([upgrade/6]). +-export([loop/4]). + +-callback init(Req, any()) + -> {ok | module(), Req, any()} + | {module(), Req, any(), hibernate} + | {module(), Req, any(), timeout()} + | {module(), Req, any(), timeout(), hibernate} + when Req::cowboy_req:req(). +-callback info(any(), Req, State) + -> {ok, Req, State} + | {ok, Req, State, hibernate} + | {shutdown, Req, State} + when Req::cowboy_req:req(), State::any(). +%% @todo optional -callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. + +-record(state, { + env :: cowboy_middleware:env(), + hibernate = false :: boolean(), + buffer_size = 0 :: non_neg_integer(), + max_buffer = 5000 :: non_neg_integer() | infinity, + timeout = infinity :: timeout(), + timeout_ref = undefined :: undefined | reference(), + resp_sent = false :: boolean() +}). + +-spec upgrade(Req, Env, module(), any(), timeout(), run | hibernate) + -> {ok, Req, Env} | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(), Env::cowboy_middleware:env(). +upgrade(Req, Env, Handler, HandlerState, Timeout, run) -> + State = #state{env=Env, max_buffer=get_max_buffer(Env), timeout=Timeout}, + State2 = timeout(State), + after_call(Req, State2, Handler, HandlerState); +upgrade(Req, Env, Handler, HandlerState, Timeout, hibernate) -> + State = #state{env=Env, max_buffer=get_max_buffer(Env), hibernate=true, timeout=Timeout}, + State2 = timeout(State), + after_call(Req, State2, Handler, HandlerState). + +get_max_buffer(Env) -> + case lists:keyfind(loop_max_buffer, 1, Env) of + false -> 5000; + {_, MaxBuffer} -> MaxBuffer + end. + +%% Update the state if the response was sent in the callback. +after_call(Req, State=#state{resp_sent=false}, Handler, + HandlerState) -> + receive + {cowboy_req, resp_sent} -> + before_loop(Req, State#state{resp_sent=true}, Handler, HandlerState) + after 0 -> + before_loop(Req, State, Handler, HandlerState) + end; +after_call(Req, State, Handler, HandlerState) -> + before_loop(Req, State, Handler, HandlerState). + +before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) -> + [Socket, Transport] = cowboy_req:get([socket, transport], Req), + Transport:setopts(Socket, [{active, once}]), + {suspend, ?MODULE, loop, [Req, State#state{hibernate=false}, Handler, HandlerState]}; +before_loop(Req, State, Handler, HandlerState) -> + [Socket, Transport] = cowboy_req:get([socket, transport], Req), + Transport:setopts(Socket, [{active, once}]), + loop(Req, State, Handler, HandlerState). + +%% Almost the same code can be found in cowboy_websocket. +timeout(State=#state{timeout=infinity}) -> + State#state{timeout_ref=undefined}; +timeout(State=#state{timeout=Timeout, + timeout_ref=PrevRef}) -> + _ = case PrevRef of + undefined -> ignore; + PrevRef -> erlang:cancel_timer(PrevRef) + end, + TRef = erlang:start_timer(Timeout, self(), ?MODULE), + State#state{timeout_ref=TRef}. + +-spec loop(Req, #state{}, module(), any()) + -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(). +loop(Req, State=#state{buffer_size=NbBytes, + max_buffer=Threshold, timeout_ref=TRef, + resp_sent=RespSent}, Handler, HandlerState) -> + [Socket, Transport] = cowboy_req:get([socket, transport], Req), + {OK, Closed, Error} = Transport:messages(), + receive + {OK, Socket, Data} -> + NbBytes2 = NbBytes + byte_size(Data), + if NbBytes2 > Threshold -> + _ = if RespSent -> ok; true -> + cowboy_req:reply(500, Req) + end, + cowboy_handler:terminate({error, overflow}, Req, HandlerState, Handler), + exit(normal); + true -> + Req2 = cowboy_req:append_buffer(Data, Req), + State2 = timeout(State#state{buffer_size=NbBytes2}), + before_loop(Req2, State2, Handler, HandlerState) + end; + {Closed, Socket} -> + terminate(Req, State, Handler, HandlerState, {error, closed}); + {Error, Socket, Reason} -> + terminate(Req, State, Handler, HandlerState, {error, Reason}); + {timeout, TRef, ?MODULE} -> + after_loop(Req, State, Handler, HandlerState, timeout); + {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) -> + loop(Req, State, Handler, HandlerState); + Message -> + %% We set the socket back to {active, false} mode in case + %% the handler is going to call recv. We also flush any + %% data received after that and put it into the buffer. + %% We do not check the size here, if data keeps coming + %% we'll error out on the next packet received. + Transport:setopts(Socket, [{active, false}]), + Req2 = receive {OK, Socket, Data} -> + cowboy_req:append_buffer(Data, Req) + after 0 -> + Req + end, + call(Req2, State, Handler, HandlerState, Message) + end. + +call(Req, State=#state{resp_sent=RespSent}, + Handler, HandlerState, Message) -> + try Handler:info(Message, Req, HandlerState) of + {ok, Req2, HandlerState2} -> + after_call(Req2, State, Handler, HandlerState2); + {ok, Req2, HandlerState2, hibernate} -> + after_call(Req2, State#state{hibernate=true}, Handler, HandlerState2); + {shutdown, Req2, HandlerState2} -> + after_loop(Req2, State, Handler, HandlerState2, shutdown) + catch Class:Reason -> + Stacktrace = erlang:get_stacktrace(), + if RespSent -> ok; true -> + cowboy_req:maybe_reply(Stacktrace, Req) + end, + cowboy_handler:terminate({crash, Class, Reason}, Req, HandlerState, Handler), + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, info, 3}}, + {stacktrace, Stacktrace}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState} + ]) + end. + +%% It is sometimes important to make a socket passive as it was initially +%% and as it is expected to be by cowboy_protocol, right after we're done +%% with loop handling. The browser may freely pipeline a bunch of requests +%% if previous one was, say, a JSONP long-polling request. +after_loop(Req, State, Handler, HandlerState, Reason) -> + [Socket, Transport] = cowboy_req:get([socket, transport], Req), + Transport:setopts(Socket, [{active, false}]), + {OK, _Closed, _Error} = Transport:messages(), + Req2 = receive + {OK, Socket, Data} -> + cowboy_req:append_buffer(Data, Req) + after 0 -> + Req + end, + terminate(Req2, State, Handler, HandlerState, Reason). + +terminate(Req, #state{env=Env, timeout_ref=TRef}, + Handler, HandlerState, Reason) -> + _ = case TRef of + undefined -> ignore; + TRef -> erlang:cancel_timer(TRef) + end, + flush_timeouts(), + Result = cowboy_handler:terminate(Reason, Req, HandlerState, Handler), + {ok, Req, [{result, Result}|Env]}. + +flush_timeouts() -> + receive + {timeout, TRef, ?MODULE} when is_reference(TRef) -> + flush_timeouts() + after 0 -> + ok + end. diff --git a/src/cowboy_loop_handler.erl b/src/cowboy_loop_handler.erl deleted file mode 100644 index 5e50c34..0000000 --- a/src/cowboy_loop_handler.erl +++ /dev/null @@ -1,39 +0,0 @@ -%% Copyright (c) 2011-2014, Loïc Hoguin -%% -%% 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_loop_handler). - --type opts() :: any(). --type state() :: any(). -%% @todo see terminate -%-type terminate_reason() :: {normal, shutdown} -% | {normal, timeout} -% | {error, closed} -% | {error, overflow} -% | {error, atom()}. - --callback init(Req, opts()) - -> {http, Req, state()} - | {long_polling | rest | ws | module(), Req, state()} - | {long_polling | rest | ws | module(), Req, state(), hibernate} - | {long_polling | rest | ws | module(), Req, state(), timeout()} - | {long_polling | rest | ws | module(), Req, state(), timeout(), hibernate} - | {shutdown, Req, state()} - when Req::cowboy_req:req(). --callback info(any(), Req, State) - -> {ok, Req, State} - | {loop, Req, State} - | {loop, Req, State, hibernate} - when Req::cowboy_req:req(), State::state(). -%% @todo optional -callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index 63f33e6..98c6f3b 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -19,6 +19,15 @@ -export([upgrade/6]). +-callback init(Req, any()) + -> {ok | module(), Req, any()} + | {module(), Req, any(), hibernate} + | {module(), Req, any(), timeout()} + | {module(), Req, any(), timeout(), hibernate} + when Req::cowboy_req:req(). +%% @todo optional REST callbacks +%% @todo optional -callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. + -record(state, { env :: cowboy_middleware:env(), method = undefined :: binary(), @@ -967,11 +976,11 @@ next(Req, State, StatusCode) when is_integer(StatusCode) -> respond(Req, State, StatusCode) -> terminate(cowboy_req:reply(StatusCode, Req), State). -error_terminate(Req, State=#state{handler=Handler, handler_state=HandlerState}, +error_terminate(Req, #state{handler=Handler, handler_state=HandlerState}, Class, Reason, Callback) -> - _ = terminate(Req, State), Stacktrace = erlang:get_stacktrace(), cowboy_req:maybe_reply(Stacktrace, Req), + cowboy_handler:terminate({crash, Class, Reason}, Req, HandlerState, Handler), erlang:Class([ {reason, Reason}, {mfa, {Handler, Callback, 2}}, @@ -981,4 +990,5 @@ error_terminate(Req, State=#state{handler=Handler, handler_state=HandlerState}, ]). terminate(Req, #state{env=Env, handler=Handler, handler_state=HandlerState}) -> - cowboy_handler:terminate(Req, Env, Handler, HandlerState, {normal, shutdown}). + Result = cowboy_handler:terminate(normal, Req, HandlerState, Handler), + {ok, Req, [{result, Result}|Env]}. diff --git a/src/cowboy_static.erl b/src/cowboy_static.erl index ecca6fa..99ad82e 100644 --- a/src/cowboy_static.erl +++ b/src/cowboy_static.erl @@ -42,7 +42,7 @@ %% If the handler is configured to manage a directory, check that the %% requested file is inside the configured directory. --spec init(Req, opts()) -> {rest, Req, error | state()} when Req::cowboy_req:req(). +-spec init(Req, opts()) -> {cowboy_rest, Req, error | state()} when Req::cowboy_req:req(). init(Req, {Name, Path}) -> init_opts(Req, {Name, Path, []}); init(Req, {Name, App, Path}) @@ -87,7 +87,7 @@ init_dir(Req, Path, Extra) -> << Dir:Len/binary, $/, _/binary >> -> init_info(Req, Filepath, Extra); _ -> - {rest, Req, error} + {cowboy_rest, Req, error} end. fullpath(Path) -> @@ -105,7 +105,7 @@ fullpath([Segment|Tail], Acc) -> init_info(Req, Path, Extra) -> Info = file:read_file_info(Path, [{time, universal}]), - {rest, Req, {Path, Info, Extra}}. + {cowboy_rest, Req, {Path, Info, Extra}}. -ifdef(TEST). fullpath_test_() -> diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl index e0b20ca..c700af9 100644 --- a/src/cowboy_websocket.erl +++ b/src/cowboy_websocket.erl @@ -33,8 +33,32 @@ -type frag_state() :: undefined | {nofin, opcode(), binary()} | {fin, opcode(), binary()}. -type rsv() :: << _:3 >>. --type terminate_reason() :: {normal | error | remote, atom()} - | {remote, close_code(), binary()}. +-type terminate_reason() :: normal | shutdown | timeout + | remote | {remote, close_code(), binary()} + | {error, badencoding | badframe | closed | atom()} + | {crash, error | exit | throw, any()}. + +-callback init(Req, any()) + -> {ok | module(), Req, any()} + | {module(), Req, any(), hibernate} + | {module(), Req, any(), timeout()} + | {module(), Req, any(), timeout(), hibernate} + when Req::cowboy_req:req(). +-callback websocket_handle({text | binary | ping | pong, binary()}, Req, State) + -> {ok, Req, State} + | {ok, Req, State, hibernate} + | {reply, frame() | [frame()], Req, State} + | {reply, frame() | [frame()], Req, State, hibernate} + | {shutdown, Req, State} + when Req::cowboy_req:req(), State::any(). +-callback websocket_info(any(), Req, State) + -> {ok, Req, State} + | {ok, Req, State, hibernate} + | {reply, frame() | [frame()], Req, State} + | {reply, frame() | [frame()], Req, State, hibernate} + | {shutdown, Req, State} + when Req::cowboy_req:req(), State::any(). +%% @todo optional -callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. -record(state, { env :: cowboy_middleware:env(), @@ -181,7 +205,7 @@ handler_loop(State=#state{socket=Socket, messages={OK, Closed, Error}, {Error, Socket, Reason} -> handler_terminate(State, Req, HandlerState, {error, Reason}); {timeout, TRef, ?MODULE} -> - websocket_close(State, Req, HandlerState, {normal, timeout}); + websocket_close(State, Req, HandlerState, timeout); {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) -> handler_loop(State, Req, HandlerState, SoFar); Message -> @@ -487,7 +511,7 @@ websocket_payload_loop(State=#state{socket=Socket, transport=Transport, {Error, Socket, Reason} -> handler_terminate(State, Req, HandlerState, {error, Reason}); {timeout, TRef, ?MODULE} -> - websocket_close(State, Req, HandlerState, {normal, timeout}); + websocket_close(State, Req, HandlerState, timeout); {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) -> websocket_payload_loop(State, Req, HandlerState, Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Rsv); @@ -524,7 +548,7 @@ websocket_dispatch(State, Req, HandlerState, RemainingData, 2, Payload) -> websocket_handle, {binary, Payload}, fun websocket_data/4); %% Close control frame. websocket_dispatch(State, Req, HandlerState, _RemainingData, 8, <<>>) -> - websocket_close(State, Req, HandlerState, {remote, closed}); + websocket_close(State, Req, HandlerState, remote); websocket_dispatch(State, Req, HandlerState, _RemainingData, 8, << Code:16, Payload/bits >>) -> websocket_close(State, Req, HandlerState, {remote, Code, Payload}); @@ -558,8 +582,7 @@ handler_call(State=#state{handler=Handler}, Req, HandlerState, {ok, State2} -> NextState(State2, Req2, HandlerState2, RemainingData); {shutdown, State2} -> - handler_terminate(State2, Req2, HandlerState2, - {normal, shutdown}); + handler_terminate(State2, Req2, HandlerState2, shutdown); {{error, _} = Error, State2} -> handler_terminate(State2, Req2, HandlerState2, Error) end; @@ -570,8 +593,7 @@ handler_call(State=#state{handler=Handler}, Req, HandlerState, NextState(State2#state{hibernate=true}, Req2, HandlerState2, RemainingData); {shutdown, State2} -> - handler_terminate(State2, Req2, HandlerState2, - {normal, shutdown}); + handler_terminate(State2, Req2, HandlerState2, shutdown); {{error, _} = Error, State2} -> handler_terminate(State2, Req2, HandlerState2, Error) end; @@ -580,8 +602,7 @@ handler_call(State=#state{handler=Handler}, Req, HandlerState, {ok, State2} -> NextState(State2, Req2, HandlerState2, RemainingData); {shutdown, State2} -> - handler_terminate(State2, Req2, HandlerState2, - {normal, shutdown}); + handler_terminate(State2, Req2, HandlerState2, shutdown); {{error, _} = Error, State2} -> handler_terminate(State2, Req2, HandlerState2, Error) end; @@ -591,15 +612,14 @@ handler_call(State=#state{handler=Handler}, Req, HandlerState, NextState(State2#state{hibernate=true}, Req2, HandlerState2, RemainingData); {shutdown, State2} -> - handler_terminate(State2, Req2, HandlerState2, - {normal, shutdown}); + handler_terminate(State2, Req2, HandlerState2, shutdown); {{error, _} = Error, State2} -> handler_terminate(State2, Req2, HandlerState2, Error) end; {shutdown, Req2, HandlerState2} -> - websocket_close(State, Req2, HandlerState2, {normal, shutdown}) + websocket_close(State, Req2, HandlerState2, shutdown) catch Class:Reason -> - _ = websocket_close(State, Req, HandlerState, {error, handler}), + _ = websocket_close(State, Req, HandlerState, {crash, Class, Reason}), erlang:Class([ {reason, Reason}, {mfa, {Handler, Callback, 3}}, @@ -696,15 +716,15 @@ websocket_send_many([Frame|Tail], State) -> websocket_close(State=#state{socket=Socket, transport=Transport}, Req, HandlerState, Reason) -> case Reason of - {normal, _} -> + Normal when Normal =:= shutdown; Normal =:= timeout -> Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>); {error, badframe} -> Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1002:16 >>); {error, badencoding} -> Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1007:16 >>); - {error, handler} -> + {crash, _, _} -> Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1011:16 >>); - {remote, closed} -> + remote -> Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>); {remote, Code, _} -> Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, Code:16 >>) @@ -716,5 +736,5 @@ websocket_close(State=#state{socket=Socket, transport=Transport}, when Req::cowboy_req:req(). handler_terminate(#state{env=Env, handler=Handler}, Req, HandlerState, Reason) -> - _ = cowboy_handler:terminate(Req, Env, Handler, HandlerState, Reason), + cowboy_handler:terminate(Reason, Req, HandlerState, Handler), {ok, Req, [{result, closed}|Env]}. diff --git a/src/cowboy_websocket_handler.erl b/src/cowboy_websocket_handler.erl deleted file mode 100644 index a6a036b..0000000 --- a/src/cowboy_websocket_handler.erl +++ /dev/null @@ -1,51 +0,0 @@ -%% Copyright (c) 2011-2014, Loïc Hoguin -%% -%% 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_websocket_handler). - --type opts() :: any(). --type state() :: any(). -%% @todo see terminate -%-type terminate_reason() :: {normal, shutdown} -% | {normal, timeout} -% | {error, closed} -% | {remote, closed} -% | {remote, cowboy_websocket:close_code(), binary()} -% | {error, badencoding} -% | {error, badframe} -% | {error, atom()}. - --callback init(Req, opts()) - -> {http, Req, state()} - | {long_polling | rest | ws | module(), Req, state()} - | {long_polling | rest | ws | module(), Req, state(), hibernate} - | {long_polling | rest | ws | module(), Req, state(), timeout()} - | {long_polling | rest | ws | module(), Req, state(), timeout(), hibernate} - | {shutdown, Req, state()} - when Req::cowboy_req:req(). --callback websocket_handle({text | binary | ping | pong, binary()}, Req, State) - -> {ok, Req, State} - | {ok, Req, State, hibernate} - | {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State} - | {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State, hibernate} - | {shutdown, Req, State} - when Req::cowboy_req:req(), State::state(). --callback websocket_info(any(), Req, State) - -> {ok, Req, State} - | {ok, Req, State, hibernate} - | {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State} - | {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State, hibernate} - | {shutdown, Req, State} - when Req::cowboy_req:req(), State::state(). -%% @todo optional -callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. diff --git a/test/handlers/long_polling_h.erl b/test/handlers/long_polling_h.erl index 1b240fd..20fe7ee 100644 --- a/test/handlers/long_polling_h.erl +++ b/test/handlers/long_polling_h.erl @@ -11,15 +11,15 @@ init(Req, _) -> erlang:send_after(200, self(), timeout), - {long_polling, Req, 2, 5000, hibernate}. + {cowboy_loop, Req, 2, 5000, hibernate}. info(timeout, Req, 0) -> - {ok, cowboy_req:reply(102, Req), 0}; + {shutdown, cowboy_req:reply(102, Req), 0}; info(timeout, Req, Count) -> erlang:send_after(200, self(), timeout), - {loop, Req, Count - 1, hibernate}. + {ok, Req, Count - 1, hibernate}. -terminate({normal, shutdown}, _, 0) -> +terminate(shutdown, _, 0) -> ok; terminate({error, overflow}, _, _) -> ok. diff --git a/test/handlers/loop_handler_body_h.erl b/test/handlers/loop_handler_body_h.erl index ac75773..096fb3d 100644 --- a/test/handlers/loop_handler_body_h.erl +++ b/test/handlers/loop_handler_body_h.erl @@ -11,12 +11,12 @@ init(Req, _) -> self() ! timeout, - {long_polling, Req, undefined, 5000, hibernate}. + {cowboy_loop, Req, undefined, 5000, hibernate}. info(timeout, Req, State) -> {ok, Body, Req2} = cowboy_req:body(Req), 100000 = byte_size(Body), - {ok, cowboy_req:reply(200, Req2), State}. + {shutdown, cowboy_req:reply(200, Req2), State}. -terminate({normal, shutdown}, _, _) -> +terminate(shutdown, _, _) -> ok. diff --git a/test/handlers/loop_handler_timeout_h.erl b/test/handlers/loop_handler_timeout_h.erl index 8e24d33..a1bfa51 100644 --- a/test/handlers/loop_handler_timeout_h.erl +++ b/test/handlers/loop_handler_timeout_h.erl @@ -1,7 +1,7 @@ %% This module implements a loop handler that sends %% itself a timeout that will intentionally arrive %% too late, as it configures itself to only wait -%% 200ms before closing the connection in init/3. +%% 200ms before closing the connection in init/2. %% This results in a 204 reply being sent back by Cowboy. -module(loop_handler_timeout_h). @@ -12,10 +12,10 @@ init(Req, _) -> erlang:send_after(1000, self(), timeout), - {long_polling, Req, undefined, 200, hibernate}. + {cowboy_loop, Req, undefined, 200, hibernate}. info(timeout, Req, State) -> - {ok, cowboy_req:reply(500, Req), State}. + {shutdown, cowboy_req:reply(500, Req), State}. -terminate({normal, timeout}, _, _) -> +terminate(timeout, _, _) -> ok. diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index e98ce1b..3783b6e 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -142,7 +142,6 @@ init_dispatch(Config) -> {"localhost", [ {"/chunked_response", http_chunked, []}, {"/streamed_response", http_streamed, []}, - {"/init_shutdown", http_init_shutdown, []}, {"/headers/dupe", http_handler, [{headers, [{<<"connection">>, <<"close">>}]}]}, {"/set_resp/header", http_set_resp, @@ -292,9 +291,7 @@ check_status(Config) -> {403, "/static/unreadable"}, {404, "/not/found"}, {404, "/static/not_found"}, - {500, "/handler_errors?case=handle_before_reply"}, - {500, "/handler_errors?case=init_before_reply"}, - {666, "/init_shutdown"} + {500, "/handler_errors?case=init_before_reply"} ], _ = [{Status, URL} = begin Ret = do_get(URL, Config), @@ -342,40 +339,12 @@ echo_body_qs_max_length(Config) -> {response, nofin, 413, _} = gun:await(ConnPid, Ref), ok. -error_chain_handle_after_reply(Config) -> - {ConnPid, MRef} = gun_monitor_open(Config), - Ref1 = gun:get(ConnPid, "/"), - Ref2 = gun:get(ConnPid, "/handler_errors?case=handle_after_reply"), - {response, nofin, 200, _} = gun:await(ConnPid, Ref1, MRef), - {response, nofin, 200, _} = gun:await(ConnPid, Ref2, MRef), - gun_is_gone(ConnPid, MRef). - -error_chain_handle_before_reply(Config) -> - {ConnPid, MRef} = gun_monitor_open(Config), - Ref1 = gun:get(ConnPid, "/"), - Ref2 = gun:get(ConnPid, "/handler_errors?case=handle_before_reply"), - {response, nofin, 200, _} = gun:await(ConnPid, Ref1, MRef), - {response, fin, 500, _} = gun:await(ConnPid, Ref2, MRef), - gun_is_gone(ConnPid, MRef). - -error_handle_after_reply(Config) -> - {ConnPid, MRef} = gun_monitor_open(Config), - Ref = gun:get(ConnPid, "/handler_errors?case=handle_after_reply"), - {response, nofin, 200, _} = gun:await(ConnPid, Ref, MRef), - gun_is_gone(ConnPid, MRef). - error_init_after_reply(Config) -> {ConnPid, MRef} = gun_monitor_open(Config), Ref = gun:get(ConnPid, "/handler_errors?case=init_after_reply"), {response, nofin, 200, _} = gun:await(ConnPid, Ref, MRef), gun_is_gone(ConnPid, MRef). -error_init_reply_handle_error(Config) -> - {ConnPid, MRef} = gun_monitor_open(Config), - Ref = gun:get(ConnPid, "/handler_errors?case=init_reply_handle_error"), - {response, nofin, 200, _} = gun:await(ConnPid, Ref, MRef), - gun_is_gone(ConnPid, MRef). - headers_dupe(Config) -> {ConnPid, MRef} = gun_monitor_open(Config), Ref = gun:get(ConnPid, "/headers/dupe"), diff --git a/test/http_SUITE_data/http_body_qs.erl b/test/http_SUITE_data/http_body_qs.erl index b219566..945b7fd 100644 --- a/test/http_SUITE_data/http_body_qs.erl +++ b/test/http_SUITE_data/http_body_qs.erl @@ -3,15 +3,11 @@ -module(http_body_qs). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> Method = cowboy_req:method(Req), HasBody = cowboy_req:has_body(Req), - {ok, maybe_echo(Method, HasBody, Req), State}. + {ok, maybe_echo(Method, HasBody, Req), Opts}. maybe_echo(<<"POST">>, true, Req) -> case cowboy_req:body_qs(Req) of diff --git a/test/http_SUITE_data/http_chunked.erl b/test/http_SUITE_data/http_chunked.erl index 0c18363..87a6852 100644 --- a/test/http_SUITE_data/http_chunked.erl +++ b/test/http_SUITE_data/http_chunked.erl @@ -3,15 +3,11 @@ -module(http_chunked). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> Req2 = cowboy_req:chunked_reply(200, Req), timer:sleep(100), cowboy_req:chunk("chunked_handler\r\n", Req2), timer:sleep(100), cowboy_req:chunk("works fine!", Req2), - {ok, Req2, State}. + {ok, Req2, Opts}. diff --git a/test/http_SUITE_data/http_echo_body.erl b/test/http_SUITE_data/http_echo_body.erl index 8743844..c76b9b3 100644 --- a/test/http_SUITE_data/http_echo_body.erl +++ b/test/http_SUITE_data/http_echo_body.erl @@ -3,18 +3,14 @@ -module(http_echo_body). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> true = cowboy_req:has_body(Req), Req3 = case cowboy_req:body(Req, [{length, 1000000}]) of {ok, Body, Req2} -> handle_body(Req2, Body); {more, _, Req2} -> handle_badlength(Req2) end, - {ok, Req3, State}. + {ok, Req3, Opts}. handle_badlength(Req) -> cowboy_req:reply(413, [], <<"Request entity too large">>, Req). diff --git a/test/http_SUITE_data/http_errors.erl b/test/http_SUITE_data/http_errors.erl index 39eda60..f105c58 100644 --- a/test/http_SUITE_data/http_errors.erl +++ b/test/http_SUITE_data/http_errors.erl @@ -3,7 +3,6 @@ -module(http_errors). -export([init/2]). --export([handle/2]). init(Req, _Opts) -> #{'case' := Case} = cowboy_req:match_qs(Req, ['case']), @@ -15,22 +14,4 @@ case_init(<<"init_before_reply">> = Case, _Req) -> case_init(<<"init_after_reply">> = Case, Req) -> cowboy_error_h:ignore(?MODULE, case_init, 2), _ = cowboy_req:reply(200, [], "http_handler_crashes", Req), - error(Case); -case_init(<<"init_reply_handle_error">> = Case, Req) -> - Req1 = cowboy_req:reply(200, [], "http_handler_crashes", Req), - {http, Req1, Case}; -case_init(<<"handle_before_reply">> = Case, Req) -> - {http, Req, Case}; -case_init(<<"handle_after_reply">> = Case, Req) -> - {http, Req, Case}. - -handle(_Req, <<"init_reply_handle_error">> = Case) -> - cowboy_error_h:ignore(?MODULE, handle, 2), - error(Case); -handle(_Req, <<"handle_before_reply">> = Case) -> - cowboy_error_h:ignore(?MODULE, handle, 2), - error(Case); -handle(Req, <<"handle_after_reply">> = Case) -> - cowboy_error_h:ignore(?MODULE, handle, 2), - _ = cowboy_req:reply(200, [], "http_handler_crashes", Req), error(Case). diff --git a/test/http_SUITE_data/http_handler.erl b/test/http_SUITE_data/http_handler.erl index 61c14f8..62e9193 100644 --- a/test/http_SUITE_data/http_handler.erl +++ b/test/http_SUITE_data/http_handler.erl @@ -3,12 +3,8 @@ -module(http_handler). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> - Headers = proplists:get_value(headers, State, []), - Body = proplists:get_value(body, State, "http_handler"), - {ok, cowboy_req:reply(200, Headers, Body, Req), State}. + Headers = proplists:get_value(headers, Opts, []), + Body = proplists:get_value(body, Opts, "http_handler"), + {ok, cowboy_req:reply(200, Headers, Body, Req), Opts}. diff --git a/test/http_SUITE_data/http_init_shutdown.erl b/test/http_SUITE_data/http_init_shutdown.erl deleted file mode 100644 index 5aae898..0000000 --- a/test/http_SUITE_data/http_init_shutdown.erl +++ /dev/null @@ -1,10 +0,0 @@ -%% Feel free to use, reuse and abuse the code in this file. - --module(http_init_shutdown). - --export([init/2]). - -init(Req, _) -> - Req2 = cowboy_req:reply(<<"666 Init Shutdown Testing">>, - [{<<"connection">>, <<"close">>}], Req), - {shutdown, Req2, undefined}. diff --git a/test/http_SUITE_data/http_loop_stream_recv.erl b/test/http_SUITE_data/http_loop_stream_recv.erl index 8547cc9..4cd39a2 100644 --- a/test/http_SUITE_data/http_loop_stream_recv.erl +++ b/test/http_SUITE_data/http_loop_stream_recv.erl @@ -9,7 +9,7 @@ init(Req, _) -> receive after 100 -> ok end, self() ! stream, - {long_polling, Req, undefined, 100}. + {cowboy_loop, Req, undefined, 100}. info(stream, Req, undefined) -> stream(Req, 1, <<>>). @@ -17,7 +17,7 @@ info(stream, Req, undefined) -> stream(Req, ID, Acc) -> case cowboy_req:body(Req) of {ok, <<>>, Req2} -> - {ok, cowboy_req:reply(200, Req2), undefined}; + {shutdown, cowboy_req:reply(200, Req2), undefined}; {_, Data, Req2} -> parse_id(Req2, ID, << Acc/binary, Data/binary >>) end. @@ -30,5 +30,5 @@ parse_id(Req, ID, Data) -> stream(Req, ID, Data) end. -terminate({normal, shutdown}, _, _) -> +terminate(shutdown, _, _) -> ok. diff --git a/test/http_SUITE_data/http_multipart.erl b/test/http_SUITE_data/http_multipart.erl index 6bd6408..196cbce 100644 --- a/test/http_SUITE_data/http_multipart.erl +++ b/test/http_SUITE_data/http_multipart.erl @@ -3,14 +3,10 @@ -module(http_multipart). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> {Result, Req2} = acc_multipart(Req, []), - {ok, cowboy_req:reply(200, [], term_to_binary(Result), Req2), State}. + {ok, cowboy_req:reply(200, [], term_to_binary(Result), Req2), Opts}. acc_multipart(Req, Acc) -> case cowboy_req:part(Req) of diff --git a/test/http_SUITE_data/http_multipart_stream.erl b/test/http_SUITE_data/http_multipart_stream.erl index 43d459a..82662ad 100644 --- a/test/http_SUITE_data/http_multipart_stream.erl +++ b/test/http_SUITE_data/http_multipart_stream.erl @@ -3,14 +3,10 @@ -module(http_multipart_stream). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> Req2 = multipart(Req), - {ok, cowboy_req:reply(200, Req2), State}. + {ok, cowboy_req:reply(200, Req2), Opts}. multipart(Req) -> case cowboy_req:part(Req) of diff --git a/test/http_SUITE_data/http_req_attr.erl b/test/http_SUITE_data/http_req_attr.erl index 9c5acba..ce9c185 100644 --- a/test/http_SUITE_data/http_req_attr.erl +++ b/test/http_SUITE_data/http_req_attr.erl @@ -5,15 +5,11 @@ -module(http_req_attr). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> #{attr := Attr} = cowboy_req:match_qs(Req, [attr]), <<"host_and_port">> = Attr, Host = cowboy_req:host(Req), Port = cowboy_req:port(Req), Value = [Host, "\n", integer_to_list(Port)], - {ok, cowboy_req:reply(200, [], Value, Req), State}. + {ok, cowboy_req:reply(200, [], Value, Req), Opts}. diff --git a/test/http_SUITE_data/http_set_resp.erl b/test/http_SUITE_data/http_set_resp.erl index 77196a8..2e7f835 100644 --- a/test/http_SUITE_data/http_set_resp.erl +++ b/test/http_SUITE_data/http_set_resp.erl @@ -3,7 +3,6 @@ -module(http_set_resp). -export([init/2]). --export([handle/2]). init(Req, Opts) -> Headers = proplists:get_value(headers, Opts, []), @@ -14,16 +13,13 @@ init(Req, Opts) -> Req3 = cowboy_req:set_resp_body(Body, Req2), Req4 = cowboy_req:set_resp_header(<<"x-cowboy-test">>, <<"ok">>, Req3), Req5 = cowboy_req:set_resp_cookie(<<"cake">>, <<"lie">>, [], Req4), - {http, Req5, undefined}. - -handle(Req, State) -> - case cowboy_req:has_resp_header(<<"x-cowboy-test">>, Req) of - false -> {ok, Req, State}; + case cowboy_req:has_resp_header(<<"x-cowboy-test">>, Req5) of + false -> {ok, Req5, Opts}; true -> - case cowboy_req:has_resp_body(Req) of + case cowboy_req:has_resp_body(Req5) of false -> - {ok, Req, State}; + {ok, Req5, Opts}; true -> - {ok, cowboy_req:reply(200, Req), State} + {ok, cowboy_req:reply(200, Req5), Opts} end end. diff --git a/test/http_SUITE_data/http_stream_body.erl b/test/http_SUITE_data/http_stream_body.erl index 29569cd..aea5300 100644 --- a/test/http_SUITE_data/http_stream_body.erl +++ b/test/http_SUITE_data/http_stream_body.erl @@ -3,14 +3,10 @@ -module(http_stream_body). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> - Body = proplists:get_value(body, State, "http_handler_stream_body"), - Reply = proplists:get_value(reply, State), + Body = proplists:get_value(body, Opts, "http_handler_stream_body"), + Reply = proplists:get_value(reply, Opts), SFun = fun(Socket, Transport) -> Transport:send(Socket, Body) end, Req2 = case Reply of set_resp -> @@ -23,4 +19,4 @@ handle(Req, State) -> SFun2 = fun(SendFun) -> lists:foreach(SendFun, Body) end, cowboy_req:set_resp_body_fun(chunked, SFun2, Req) end, - {ok, cowboy_req:reply(200, Req2), State}. + {ok, cowboy_req:reply(200, Req2), Opts}. diff --git a/test/http_SUITE_data/http_streamed.erl b/test/http_SUITE_data/http_streamed.erl index ba710f1..6ca53fb 100644 --- a/test/http_SUITE_data/http_streamed.erl +++ b/test/http_SUITE_data/http_streamed.erl @@ -3,16 +3,12 @@ -module(http_streamed). -export([init/2]). --export([handle/2]). init(Req, Opts) -> - {http, Req, Opts}. - -handle(Req, State) -> Req2 = cowboy_req:set([{resp_state, waiting_stream}], Req), Req3 = cowboy_req:chunked_reply(200, Req2), timer:sleep(100), cowboy_req:chunk("streamed_handler\r\n", Req3), timer:sleep(100), cowboy_req:chunk("works fine!", Req3), - {ok, Req3, State}. + {ok, Req3, Opts}. diff --git a/test/http_SUITE_data/rest_empty_resource.erl b/test/http_SUITE_data/rest_empty_resource.erl index 97fc26f..8a944cd 100644 --- a/test/http_SUITE_data/rest_empty_resource.erl +++ b/test/http_SUITE_data/rest_empty_resource.erl @@ -3,4 +3,4 @@ -export([init/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. diff --git a/test/http_SUITE_data/rest_expires.erl b/test/http_SUITE_data/rest_expires.erl index e71b107..8665b06 100644 --- a/test/http_SUITE_data/rest_expires.erl +++ b/test/http_SUITE_data/rest_expires.erl @@ -7,7 +7,7 @@ -export([last_modified/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. content_types_provided(Req, State) -> {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}. diff --git a/test/http_SUITE_data/rest_expires_binary.erl b/test/http_SUITE_data/rest_expires_binary.erl index 84b0675..4d6bd3c 100644 --- a/test/http_SUITE_data/rest_expires_binary.erl +++ b/test/http_SUITE_data/rest_expires_binary.erl @@ -6,7 +6,7 @@ -export([expires/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. content_types_provided(Req, State) -> {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}. diff --git a/test/http_SUITE_data/rest_forbidden_resource.erl b/test/http_SUITE_data/rest_forbidden_resource.erl index 1e6d99f..0a65228 100644 --- a/test/http_SUITE_data/rest_forbidden_resource.erl +++ b/test/http_SUITE_data/rest_forbidden_resource.erl @@ -9,7 +9,7 @@ -export([from_text/2]). init(Req, [Forbidden]) -> - {rest, Req, Forbidden}. + {cowboy_rest, Req, Forbidden}. allowed_methods(Req, State) -> {[<<"GET">>, <<"HEAD">>, <<"POST">>], Req, State}. diff --git a/test/http_SUITE_data/rest_missing_callbacks.erl b/test/http_SUITE_data/rest_missing_callbacks.erl index fec308a..e1fcac1 100644 --- a/test/http_SUITE_data/rest_missing_callbacks.erl +++ b/test/http_SUITE_data/rest_missing_callbacks.erl @@ -6,7 +6,7 @@ -export([content_types_provided/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. allowed_methods(Req, State) -> {[<<"GET">>, <<"PUT">>], Req, State}. diff --git a/test/http_SUITE_data/rest_nodelete_resource.erl b/test/http_SUITE_data/rest_nodelete_resource.erl index e8123db..b9f40bd 100644 --- a/test/http_SUITE_data/rest_nodelete_resource.erl +++ b/test/http_SUITE_data/rest_nodelete_resource.erl @@ -6,7 +6,7 @@ -export([get_text_plain/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. allowed_methods(Req, State) -> {[<<"GET">>, <<"HEAD">>, <<"DELETE">>], Req, State}. diff --git a/test/http_SUITE_data/rest_param_all.erl b/test/http_SUITE_data/rest_param_all.erl index 54950eb..2b2ea23 100644 --- a/test/http_SUITE_data/rest_param_all.erl +++ b/test/http_SUITE_data/rest_param_all.erl @@ -8,7 +8,7 @@ -export([put_text_plain/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. allowed_methods(Req, State) -> {[<<"GET">>, <<"PUT">>], Req, State}. diff --git a/test/http_SUITE_data/rest_patch_resource.erl b/test/http_SUITE_data/rest_patch_resource.erl index 4b81648..03f780f 100644 --- a/test/http_SUITE_data/rest_patch_resource.erl +++ b/test/http_SUITE_data/rest_patch_resource.erl @@ -8,7 +8,7 @@ -export([patch_text_plain/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. allowed_methods(Req, State) -> {[<<"HEAD">>, <<"GET">>, <<"PATCH">>], Req, State}. diff --git a/test/http_SUITE_data/rest_post_charset_resource.erl b/test/http_SUITE_data/rest_post_charset_resource.erl index 7b7c49c..0be6aa3 100644 --- a/test/http_SUITE_data/rest_post_charset_resource.erl +++ b/test/http_SUITE_data/rest_post_charset_resource.erl @@ -6,7 +6,7 @@ -export([from_text/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. allowed_methods(Req, State) -> {[<<"POST">>], Req, State}. diff --git a/test/http_SUITE_data/rest_postonly_resource.erl b/test/http_SUITE_data/rest_postonly_resource.erl index 9b14b24..942e55d 100644 --- a/test/http_SUITE_data/rest_postonly_resource.erl +++ b/test/http_SUITE_data/rest_postonly_resource.erl @@ -6,7 +6,7 @@ -export([from_text/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. allowed_methods(Req, State) -> {[<<"POST">>], Req, State}. diff --git a/test/http_SUITE_data/rest_resource_etags.erl b/test/http_SUITE_data/rest_resource_etags.erl index 9509d0a..23b9dfc 100644 --- a/test/http_SUITE_data/rest_resource_etags.erl +++ b/test/http_SUITE_data/rest_resource_etags.erl @@ -6,7 +6,7 @@ -export([get_text_plain/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. generate_etag(Req, State) -> #{type := Type} = cowboy_req:match_qs(Req, [type]), diff --git a/test/http_SUITE_data/rest_simple_resource.erl b/test/http_SUITE_data/rest_simple_resource.erl index 3d2787f..68e1b95 100644 --- a/test/http_SUITE_data/rest_simple_resource.erl +++ b/test/http_SUITE_data/rest_simple_resource.erl @@ -5,7 +5,7 @@ -export([get_text_plain/2]). init(Req, Opts) -> - {rest, Req, Opts}. + {cowboy_rest, Req, Opts}. content_types_provided(Req, State) -> {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}. diff --git a/test/ws_SUITE_data/ws_echo.erl b/test/ws_SUITE_data/ws_echo.erl index c4ab406..89ebcb6 100644 --- a/test/ws_SUITE_data/ws_echo.erl +++ b/test/ws_SUITE_data/ws_echo.erl @@ -7,7 +7,7 @@ -export([websocket_info/3]). init(Req, _) -> - {ws, Req, undefined}. + {cowboy_websocket, Req, undefined}. websocket_handle({text, Data}, Req, State) -> {reply, {text, Data}, Req, State}; diff --git a/test/ws_SUITE_data/ws_echo_timer.erl b/test/ws_SUITE_data/ws_echo_timer.erl index 199f02c..a26c332 100644 --- a/test/ws_SUITE_data/ws_echo_timer.erl +++ b/test/ws_SUITE_data/ws_echo_timer.erl @@ -8,7 +8,7 @@ init(Req, _) -> erlang:start_timer(1000, self(), <<"websocket_init">>), - {ws, Req, undefined}. + {cowboy_websocket, Req, undefined}. websocket_handle({text, Data}, Req, State) -> {reply, {text, Data}, Req, State}; diff --git a/test/ws_SUITE_data/ws_init_shutdown.erl b/test/ws_SUITE_data/ws_init_shutdown.erl index 68f96f0..7bce03b 100644 --- a/test/ws_SUITE_data/ws_init_shutdown.erl +++ b/test/ws_SUITE_data/ws_init_shutdown.erl @@ -4,5 +4,5 @@ -export([init/2]). -init(Req, _) -> - {shutdown, cowboy_req:reply(403, Req), undefined}. +init(Req, Opts) -> + {ok, cowboy_req:reply(403, Req), Opts}. diff --git a/test/ws_SUITE_data/ws_send_many.erl b/test/ws_SUITE_data/ws_send_many.erl index 2da82c3..6585ffa 100644 --- a/test/ws_SUITE_data/ws_send_many.erl +++ b/test/ws_SUITE_data/ws_send_many.erl @@ -8,7 +8,7 @@ init(Req, Opts) -> erlang:send_after(10, self(), send_many), - {ws, Req, Opts}. + {cowboy_websocket, Req, Opts}. websocket_handle(_Frame, Req, State) -> {ok, Req, State}. diff --git a/test/ws_SUITE_data/ws_timeout_cancel.erl b/test/ws_SUITE_data/ws_timeout_cancel.erl index 6fcfc43..7376140 100644 --- a/test/ws_SUITE_data/ws_timeout_cancel.erl +++ b/test/ws_SUITE_data/ws_timeout_cancel.erl @@ -8,7 +8,7 @@ init(Req, _) -> erlang:start_timer(500, self(), should_not_cancel_timer), - {ws, Req, undefined, 1000}. + {cowboy_websocket, Req, undefined, 1000}. websocket_handle({text, Data}, Req, State) -> {reply, {text, Data}, Req, State}; diff --git a/test/ws_SUITE_data/ws_timeout_hibernate.erl b/test/ws_SUITE_data/ws_timeout_hibernate.erl index da901d7..15cde66 100644 --- a/test/ws_SUITE_data/ws_timeout_hibernate.erl +++ b/test/ws_SUITE_data/ws_timeout_hibernate.erl @@ -7,7 +7,7 @@ -export([websocket_info/3]). init(Req, _) -> - {ws, Req, undefined, 1000, hibernate}. + {cowboy_websocket, Req, undefined, 1000, hibernate}. websocket_handle(_Frame, Req, State) -> {ok, Req, State, hibernate}. -- cgit v1.2.3