0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 20:30:23 +00:00

Unify the init and terminate callbacks

This set of changes is the first step to simplify the
writing of handlers, by removing some extraneous
callbacks and making others optional.

init/3 is now init/2, its first argument being removed.

rest_init/2 and rest_terminate/2 have been removed.

websocket_init/3 and websocket_terminate/3 have been removed.

terminate/3 is now optional. It is called regardless of
the type of handler, including rest and websocket.

The return value of init/2 changed. It now returns
{Mod, Req, Opts} with Mod being either one of the four
handler type or a custom module. It can also return extra
timeout and hibernate options.

The signature for sub protocols has changed, they now
receive these extra timeout and hibernate options.

Loop handlers are now implemented in cowboy_long_polling,
and will be renamed throughout the project in a future commit.
This commit is contained in:
Loïc Hoguin 2014-09-26 15:58:44 +03:00
parent fd37fad592
commit 5ce4c2bfb4
70 changed files with 668 additions and 1015 deletions

View file

@ -7,36 +7,17 @@ will simply call the three callbacks sequentially.
:: Initialization
The first callback, `init/3`, is common to all handlers,
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 `ok`.
HTTP handlers just return `http`.
``` erlang
init(_Type, Req, _Opts) ->
{ok, Req, no_state}.
init(Req, Opts) ->
{http, Req, Opts}.
```
This function receives the name of the transport and
protocol modules used for processing the request.
They can be used to quickly dismiss requests. For
example the following handler will crash when accessed
using TCP instead of SSL.
``` erlang
init({ssl, _}, Req, _Opts) ->
{ok, Req, no_state}.
```
This function also receives the options associated with
this route that you configured previously. If your
handler does not use options, then it is recommended
you match the value `[]` directly to quickly detect
configuration errors.
``` erlang
init(_Type, Req, []) ->
{ok, Req, no_state}.
```
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
@ -45,9 +26,9 @@ example, this will crash if the required `lang`
option is not found.
``` erlang
init(_Type, Req, Opts) ->
{_, _Lang} = lists:keyfind(lang, 1, Opts),
{ok, Req, no_state}.
init(Req, Opts) ->
{_, Lang} = lists:keyfind(lang, 1, Opts),
{http, Req, Lang}.
```
If your users are unlikely to figure out the issue
@ -58,15 +39,15 @@ continue with the handler code, so we use the
`shutdown` return value to stop early.
``` erlang
init(_Type, Req, Opts) ->
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, no_state};
_ ->
{ok, Req, no_state}
{shutdown, Req2, undefined};
{_, Lang} ->
{http, Req, Lang}
end.
```
@ -75,9 +56,9 @@ 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.
We recommend that you create a state record for this.
The record will make your handler code clearer and
will allow you to better use Dialyzer for type checking.
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, {
@ -85,15 +66,25 @@ will allow you to better use Dialyzer for type checking.
%% More fields here.
}).
init(_Type, Req, Opts) ->
init(Req, Opts) ->
{_, Lang} = lists:keyfind(lang, 1, Opts),
{ok, Req, #state{lang=Lang}}.
{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, wait for it, handle the request.
handlers. It's where you handle the request.
A handle function that does nothing would look like this:
@ -118,8 +109,7 @@ handle(Req, State) ->
:: Cleaning up
The third and last callback, `terminate/3`, will most likely
be empty in your handler.
The third and last callback, `terminate/3`, is optional.
``` erlang
terminate(_Reason, Req, State) ->
@ -140,6 +130,3 @@ 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.
This function is still available should you need it. It will
always be called.

View file

@ -34,8 +34,8 @@ process enter hibernation until a message is received.
This snippet enables the loop handler.
``` erlang
init(_Type, Req, _Opts) ->
{loop, Req, undefined_state}.
init(Req, Opts) ->
{long_polling, Req, Opts}.
```
However it is largely recommended that you set a timeout
@ -43,8 +43,8 @@ value. The next example sets a timeout value of 30s and
also makes the process hibernate.
``` erlang
init(_Type, Req, _Opts) ->
{loop, Req, undefined_state, 30000, hibernate}.
init(Req, Opts) ->
{long_polling, Req, Opts, 30000, hibernate}.
```
:: Receive loop
@ -94,9 +94,9 @@ a chunk is sent every time a `chunk` message is received,
and the loop is stopped by sending an `eof` message.
``` erlang
init(_Type, Req, _Opts) ->
init(Req, Opts) ->
Req2 = cowboy_req:chunked_reply(200, [], Req),
{loop, Req2, undefined_state}.
{long_polling, Req2, Opts}.
info(eof, Req, State) ->
{ok, Req, State};

View file

@ -267,17 +267,3 @@ rather the one of the machine that connected to the server.
``` erlang
{IP, Port} = cowboy_req:peer(Req).
```
:: Reducing the memory footprint
When you are done reading information from the request object
and know you are not going to access it anymore, for example
when using long-polling or Websocket, you can use the `compact/1`
function to remove most of the data from the request object and
free memory.
``` erlang
Req2 = cowboy_req:compact(Req).
```
You will still be able to send a reply if needed.

View file

@ -8,18 +8,20 @@ The REST handler is the recommended way to handle requests.
:: Initialization
First, the `init/3` callback is called. This callback is common
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 an `upgrade` tuple.
must return a `rest` tuple.
``` erlang
init({tcp, http}, Req, Opts) ->
{upgrade, protocol, cowboy_rest}.
init(Req, Opts) ->
{rest, Req, Opts}.
```
Cowboy will then switch to the REST protocol and start executing
the state machine, starting from `rest_init/2` if it's defined,
and ending with `rest_terminate/2` also if defined.
the state machine.
After reaching the end of the flowchart, the `terminate/3` callback
will be called if it is defined.
:: Methods
@ -36,28 +38,17 @@ on what other defined callbacks return. The various flowcharts
in the next chapter should be a useful to determine which callbacks
you need.
When the request starts being processed, Cowboy will call the
`rest_init/2` function if it is defined, with the Req object
and the handler options as arguments. This function must return
`{ok, Req, State}` where `State` is the handler's state that all
subsequent callbacks will receive.
All callbacks take two arguments, the Req object and the State,
and return a three-element tuple of the form `{Value, Req, State}`.
At the end of every request, the special callback `rest_terminate/2`
will be called if it is defined. It cannot be used to send a reply,
and must always return `ok`.
All other callbacks are resource callbacks. They all take two
arguments, the Req object and the State, and return a three-element
tuple of the form `{Value, Req, State}`.
All callbacks can also return `{halt, Req, State}` to stop execution
of the request.
The following table summarizes the callbacks and their default values.
If the callback isn't defined, then the default value will be used.
Please look at the flowcharts to find out the result of each return
value.
All callbacks can also return `{halt, Req, State}` to stop execution
of the request, at which point `rest_terminate/2` will be called.
In the following table, "skip" means the callback is entirely skipped
if it is undefined, moving directly to the next step. Similarly,
"none" means there is no default value for this callback.

View file

@ -1,36 +1,61 @@
::: Protocol upgrades
Cowboy features many different handlers, each for different purposes.
All handlers have a common entry point: the `init/3` function.
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.
The default handler type is the simple HTTP handler.
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.
To switch to a different protocol, you must perform a protocol
upgrade. This is what is done for Websocket and REST and is
explained in details in the respective chapters.
The following table lists the built-in handler types.
You can also create your own protocol on top of Cowboy and use
the protocol upgrade mechanism to switch to it.
|| 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
For example, if you create the `my_protocol` module implementing
the `cowboy_sub_protocol` behavior, then you can upgrade to it
by simply returning the module name from `init/3`.
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(_, _, _Opts) ->
{upgrade, protocol, my_protocol}.
init(Req, Opts) ->
{my_protocol, Req, Opts}.
```
The `cowboy_sub_protocol` behavior only requires one callback,
`upgrade/4`. It receives the Req object, the middleware environment,
and the handler and options for this request. This is the same
module as the `init/3` function and the same options that were
passed to it.
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
upgrade(Req, Env, Handler, HandlerOpts) ->
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.

View file

@ -11,53 +11,18 @@ socket communication and decoding/encoding of frames.
:: Initialization
First, the `init/3` callback is called. This callback is common
First, the `init/2` callback is called. This callback is common
to all handlers. To establish a Websocket connection, this function
must return an `upgrade` tuple.
must return a `ws` tuple.
``` erlang
init(_, Req, Opts) ->
{upgrade, protocol, cowboy_websocket}.
```
It is also possible to return an update Req object and options
using the longer form of this tuple.
``` erlang
init(_Type, Req, Opts) ->
{upgrade, protocol, cowboy_websocket, Req, Opts}.
init(Req, Opts) ->
{ws, Req, Opts}.
```
Upon receiving this tuple, Cowboy will switch to the code
that handles Websocket connections. It does not immediately
perform the handshake however. First, it calls the `websocket_init/3`
callback.
This function must be used to initialize the state, and can
also be used to register the process, start a timer, etc.
As long as the function returns an `ok` tuple, then Cowboy
performs the Websocket handshake.
``` erlang
websocket_init(_Type, Req, _Opts) ->
{ok, Req, #state{}}.
```
A `shutdown` tuple can be returned to refuse to perform the
handshake. When doing so, Cowboy will send a `400 Bad Request`
response to the client and close the connection.
``` erlang
websocket_init(_Type, Req, _Opts) ->
{shutdown, Req}.
```
It is also possible to perform a `cowboy_req:reply/{2,3,4}`
before returning a `shutdown` tuple, allowing you to override
the response sent back to the client.
Note that browser support for handling Websocket connection
failures may vary.
that handles Websocket connections and perform the handshake
immediately.
If the sec-websocket-protocol header was sent with the request
for establishing a Websocket connection, then the Websocket
@ -66,7 +31,7 @@ back to the client, otherwise the client might decide to close
the connection, assuming no correct subprotocol was found.
``` erlang
websocket_init(_Type, Req, _Opts) ->
init(Req, _Opts) ->
case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of
undefined ->
{ok, Req, #state{}};
@ -77,15 +42,15 @@ websocket_init(_Type, Req, _Opts) ->
<<"mychat2">>, Req),
{ok, Req2, #state{}};
false ->
{shutdown, Req}
{shutdown, Req, undefined}
end
end.
```
It is not recommended to wait too long inside the `websocket_init/3`
It is not recommended to wait too long inside the `init/2`
function. Any extra initialization may be done after returning by
sending yourself a message before doing anything. Any message sent
to `self()` from `websocket_init/3` is guaranteed to arrive before
to `self()` from `init/2` is guaranteed to arrive before
any frames from the client.
It is also very easy to ensure that this message arrives before
@ -93,10 +58,10 @@ any message from other processes by sending it before registering
or enabling timers.
``` erlang
websocket_init(_Type, Req, _Opts) ->
init(Req, _Opts) ->
self() ! post_init,
%% Register process here...
{ok, Req, #state{}}.
{ws, Req, #state{}}.
websocket_info(post_init, Req, State) ->
%% Perform post_init initialization here...
@ -195,8 +160,8 @@ leave the process alive forever.
A good timeout value is 60 seconds.
``` erlang
websocket_init(_Type, Req, _Opts) ->
{ok, Req, #state{}, 60000}.
init(Req, _Opts) ->
{ws, Req, #state{}, 60000}.
```
This value cannot be changed once it is set. It defaults to