Cowboy, the popular Erlang web server, has deprecated middleware and replaced the concept with stream handlers, a more flexible, but more complicated API. This page contains a documented reference implementation of a stream handler, to help others when developing their stream handlers in the future.
How Stream Handlers Work
Stream handlers are daisy-chained such that each callback in the first handler
invokes the same callback in the second, which invokes the next, which invokes
the next, etc. This recursion is handled in the cowboy_stream module where the
functions call the callbacks in the cowboy_stream behavior.
Protocol handlers invoke the stream handler chain for a new stream by calling
cowboy_stream:init/3, which calls the init/3 callback in the first stream
handler. The callback returns {Commands, State} where Commands is a list
of commands to be executed by Cowboy and State is a "State" object created
for the stream handler. cowboy_stream:init/3 returns {Commands, {Module, State}
where Module is a reference to the stream handler.
Here is an example of this recursion with two stream handlers:
Protocol Handler
cowboy_stream:init() -> {Commands, {handler1, Handler1State}}
handler1:init() -> {Commands, Handler1State}
cowboy_stream:init() -> {Commands, {handler2, Handler2State}}
handler2:init() -> {Commands, Handler2State}
cowboy_stream:init() -> {[], undefined}The data/4, info/3, and terminate/3 callbacks in the cowboy_stream
behavior take the State parameter value created by the init/3 callback and
perform the same recursion as described, above. But,
the corresponding cowboy_stream module functions take {Module, State} as the
State parameter and invoke the callback in Module with it's associated State.
This reference implementation uses a standard convention where each callback
stores the {Module, State}, for the next handler callback, in the state
returned from the current callback as state#{next => {Module, State}}.
Thus, each callback passes this value to the cowboy_stream function so it can
invoke the next handler callback.
Here is an example of this recursion for the data/4 callback:
Protocol Handler
cowboy_stream:data(..., {handler1, Handler1State#{next => {handler2, Handler2State}}})
handler1:data(..., State = #{next => {handler2, Handler2State}})
cowboy_stream:data(..., {handler2, Handler2State#{next => undefined}})
handler2:data(..., State = #{next => undefined})
cowboy_stream:data(..., undefined)