-
Notifications
You must be signed in to change notification settings - Fork 0
/
websockets.html
663 lines (602 loc) · 23.9 KB
/
websockets.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta name="keywords" content="Yaws"/>
<title>Yaws</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" href="stil.css"/>
<link rel="shortcut icon" href="/icons/yaws_y.gif" type="image/x-icon"/>
</head>
<body>
<div class="logo">
<img src="icons/yaws_head.gif" width="600" alt="YAWS"/>
</div>
<div id="sidebar">
<h4> Yaws </h4>
<div class=""> <a href="index.html" id="index" >Top Page</a> </div>
<div class=""> <a href="configuration.html" id="configuration">Build Config and Run</a></div>
<div class=""> <a href="dynamic.html" id="dynamic" >Dynamic Content</a> </div>
<div class=""> <a href="https://github.com/erlyaws/yaws/releases/" id="download">Download </a> </div>
<div class=""> <a href="contact.html" id="contact">Contact </a> </div>
<div class=""> <a href="doc.html" id="doc">Documentation</a> </div>
<div class=""> <a href="articles.html" id="resources">Articles</a> </div>
<h4> Examples </h4>
<div class=""> <a href="/json_intro.html">AJAX/JSON RPC</a></div>
<div class=""> <a href="/appmods.html">Appmods</a> </div>
<div class=""> <a href="/arg.html">Arg</a> </div>
<div class=""> <a href="/privbind.html">Binding to Privileged Ports</a></div>
<div class=""> <a href="/bindings.html">Bindings</a> </div>
<div class=""> <a href="/cgi.html">CGI</a></div>
<div class=""> <a href="/session.html">Cookie Sessions</a> </div>
<div class=""> <a href="/cookies.html">Cookies</a> </div>
<div class=""> <a href="/dynamic.html">Dynamic Content</a> </div>
<div class=""> <a href="/embed.html">Embedding Yaws</a></div>
<div class=""> <a href="/upload0.html">File Upload</a> </div>
<div class=""> <a href="/form.html">Forms</a> </div>
<div class=""> <a href="/haxe_intro.html">haXe Remoting</a></div>
<div class=""> <a href="/pcookie.html">Persistent Cookies</a> </div>
<div class=""> <a href="/query.html">Query Part of URL</a></div>
<div class=""> <a href="/rebar_release.html">Rebar Releases</a></div>
<div class=""> <a href="/redirect.html">Redirect</a> </div>
<div class=""> <a href="/server_sent_events.html">Server-Sent Events</a> </div>
<div class=""> <a href="/ssi.html">Server Side Includes</a> </div>
<div class=""> <a href="/simple.html">Simple</a> </div>
<div class=""> <a href="/soap_intro.html">SOAP with Yaws</a></div>
<div class=""> <a href="/stream.html">Streaming Data</a> </div>
<div class="choosen"> <a href="/websockets.html">Web Sockets</a> </div>
<a href="/shoppingcart/index.html">Tiny Shopping Cart</a>
<div class=""> <a href="/yapp_intro.html">Yaws Applications (yapps)</a></div>
<div class=""> <a href="/logger_mod.html">Write Your Own Logger</a></div>
<h4> Misc </h4>
<div class=""> <a href="/internals.html">Internals</a> </div>
</div>
<div id="entry">
<h1>WebSockets in Yaws</h1>
<p>
WebSockets! The new kid in town! Joe
<a href="https://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html">loves it</a>,
maybe you should too?
</p>
<p>
WebSockets allow for *real* two-way communication between the browser and
Yaws without the overhead and latency that come with polling/long-polling
solutions. That should be enough for an introduction. Now... how to use it?
</p>
<h3>A simple example</h3>
<p>
First of all, here is a simple example. It shows how to upgrade connections
from HTTP to WebSocket.
</p>
<div class="box">
<pre>
<erl>
out(A) ->
%% To use the extended version of the basic echo callback, add
%% 'extversion=true' in the query string.
CallbackMod = case queryvar(A, "extversion") of
{ok, "true"} -> basic_echo_callback_extended;
_ -> basic_echo_callback
end,
%% To enable keepalive timer add 'keepalive=true' in the query string.
KeepAlive = case queryvar(A, "keepalive") of
{ok, "true"} -> true;
_ -> false
end,
%% To define a keepalive timeout value, add 'timeout=Int' in the query
%% string.
Tout = case queryvar(A, "timeout") of
{ok, Val} ->
try
list_to_integer(Val)
catch
_:_ -> infinity
end;
_ ->
infinity
end,
%% To drop connection when a timeout occured, add 'drop=true' in the query
%% string.
Drop = case queryvar(A, "drop") of
{ok, "true"} -> true;
_ -> false
end,
%% To reject unmasked frames , add 'close_unmasked=true' in the query
%% string.
CloseUnmasked = case queryvar(A, "close_unmasked") of
{ok, "true"} -> true;
_ -> false
end,
%% NOTE: change the line below to
%% Opts = [{origin, any}],
%% if you want to accept calls from any origin.
Opts = [
{origin, "http://" ++ (A#arg.headers)#headers.host},
{keepalive, KeepAlive},
{keepalive_timeout, Tout},
{drop_on_timeout, Drop},
{close_if_unmasked, CloseUnmasked}
],
{websocket, CallbackMod, Opts}.
</erl>
</pre>
</div>
<p>
The above code can be executed <a href="websockets_example.html">Here</a>.
</p>
<h3>Establish a WebSocket connection</h3>
<p>
To establish a WebSocket connection, a client must send a valid HTTP
Upgrade request. Then, from the server side, the Yaws script (or the appmod
or whatever) should return:
</p>
<div class="box">
<pre>
{websocket, CallbackMod, Options}
</pre>
</div>
<p>
where <code>CallbackMod</code> is an atom identifying the WebSocket callback
module, and <code>Options</code> is a (possibly empty) list (see below for
details).
</p>
<p>
From here, Yaws spawns an Erlang process to manage the WebSocket
connection. Once the handshake response is returned by Yaws, the connection
is established and the handling process is ready to send or receive data. If
something goes wrong during this step, Yaws returns an HTTP error (400, 403
or 500 depending of the error type).
</p>
<h4>Supported Options</h4>
<p>
The following options are available:
<ul>
<li>
<code>{callback, CallbackType}</code>
<p>
Specify the type of the callback module. <code>CallbackType</code> can
be either of the following:
<ul>
<li style="margin-bottom: 10px;">
<code>basic</code> - Same as <code>{basic, []}</code>. This is the
default.
</li>
<li style="margin-bottom: 10px;">
<code>{basic, InitialState}</code> - Indicate your callback module
is a basic callback module. <code>InitialState</code> is the
callback's initial state for handling this client.
</li>
<li style="margin-bottom: 10px;">
<code>{advanced, InitialState}</code> - Same as above but for an
advanced callback module.
</li>
</ul>
</p>
</li>
<li>
<code>{origin, Origin}</code>
<p>
Specify the <code>Origin</code> URL from which messages will be
accepted. This is useful for protecting against cross-site attack. The
option defaults to <code>any</code>, meaning calls will be accepted
from any origin.
</p>
</li>
<li>
<code>{keepalive, KeepAliveBoolean}</code>
<p>
If true, Yaws will automatically send a ping message
every <code>keepAliveTimeout</code> milliseconds. By default keepalive
pings are disabled.
</p>
</li>
<li>
<code>{keepalive_timeout, keepAliveTimeout}</code>
<p>
Specify the interval in milliseconds to send keepalive pings, by
default 30000. Ignored if <code>KeepAliveBoolean</code> is false.
</p>
</li>
<li>
<code>{keepalive_grace_period, KeepAliveGracePeriod}</code>
<p>
Specify the amount of time, in milliseconds, to wait after sending a
keepalive ping. If no message is received
within <code>KeepAliveGracePeriod</code> milliseconds, a timeout will
occur. Depending on the <code>DropBoolean</code> value, a close frame
is sent with the status code 1006 (if <code>DropBoolean</code> is
true) or the callback module is notified
(see <code>Module:handle_info/2</code> below). <br/> By
default, <code>KeepAliveGracePeriod</code> is set to 2000. Ignored
if <code>KeepAliveBoolean</code> is false.
</p>
</li>
<li>
<code>{drop_on_timeout, DropBoolean}</code>
<p>
If true, a close frame is sent with the status code 1006 when a
timeout occurs after a keepalive ping has been sent
(see <code>KeepAliveGracePeriod</code>). Disabled by default. Ignored
if <code>KeepAliveBoolean</code> is false.
</p>
</li>
<li>
<code>{close_timeout, CloseTimeout}</code>
<p>
After sending a close frame to a client, Yaws will wait for the client
acknowledgement for <code>CloseTimeout</code> milliseconds. Then it
will close the underlying TCP connection. By
default <code>CloseTimeout</code> is set to 5000.
</p>
</li>
<li>
<code>{close_if_unmasked, CloseUnmaskedBoolean}</code>
<p>
If true, Yaws will reject any unmasked incoming frame by sending a
close frame with the status code 1002. Disabled by default.<br/>
Note: According to RFC 6455, a client must mask all frames that it
sends to the server
(See <a href="https://tools.ietf.org/html/rfc6455#section-5.1">RFC
6455 - Section 5.1</a>).
</p>
</li>
<li>
<code>{max_frame_size, MaxFrameSize}</code>
<p>
Specify the maximum allowed size, in bytes, for received frames. By
default 16MB. It is also the maximum size for unfragmented
messages.<br/> This limit is checked for all types of callback module.
</p>
</li>
<li>
<code>{max_message_size, MaxMsgSize}</code>
<p>
Specify the maximum allowed message size in bytes, by default
16MB.<br/> This limit is checked only for basic callback modules.
</p>
</li>
<li>
<code>{auto_fragment_message, AutoFragBoolean}</code>
<p>
If true, outgoing messages will be automatically fragmented if their
payload exceeds <code>OutFragSize</code> bytes. This flag is set to
false by default.
</p>
</li>
<li>
<code>{auto_fragment_threshold, OutFragSize}</code>
<p>
Specify the maximum payload size of each fragment
if <code>AutoFragBoolean</code> is true. <code>OutFragSize</code> is
set to 1MB by default. Ignored is <code>AutoFragBoolean</code> is
false.
</p>
</li>
</ul>
</p>
<h3>WebSocket callback modules</h3>
<p>
All frames received on a WebSocket connection are passed to the callback
modules specified during the connection establishment by
calling <code>Module:handle_message/1</code>
or <code>Module:handle_message/2</code>, depending on whether it’s a basic
or an advanced callback module.
</p>
<h4>Basic callback modules</h4>
<p>
When a basic callback module is used, the messages defragmentation is
handled by Yaws. From the callback module point of view, all incoming
messages are unfragmented. This implies that fragmented frames will be
accumulated, thus basic callback modules does not support data streaming.
</p>
<p>
A basic callback module <strong>MUST</strong> define the stateless
function <code>Module:handle_message/1</code>:
</p>
<blockquote>
<strong>Module:handle_message(Message) -> Result</strong>
<pre>
Message :: {Type, Data} | {close, Status, Reason}
Result :: noreply | {reply, Reply} | {close, CloseReason}
Type :: text | binary
Data :: binary()
Reply :: {Type, Data} | #ws_frame{} |
[{Type, Data}] | [#ws_frame{}]
CloseReason :: Status | {Status, Reason}
Status :: integer() %% RFC 6455 Status Code
Reason :: binary()
</pre>
<p>
This function is called when a message is received. <code>{text,
Data}</code> (or <code>{binary, Data}</code>) is the unfragmented text (or
binary) message. When the client closes the connection, the callback
module is notified with the message <code>{close, Status, Reason}</code>,
where <code>Status</code> is the numerical status code sent by the client
or the value 1000
(see <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455 -
Section 7.4.1</a>) if the client sent no status code. For an abnormal
client closure, the status code is 1006 (as specified
by <a href="https://tools.ietf.org/html/rfc6455#section-7.1.5">RFC 6455 -
Section 7.1.5</a>). <code>Reason</code> is a binary containing any text
the client sent to indicate the reason for closing the socket; this binary
may be empty.
</p>
<p>
If the function returns <code>{reply, Reply}</code>, <code>Reply</code> is
sent to the client. It is possible to send one or more unfragmentated
messages by returning <code>{Type, Data}</code> or <code>[{Type,
Data}]</code>. It is also possible to send one or more frames
using the <code>#ws_frame{}</code> record instead, defined
in <code>include/yaws_api.hrl</code> (useful to fragment messages by
hand).
</p>
<p>
If the function returns <code>noreply</code>, nothing happens.
</p>
<p>
If the function returns <code>{close, CloseReason}</code>, the handling
process closes the connection sending a close control frame to the
client. <code>CloseReason</code> is used to set the status code and the
(optional) close reason of the close control frame. Then the handling
process terminates calling <code>Module:terminate(CloseReason,
State)</code> (if defined, see below).
</p>
</blockquote>
<p>
Because just handling messages is not enough for real applications, a basic
callback module can define optional functions, mainly to manage a callback
state. It can define one, some or all of the following functions:
</p>
<blockquote>
<strong>Module:init(Args) -> Result</strong>
<pre>
Args :: [ReqArg, InitialState]
Result :: {ok, State} | {ok, State, Timeout} | {error, Reason}
ReqArg :: #arg{}
InitialState :: term()
State :: term()
Timeout :: integer() >= 0 | infinity
Reason :: term()
</pre>
<p>
If defined, this function is called to initialize the internal state of
the callback module.
</p>
<p>
<code>ReqArg</code> is the #arg{} record supplied to
the <code>out/1</code> function and <code>InitialState</code> is the term
associated to the <code>CallbackType</code> described above.
</p>
<p>
If an integer timeout value is provided, it will overload the next
keepalive timeout (see <code>keepalive_timeout</code> option above). The
atom <code>infinity</code> can be used to wait indefinitely. If no value
is specified, the default keepalive timeout is used.
</p>
<p>
If something goes wrong during initialization, the function should
return <code>{error, Reason}</code>, where <code>Reason</code> is any
term.
</p>
</blockquote>
<blockquote>
<strong>Module:handle_open(WSState, State) -> Result</strong>
<pre>
WSState :: #ws_state{}
State :: term()
Result :: {ok, NewState} {error, Reason}
NewState :: term()
Reason :: term()
</pre>
<p>
If defined, this function is called when the connection is upgraded from
HTTP to WebSocket.
</p>
<p>
<code>WSState</code> is the state of the WebSocket connection. It can be
used to send messages to the client
using <code>yaws_api:websocket_send(WSState, Message)</code>.
</p>
<p>
<code>State</code> is the internal state of the callback module.
</p>
<p>
If the function returns <code>{ok, NewState}</code>, the handling process
will continue executing with the possibly updated internal
state <code>NewState</code>.
</p>
<p>
If the function returns <code>{error, Reason}</code>, the handling
process closes the connection and terminates
calling <code>Module:terminate({error, Reason}, State)</code> (if defined,
see below).
</p>
</blockquote>
<blockquote>
<strong>Module:handle_message(Message, State) -> Result</strong>
<pre>
Message :: see Module:handle_message/1
State :: term()
Result :: {noreply, NewState} | {noreply, NewState, Timeout} |
{reply, Reply} | {reply, Reply, NewState} |
{reply, Reply, NewState, Timeout} |
{close, CloseReason, NewState} |
{close, CloseReason, Reply, NewState}
NewState :: term()
Timeout :: integer() >= 0 | infinity
Reply :: see Module:handle_message/1
CloseReason :: see Module:handle_message/1
</pre>
<p>
If defined, this function is called in place
of <code>Module:handle_message/1</code>. The main difference with the
previous version is that this one handles the internal state of the
callback module.
</p>
<p>
<code>State</code> is internal state of the callback module.
</p>
<p>
See <code>Module:handle_message/1</code> for a description of the other
arguments and possible return values.
</p>
</blockquote>
<blockquote>
<strong>Module:handle_info(Info, State) -> Result</strong>
<pre>
Info :: timeout | term()
State :: term()
Result :: {noreply, NewState} | {noreply, NewState, Timeout} |
{reply, Reply} | {reply, Reply, NewState} |
{reply, Reply, NewState, Timeout} |
{close, CloseReason, NewState} |
{close, CloseReason, Reply, NewState}
NewState :: term()
Timeout :: integer() >= 0 | infinity
Reply :: see Module:handle_message/1
CloseReason :: see Module:handle_message/1
</pre>
<p>
If defined, this function is called when a timeout occurs
(see <code>drop_on_timeout</code> option above) or when the handling
process receives any unknown message.
</p>
<p>
<code>Info</code> is either the atom <code>timeout</code>, if a timeout
has occurred, or the received message.
</p>
<p>
See <code>Module:handle_message/1</code> for a description of the other
arguments and possible return values.
</p>
</blockquote>
<blockquote>
<strong>Module:terminate(Reason, State) -> ok</strong>
<pre>
Reason :: Status | {Status, Text} | {error, Error}
State :: term()
Status :: integer() %% RFC 6455 status code
Text :: binary()
Error :: term()
</pre>
<p>
If defined, this function is called when the handling process is about to
terminate. it should be the opposite of <code>Module:init/1</code> and do
any necessary cleaning up.
</p>
<p>
<code>Reason</code> is a term denoting the stop reason
and <code>State</code> is the internal state of the callback module.
</p>
</blockquote>
<h4>Advanced callback modules</h4>
<p>
Advanced callback modules should be used when automatic messages
defragmentation done by Yaws is not desirable or acceptable. One could be
used for example to handle data streaming over WebSockets. So, such modules
should be prepared to handle frames directly (fragmented or not).
</p>
<p>
Unlike basic callback modules, Advanced ones <strong>MUST</strong> manage an
internal state. So it <strong>MUST</strong> define the stateful
function <code>Module:handle_message/2</code> :
</p>
<blockquote>
<strong>Module:handle_message(Frame, State) -> Result</strong>
<pre>
Frame :: #ws_frame_info{} | {fail_connection, Status, Reason}
State :: term()
Result :: {noreply, NewState} | {noreply, NewState, Timeout} |
{reply, Reply} | {reply, Reply, NewState} |
{reply, Reply, NewState, Timeout} |
{close, CloseReason, NewState} |
{close, CloseReason, Reply, NewState}
Status :: integer() %% RFC 6455 status code
Reason :: binary()
NewState :: term()
Timeout :: integer() >= 0 | infinity
Reply :: see Module:handle_message/1
CloseReason :: see Module:handle_message/1
</pre>
<p>
This function is called when a frame is
received. The <code>#ws_frame_info{}</code> record, defined
in <code>include/yaws_api.hrl</code>, provides all details about this
frame. <code>State</code> it the internal state of the callback module.
</p>
<p>
If an error occurs during the frame parsing, the
term <code>{fail_connection, Status, Reason}</code> is passed,
where <code>Status</code> is the numerical status code corresponding to
the error
(see <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455 -
Section 7.4.1</a>) and <code>Reason</code> the binary containing optional
information about it.
</p>
<p>
This function returns the same values as specified for the basic callback
module's <code>Module:handle_message/2</code>. See above for details.
</p>
</blockquote>
<p>
Advanced callback modules can also define the same optional functions as
basic callback modules (except <code>Module:handle_messages/2</code> which
is mandatory here, of course). See above for details.
</p>
<h3>Record definitions</h3>
<p>
Here is the definition of records used in callback modules, defined
in <code>include/yaws_api.hrl</code>:
</p>
<div class="box">
<pre>
%% Corresponds to the frame sections as in
%% https://tools.ietf.org/html/rfc6455#section-5.2
%% plus 'data' and 'ws_state'. Used for incoming frames.
-record(ws_frame_info, {
fin,
rsv,
opcode,
masked,
masking_key,
length,
payload,
data, % The unmasked payload. Makes payload redundant.
ws_state % The ws_state after unframing this frame.
% This is useful for the endpoint to know what type of
% fragment a potentially fragmented message is.
}).
%% Used for outgoing frames. No checks are done on the validity of a frame. This
%% is the application's responsability to send valid frames.
-record(ws_frame, {
fin = true,
rsv = 0,
opcode,
payload = <<>>
}).
%%----------------------------------------------------------------------
%% The state of a WebSocket connection.
%% This is held by the ws owner process and passed in calls to yaws_api.
%%----------------------------------------------------------------------
-type frag_type() :: text
| binary
| none. % The WebSocket is not expecting continuation
% of any fragmented message.
-record(ws_state, {
vsn :: integer(), % WebSocket version number
sock, % gen_tcp or gen_ssl socket
frag_type :: frag_type()
}).
</pre>
</div>
<div class="logo">
<img src="/icons/yaws_pb.gif" alt="pbyaws" />
</div>
<p>
<a href="https://validator.w3.org/check?uri=referer"><img
src="https://www.w3.org/Icons/valid-xhtml10"
alt="Valid XHTML 1.0!" height="31" width="88" /></a>
</p>
</body>
</html>