ws_handlers.ezdoc 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. ::: Handling Websocket connections
  2. A special handler is required for handling Websocket connections.
  3. Websocket handlers allow you to initialize the connection,
  4. handle incoming frames from the socket, handle incoming Erlang
  5. messages and then clean up on termination.
  6. Websocket handlers essentially act as a bridge between the client
  7. and the Erlang system. They will typically do little more than
  8. socket communication and decoding/encoding of frames.
  9. :: Initialization
  10. First, the `init/2` callback is called. This callback is common
  11. to all handlers. To establish a Websocket connection, this function
  12. must return a `ws` tuple.
  13. ``` erlang
  14. init(Req, _Opts) ->
  15. {cowboy_websocket, Req, #state{}}.
  16. ```
  17. Upon receiving this tuple, Cowboy will switch to the code
  18. that handles Websocket connections and perform the handshake
  19. immediately.
  20. If the sec-websocket-protocol header was sent with the request
  21. for establishing a Websocket connection, then the Websocket
  22. handler *must* select one of these subprotocol and send it
  23. back to the client, otherwise the client might decide to close
  24. the connection, assuming no correct subprotocol was found.
  25. ``` erlang
  26. init(Req, _Opts) ->
  27. case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of
  28. undefined ->
  29. {ok, Req, #state{}};
  30. Subprotocols ->
  31. case lists:keymember(<<"mychat2">>, 1, Subprotocols) of
  32. true ->
  33. Req2 = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>,
  34. <<"mychat2">>, Req),
  35. {ok, Req2, #state{}};
  36. false ->
  37. {shutdown, Req, undefined}
  38. end
  39. end.
  40. ```
  41. It is not recommended to wait too long inside the `init/2`
  42. function. Any extra initialization may be done after returning by
  43. sending yourself a message before doing anything. Any message sent
  44. to `self()` from `init/2` is guaranteed to arrive before
  45. any frames from the client.
  46. It is also very easy to ensure that this message arrives before
  47. any message from other processes by sending it before registering
  48. or enabling timers.
  49. ``` erlang
  50. init(Req, _Opts) ->
  51. self() ! post_init,
  52. %% Register process here...
  53. {cowboy_websocket, Req, #state{}}.
  54. websocket_info(post_init, Req, State) ->
  55. %% Perform post_init initialization here...
  56. {ok, Req, State}.
  57. ```
  58. :: Handling frames from the client
  59. Cowboy will call `websocket_handle/3` whenever a text, binary,
  60. ping or pong frame arrives from the client. Note that in the
  61. case of ping and pong frames, no action is expected as Cowboy
  62. automatically replies to ping frames.
  63. The handler can decide to send frames to the socket, shutdown
  64. or just continue without sending anything.
  65. The following snippet echoes back any text frame received and
  66. ignores all others.
  67. ``` erlang
  68. websocket_handle(Frame = {text, _}, Req, State) ->
  69. {reply, Frame, Req, State};
  70. websocket_handle(_Frame, Req, State) ->
  71. {ok, Req, State}.
  72. ```
  73. :: Handling Erlang messages
  74. Cowboy will call `websocket_info/3` whenever an Erlang message
  75. arrives.
  76. The handler can decide to send frames to the socket, shutdown
  77. or just continue without sending anything.
  78. The following snippet forwards any `log` message to the socket
  79. and ignores all others.
  80. ``` erlang
  81. websocket_info({log, Text}, Req, State) ->
  82. {reply, {text, Text}, Req, State};
  83. websocket_info(_Info, Req, State) ->
  84. {ok, Req, State}.
  85. ```
  86. :: Sending frames to the socket
  87. Cowboy allows sending either a single frame or a list of
  88. frames to the socket. Any frame can be sent: text, binary, ping,
  89. pong or close frames.
  90. The following example sends three frames using a single `reply`
  91. tuple.
  92. ``` erlang
  93. websocket_info(hello_world, Req, State) ->
  94. {reply, [
  95. {text, "Hello"},
  96. {text, <<"world!">>},
  97. {binary, <<0:8000>>}
  98. ], Req, State};
  99. %% More websocket_info/3 clauses here...
  100. ```
  101. Note that the payload for text and binary frames is of type
  102. `iodata()`, meaning it can be either a `binary()` or an
  103. `iolist()`.
  104. Sending a `close` frame will immediately initiate the closing
  105. of the Websocket connection. Be aware that any additional
  106. frames sent by the client or any Erlang messages waiting to
  107. be received will not be processed. Also note that when replying
  108. a list of frames that includes close, any frame found after the
  109. close frame will not be sent.
  110. :: Ping and timeout
  111. The biggest performance improvement you can do when dealing
  112. with a huge number of Websocket connections is to reduce the
  113. number of timers that are started on the server. A common use
  114. of timers when dealing with connections is for sending a ping
  115. every once in a while. This should be done exclusively on the
  116. client side. Indeed, a server handling one million Websocket
  117. connections will perform a lot better when it doesn't have to
  118. handle one million extra timers too!
  119. Cowboy will automatically respond to ping frames sent by the
  120. client. It will still forward the frame to the handler for
  121. informative purpose, but no further action is required.
  122. Cowboy can be configured to automatically close the Websocket
  123. connection when no data arrives on the socket. It is highly
  124. recommended to configure a timeout for it, as otherwise you
  125. may end up with zombie "half-connected" sockets that may
  126. leave the process alive forever.
  127. A good timeout value is 60 seconds.
  128. ``` erlang
  129. init(Req, _Opts) ->
  130. {cowboy_websocket, Req, #state{}, 60000}.
  131. ```
  132. This value cannot be changed once it is set. It defaults to
  133. `infinity`.
  134. :: Hibernate
  135. Most tuples returned from handler callbacks can include an
  136. extra value `hibernate`. After doing any necessary operations
  137. following the return of the callback, Cowboy will hibernate
  138. the process.
  139. It is highly recommended to hibernate processes that do not
  140. handle much traffic. It is a good idea to hibernate all
  141. connections by default and investigate only when you start
  142. noticing increased CPU usage.
  143. :: Supporting older browsers
  144. Unfortunately Websocket is a relatively recent technology,
  145. which means that not all browsers support it. A library like
  146. ^"Bullet^https://github.com/extend/bullet^ can be used to
  147. emulate Websocket connections on older browsers.