diff options
Diffstat (limited to 'guide/loop_handlers.md')
-rw-r--r-- | guide/loop_handlers.md | 142 |
1 files changed, 114 insertions, 28 deletions
diff --git a/guide/loop_handlers.md b/guide/loop_handlers.md index c3d1891..8689453 100644 --- a/guide/loop_handlers.md +++ b/guide/loop_handlers.md @@ -1,24 +1,11 @@ Loop handlers ============= -Purpose -------- - 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. -They are most useful when performing long-polling operations or -when using server-sent events. - -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. - -Usage ------ - 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 @@ -29,34 +16,133 @@ partially available and you need to stream the response body while the connection is open. The most known example of such practice is known as server-sent events. +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/3` and `terminate/3` callbacks which work the same as for plain HTTP handlers. -The following handler waits for a message `{reply, Body}` before -sending a response. If this message doesn't arrive within 60 -seconds, it gives up and a `204 No Content` will be replied. -It also hibernates the process to save memory while waiting for -this message. +Initialization +-------------- + +The `init/3` function must return a `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. + +This snippet enables the loop handler. + +``` erlang +init(_Type, Req, _Opts) -> + {loop, Req, undefined_state}. +``` + +However it is largely recommended that you set a timeout +value. The next example sets a timeout value of 30s and +also makes the process hibernate. ``` erlang --module(my_loop_handler). --behaviour(cowboy_loop_handler). +init(_Type, Req, _Opts) -> + {loop, Req, undefined_state, 30000, hibernate}. +``` --export([init/3]). --export([info/3]). --export([terminate/3]). +Receive loop +------------ -init({tcp, http}, Req, Opts) -> - {loop, Req, undefined_state, 60000, hibernate}. +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. + +``` erlang info({reply, Body}, Req, State) -> {ok, Req2} = cowboy_req:reply(200, [], Body, Req), {ok, Req2, State}; -info(Message, Req, State) -> +info(_Msg, Req, State) -> {loop, 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 `ok` tuple should be returned. +This will instruct Cowboy to end the request. + +Otherwise a `loop` 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` +every time a message is received. + +The following snippet does exactly that. As you can see +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) -> + {ok, Req2} = cowboy_req:chunked_reply(200, [], Req), + {loop, Req2, undefined_state}. -terminate(Reason, Req, State) -> - ok. +info(eof, Req, State) -> + {ok, Req, State}; +info({chunk, Chunk}, Req, State) -> + ok = cowboy_req:chunk(Chunk, Req), + {loop, Req, State}; +info(_Msg, Req, State) -> + {loop, Req, State}. ``` + +Cleaning up +----------- + +It is recommended that you set the connection header to +`close` when replying, as this process may be reused for +a subsequent request. + +Please refer to the [HTTP handlers chapter](http_handlers.md) +for general instructions about cleaning up. + +Timeout +------- + +By default Cowboy will not attempt to close the connection +if there is no activity from the client. This is not always +desirable, which is why you can set a timeout. Cowboy will +close the connection if no data was received from the client +after the configured time. The timeout only needs to be set +once and can't be modified afterwards. + +Because the request may have had a body, or may be followed +by another request, Cowboy is forced to buffer all data it +receives. This data may grow to become too large though, +so there is a configurable limit for it. The default buffer +size is of 5000 bytes, but it may be changed by setting the +`loop_max_buffer` middleware environment value. + +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. |