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

LH: I have added a test that does both hibernate and timeout and fixed a related issue. I also tweaked the docs and tests.
150 lines
4.4 KiB
Text
150 lines
4.4 KiB
Text
[[loop_handlers]]
|
|
== Loop handlers
|
|
|
|
Loop handlers are a special kind of HTTP handlers used when the
|
|
response can not be sent right away. The handler enters instead
|
|
a receive loop waiting for the right message before it can send
|
|
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.
|
|
|
|
Loop handlers can also be used for requests where a response is
|
|
partially available and you need to stream the response body
|
|
while the connection is open. The most known example of such
|
|
practice is server-sent events, but it also applies to any
|
|
response that takes a long time to send.
|
|
|
|
While the same can be accomplished using plain HTTP handlers,
|
|
it is recommended to use loop handlers because they are well-tested
|
|
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/2` and `terminate/3` callbacks which work the same as
|
|
for plain HTTP handlers.
|
|
|
|
=== Initialization
|
|
|
|
The `init/2` function must return a `cowboy_loop` tuple to enable
|
|
loop handler behavior. This tuple may optionally contain
|
|
the atom `hibernate` to make the process enter hibernation
|
|
until a message is received. Alternatively, the tuple may
|
|
optionally contain a positive integer to create a `timeout`
|
|
message when the process has not received messages for too
|
|
long.
|
|
|
|
This snippet enables the loop handler:
|
|
|
|
[source,erlang]
|
|
----
|
|
init(Req, State) ->
|
|
{cowboy_loop, Req, State}.
|
|
----
|
|
|
|
This also makes the process hibernate:
|
|
|
|
[source,erlang]
|
|
----
|
|
init(Req, State) ->
|
|
{cowboy_loop, Req, State, hibernate}.
|
|
----
|
|
|
|
This makes the process time out after 1000ms of idle time.
|
|
|
|
[source,erlang]
|
|
----
|
|
init(Req, State) ->
|
|
{cowboy_loop, Req, State, 1000}.
|
|
----
|
|
|
|
=== Receive loop
|
|
|
|
Once initialized, Cowboy will wait for messages to arrive
|
|
in the process' mailbox. When a message arrives, Cowboy
|
|
calls the `info/3` function with the message, the Req object
|
|
and the handler's state.
|
|
|
|
The following snippet sends a reply when it receives a
|
|
`reply` message from another process, or waits for another
|
|
message otherwise.
|
|
|
|
[source,erlang]
|
|
----
|
|
info({reply, Body}, Req, State) ->
|
|
cowboy_req:reply(200, #{}, Body, Req),
|
|
{stop, Req, State};
|
|
info(_Msg, Req, State) ->
|
|
{ok, Req, State, hibernate}.
|
|
----
|
|
|
|
Do note that the `reply` tuple here may be any message
|
|
and is simply an example.
|
|
|
|
This callback may perform any necessary operation including
|
|
sending all or parts of a reply, and will subsequently
|
|
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 `stop` tuple should be returned.
|
|
This will instruct Cowboy to end the request.
|
|
|
|
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/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
|
|
a chunk is sent every time an `event` message is received,
|
|
and the loop is stopped by sending an `eof` message.
|
|
|
|
[source,erlang]
|
|
----
|
|
init(Req, State) ->
|
|
Req2 = cowboy_req:stream_reply(200, Req),
|
|
{cowboy_loop, Req2, State}.
|
|
|
|
info(eof, Req, State) ->
|
|
{stop, Req, State};
|
|
info({event, Data}, Req, State) ->
|
|
cowboy_req:stream_body(Data, nofin, Req),
|
|
{ok, Req, State};
|
|
info(_Msg, Req, State) ->
|
|
{ok, Req, State}.
|
|
----
|
|
|
|
=== Cleaning up
|
|
|
|
Please refer to the xref:handlers[Handlers chapter]
|
|
for general instructions about cleaning up.
|
|
|
|
=== Hibernate
|
|
|
|
To save memory, you may hibernate the process in between
|
|
messages received. This is done by returning the atom
|
|
`hibernate` as part of the `loop` tuple callbacks normally
|
|
return. Just add the atom at the end and Cowboy will hibernate
|
|
accordingly.
|
|
|
|
=== Idle timeout
|
|
|
|
You may activate timeout events by returning a positive integer
|
|
`N` as part of the `loop` tuple callbacks return. The default
|
|
value is `infinity`. The `info` callback will be called with the
|
|
atom `timeout` unless a message is received within `N` milliseconds:
|
|
|
|
[source,erlang]
|
|
----
|
|
info(timeout, Req, State) ->
|
|
%% Do something...
|
|
{ok, Req, State, 1000}.
|
|
----
|