ws_handlers.asciidoc 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. [[ws_handlers]]
  2. == Websocket handlers
  3. Websocket handlers provide an interface for upgrading HTTP/1.1
  4. connections to Websocket and sending or receiving frames on
  5. the Websocket connection.
  6. As Websocket connections are established through the HTTP/1.1
  7. upgrade mechanism, Websocket handlers need to be able to first
  8. receive the HTTP request for the upgrade, before switching to
  9. Websocket and taking over the connection. They can then receive
  10. or send Websocket frames, handle incoming Erlang messages or
  11. close the connection.
  12. === Upgrade
  13. The `init/2` callback is called when the request is received.
  14. To establish a Websocket connection, you must switch to the
  15. `cowboy_websocket` module:
  16. [source,erlang]
  17. ----
  18. init(Req, State) ->
  19. {cowboy_websocket, Req, State}.
  20. ----
  21. Cowboy will perform the Websocket handshake immediately. Note
  22. that the handshake will fail if the client did not request an
  23. upgrade to Websocket.
  24. The Req object becomes unavailable after this function returns.
  25. Any information required for proper execution of the Websocket
  26. handler must be saved in the state.
  27. === Subprotocol
  28. The client may provide a list of Websocket subprotocols it
  29. supports in the sec-websocket-protocol header. The server *must*
  30. select one of them and send it back to the client or the
  31. handshake will fail.
  32. For example, a client could understand both STOMP and MQTT over
  33. Websocket, and provide the header:
  34. ----
  35. sec-websocket-protocol: v12.stomp, mqtt
  36. ----
  37. If the server only understands MQTT it can return:
  38. ----
  39. sec-websocket-protocol: mqtt
  40. ----
  41. This selection must be done in `init/2`. An example usage could
  42. be:
  43. [source,erlang]
  44. ----
  45. init(Req0, State) ->
  46. case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req0) of
  47. undefined ->
  48. {cowboy_websocket, Req0, State};
  49. Subprotocols ->
  50. case lists:keymember(<<"mqtt">>, 1, Subprotocols) of
  51. true ->
  52. Req = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>,
  53. <<"mqtt">>, Req0),
  54. {cowboy_websocket, Req, State};
  55. false ->
  56. Req = cowboy_req:reply(400, Req0),
  57. {ok, Req, State}
  58. end
  59. end.
  60. ----
  61. === Post-upgrade initialization
  62. Cowboy has separate processes for handling the connection
  63. and requests. Because Websocket takes over the connection,
  64. the Websocket protocol handling occurs in a different
  65. process than the request handling.
  66. This is reflected in the different callbacks Websocket
  67. handlers have. The `init/2` callback is called from the
  68. temporary request process and the `websocket_` callbacks
  69. from the connection process.
  70. This means that some initialization cannot be done from
  71. `init/2`. Anything that would require the current pid,
  72. or be tied to the current pid, will not work as intended.
  73. The optional `websocket_init/1` can be used instead:
  74. [source,erlang]
  75. ----
  76. websocket_init(State) ->
  77. erlang:start_timer(1000, self(), <<"Hello!">>),
  78. {ok, State}.
  79. ----
  80. All Websocket callbacks share the same return values. This
  81. means that we can send frames to the client right after
  82. the upgrade:
  83. [source,erlang]
  84. ----
  85. websocket_init(State) ->
  86. {reply, {text, <<"Hello!">>}, State}.
  87. ----
  88. === Receiving frames
  89. Cowboy will call `websocket_handle/2` whenever a text, binary,
  90. ping or pong frame arrives from the client.
  91. The handler can handle or ignore the frames. It can also
  92. send frames back to the client or stop the connection.
  93. The following snippet echoes back any text frame received and
  94. ignores all others:
  95. [source,erlang]
  96. ----
  97. websocket_handle(Frame = {text, _}, State) ->
  98. {reply, Frame, State};
  99. websocket_handle(_Frame, State) ->
  100. {ok, State}.
  101. ----
  102. Note that ping and pong frames require no action from the
  103. handler as Cowboy will automatically reply to ping frames.
  104. They are provided for informative purposes only.
  105. === Receiving Erlang messages
  106. Cowboy will call `websocket_info/2` whenever an Erlang message
  107. arrives.
  108. The handler can handle or ignore the messages. It can also
  109. send frames to the client or stop the connection.
  110. The following snippet forwards log messages to the client
  111. and ignores all others:
  112. [source,erlang]
  113. ----
  114. websocket_info({log, Text}, State) ->
  115. {reply, {text, Text}, State};
  116. websocket_info(_Info, State) ->
  117. {ok, State}.
  118. ----
  119. === Sending frames
  120. // @todo This will be deprecated and eventually replaced with a
  121. // {Commands, State} interface that allows providing more
  122. // functionality easily.
  123. All `websocket_` callbacks share return values. They may
  124. send zero, one or many frames to the client.
  125. To send nothing, just return an ok tuple:
  126. [source,erlang]
  127. ----
  128. websocket_info(_Info, State) ->
  129. {ok, State}.
  130. ----
  131. To send one frame, return a reply tuple with the frame to send:
  132. [source,erlang]
  133. ----
  134. websocket_info(_Info, State) ->
  135. {reply, {text, <<"Hello!">>}, State}.
  136. ----
  137. You can send frames of any type: text, binary, ping, pong
  138. or close frames.
  139. To send many frames at once, return a reply tuple with the
  140. list of frames to send:
  141. [source,erlang]
  142. ----
  143. websocket_info(_Info, State) ->
  144. {reply, [
  145. {text, "Hello"},
  146. {text, <<"world!">>},
  147. {binary, <<0:8000>>}
  148. ], State}.
  149. ----
  150. They are sent in the given order.
  151. === Keeping the connection alive
  152. Cowboy will automatically respond to ping frames sent by
  153. the client. They are still forwarded to the handler for
  154. informative purposes, but no further action is required.
  155. Cowboy does not send ping frames itself. The handler can
  156. do it if required. A better solution in most cases is to
  157. let the client handle pings. Doing it from the handler
  158. would imply having an additional timer per connection and
  159. this can be a considerable cost for servers that need to
  160. handle large numbers of connections.
  161. Cowboy can be configured to close idle connections
  162. automatically. It is highly recommended to configure
  163. a timeout here, to avoid having processes linger longer
  164. than needed.
  165. The `init/2` callback can set the timeout to be used
  166. for the connection. For example, this would make Cowboy
  167. close connections idle for more than 30 seconds:
  168. [source,erlang]
  169. ----
  170. init(Req, State) ->
  171. {cowboy_websocket, Req, State, #{
  172. idle_timeout => 30000}}.
  173. ----
  174. This value cannot be changed once it is set. It defaults to
  175. `60000`.
  176. === Saving memory
  177. The Websocket connection process can be set to hibernate
  178. after the callback returns.
  179. Simply add an `hibernate` field to the ok or reply tuples:
  180. [source,erlang]
  181. ----
  182. websocket_init(State) ->
  183. {ok, State, hibernate}.
  184. websocket_handle(_Frame, State) ->
  185. {ok, State, hibernate}.
  186. websocket_info(_Info, State) ->
  187. {reply, {text, <<"Hello!">>}, State, hibernate}.
  188. ----
  189. It is highly recommended to write your handlers with
  190. hibernate enabled, as this allows to greatly reduce the
  191. memory usage. Do note however that an increase in the
  192. CPU usage or latency can be observed instead, in particular
  193. for the more busy connections.
  194. === Closing the connection
  195. The connection can be closed at any time, either by telling
  196. Cowboy to stop it or by sending a close frame.
  197. To tell Cowboy to close the connection, use a stop tuple:
  198. [source,erlang]
  199. ----
  200. websocket_info(_Info, State) ->
  201. {stop, State}.
  202. ----
  203. Sending a `close` frame will immediately initiate the closing
  204. of the Websocket connection. Note that when sending a list of
  205. frames that include a close frame, any frame found after the
  206. close frame will not be sent.
  207. The following example sends a close frame with a reason message:
  208. [source,erlang]
  209. ----
  210. websocket_info(_Info, State) ->
  211. {reply, {close, 1000, <<"some-reason">>}, State}.
  212. ----