source: project/wiki/eggref/4/websockets @ 33251

Last change on this file since 33251 was 33251, checked in by svnwiki, 3 years ago

Anonymous wiki edit for IP [73.222.105.54]: Removing passing of debug option to test case example.

File size: 17.8 KB
Line 
1== websockets
2
3[[toc:]]
4
5=== Description
6
7{{websockets}} is a fast, lightweight websockets implementation. It supports version 13 of the websocket protocol and, currently, no extensions. It passes all of the [[http://autobahn.ws/testsuite/|Autobahn websockets compliance tests]].
8
9All errors triggered by the library are of condition type {{websocket}} and all are continuable (not that they should be continued in all cases).
10
11{{websockets}} also works with TLS. Just use it within a spiffy TLS request and connect to it with {{wss://}} instead of {{ws://}}.
12
13=== Author
14
15[[http://thintz.com|Thomas Hintz]] with contributions from Seth Alves.
16
17Please send an email to t@thintz.com or chicken-users@nongnu.org with questions, bug reports, or feature requests.
18
19=== Repository
20
21The git repository for the websockets source code is hosted by bitbucket:
22[[https://bitbucket.org/thomashintz/websockets|https://bitbucket.org/thomashintz/websockets]].
23
24=== Requirements
25
26The following eggs are required:
27
28* [[/egg/spiffy|spiffy]]
29* [[/egg/intarweb|intarweb]]
30* [[/egg/uri-common|uri-common]]
31* [[/egg/base64|base64]]
32* [[/egg/simple-sha1|simple-sha1]]
33* [[/egg/mailbox|mailbox]]
34* [[/egg/comparse|comparse]]
35
36=== Quick start example
37
38Put these two files in the same folder.
39
40'''index.html'''
41<enscript highlight="html">
42<html>
43  <body>
44    <script type="text/javascript">
45      var ws = new WebSocket("ws://localhost:8080/web-socket");
46      ws.onmessage = function(evt) {
47        alert(evt.data);
48      };
49      ws.onopen = function() {
50        ws.send('Hello!');
51      }
52    </script>
53  </body>
54</html>
55</enscript>
56
57'''echo.scm'''
58<enscript highlight="scheme">
59(import chicken scheme)
60(use spiffy websockets)
61
62(handle-not-found
63 (lambda (path)
64   (when (string= path "/web-socket")
65         (with-websocket
66          (lambda ()
67            (send-message (string-append "you said: " (receive-message))))))))
68
69(root-path ".")
70(start-server port: 8080)
71</enscript>
72
73Then, in the same directory, run:
74
75<enscript>
76csi -s echo.scm
77</enscript>
78
79Navigate to http://localhost:8080 and you should see an alert saying "you said: Hello!".
80
81The websocket specific code is really simple. In the above example it looks like this:
82
83<enscript highlight="scheme">
84(with-websocket
85  (lambda ()
86    (send-message (string-append "you said: " (receive-message)))))
87</enscript>
88
89You can put that in any request handler it it will work the same.
90
91=== Parameters
92
93===== {{current-websocket}}
94<parameter>(current-websocket [websocket])</parameter>
95
96{{(current-websocket)}} will be bound to a websocket object. Many procedures provide an optional argument for a websocket object and it is bound to {{current-websocket}} by default.
97
98===== {{ping-interval}}
99<parameter>(ping-interval [number])</parameter>
100
101How often to ping the client, in seconds, in the background. If {{0}} then automatic pinging will be disabled. This defaults to 15 seconds.
102
103If this is set to a value greater than 0 then a thread will run in the background sending ping messages to the client at the specified interval. This is used to keep connections open that will otherwise be closed. Often connections without a transmission will be killed after awhile. Receiving pongs in response to a ping will also reset the connection close timer.
104
105pong responses to ping messages are not passed through to the user but they are used to update the timestamp that the connection timeout thread uses to decide if it should kill a connection.
106
107===== {{close-timeout}}
108<parameter>(close-timeout [number])</parameter>
109
110How long to wait, in seconds, for the client to respond to a connection close request before timing out. If {{0}} then the connection timeout will be disabled. The default value is 5 seconds.
111
112===== {{connection-timeout}}
113<parameter>(connection-timeout [number])</parameter>
114
115The length of time in seconds without a response (of any kind) from the client before the connection to the client will be cut off. If {{0}} then the connection will never be timed out by the websocket (something else can, of course, timeout causing the websocket connection to fail anyways). The default is 60 seconds.
116
117===== {{accept-connection}}
118<parameter>(accept-connection [procedure])</parameter>
119
120A one-argument procedure taking as argument the URL path of the current page which tells
121whether to accept the websocket connection. If {{#t}} accept, {{#f}} reject. '''IT IS HIGHLY RECOMMENDED''' that this parameter be set otherwise it opens up your application to potential security vulnerabilities. You will probably want to verify it is coming from a specific source. The default is to accept all connections.
122
123===== {{access-denied}}
124<parameter>(access-denied [procedure])</parameter>
125
126A procedure to be called when the {{origin}} header value is rejected by the {{accept-connection}} procedure.
127
128The default is:
129
130<enscript highlight="scheme">
131(lambda () (send-status 'forbidden "<h1>Access denied</h1>"))
132</enscript>
133
134===== {{drop-incoming-pings}}
135<parameter>(drop-incoming-pings [boolean])</parameter>
136
137Clients should usually not initiate pings so the default is to drop all incoming pings without responding with a pong. This defaults to {{#t}}. Set this to {{#f}} to respond to incoming pings with a pong.
138
139===== {{propagate-common-errors}}
140<parameter>(propagate-common-errors [boolean])</parameter>
141
142A lot of errors that occur in the lifecycle of a websocket connection are more-or-less expected and it often is not interesting to receive and deal with these errors. The default is to correctly close the connection when an error occurs with the required opcode but the error is then not signaled to the user. The default is {{#f}}. Set to {{#t}} to receive all errors.
143
144All websocket specific errors are covered by this. The only error you may receive if this is {{#f}} is of type {{websocket}} and {{unexpected-error}} if something goes terribly wrong with the implementation and usually means there is a bug in the implementation. This does not affect errors that are caused by user code, they will always be passed on but the websocket will be properly with an error code of 1011 (unexpected-error).
145
146Note that this parameter is only relevant when using {{with-websocket}} or {{with-concurrent-websocket}}. If you don't use one of those then all errors will be propagated.
147
148
149===== {{max-frame-size}}
150<parameter>(max-frame-size [number])</parameter>
151
152The maximum allowed frame payload size. The default is {{1MiB}}. If a frame exceeds this size then its payload will not be read and the connection will be dropped immediately. This signals a {{message-too-large}} error.
153
154The maximum frame size supported by the current {{websockets}} implementation is {{1GiB}}.
155
156
157===== {{max-message-size}}
158<parameter>(max-message-size [number])</parameter>
159
160The maximum allowed total message size. The default is {{1MiB}}. If a frame will cause the {{max-message-size}} to be exceeded then its payload is not read and the connection is dropped immediately. This signals a {{message-too-large}} error.
161
162The maximum message size supported by the current {{websockets}} implementation is {{1GiB}}.
163
164=== Interface
165
166The {{websockets}} interface is not a "traditional" asynchronous API as is often seen with other websocket libraries. Instead a blocking and synchronous interface is provided as well as an asynchronous ''backed'' interface that looks and behaves like a blocking interface. See the function definitions below for further details.
167
168The main difference between {{with-websocket}} and {{with-concurrent-websocket}} is that messages are guaranteed to be delivered in order with {{with-websocket}}. This also means that, when using {{with-websocket}} but not {{with-concurrent-websocket}}, no messages will be read into memory and processed unless a call is made to {{receive-message}}. This includes pong messages that may be sent in response to any pings sent in the background ping thread, if enabled. See the definition of {{ping-interval}} for more details on how background pings and pongs work. One implication of this is that unless you are waiting to receive a message (with {{receive-message}}) then pongs will not be received and the connection timeout timer will not be reset.
169
170When receiving a {{connection-close}} message you should not send a response and you should exit the body of {{with-websocket}} or {{with-concurrent-websocket}}. At that point the connection will be closed properly. If you aren't expecting the connection to be closed you can signal an error and the websocket will be closed with an error code.
171
172==== {{with-websocket}}
173<procedure>(with-websocket [procedure])</procedure>
174
175{{with-websocket}} handles the process of setting up and closing off a websocket connection. {{procedure}} is a thunk to be executed after the websocket has been successfully setup. When you use {{with-websocket}} you can be assured that the connection will always be closed correctly even if errors occur in your application code and for all protocol violations.
176
177{{with-websocket}} sends and receives all messages in a blocking fashion. Only one message will be sent or received at a time unless more threads are introduced in {{procedure}}. Even then the processing will block sending and receiving whereas it will not with {{with-concurrent-websocket}}.
178
179Unless you expect a high volume of messages or messages of very large size on one websocket connection then this is the recommended procedure to use. It is easier to program when you know messages will arrive in order and it is more lightweight.
180
181==== {{with-concurrent-websocket}}
182<procedure>(with-concurrent-websocket [procedure])</procedure>
183
184This will, like {{with-websocket}}, handle setting up and closing a websocket connection. {{procedure}} is a thunk to be executed after the websocket has been successfully setup.
185
186{{with-concurrent-websocket}} adds to {{with-websocket}} by running a thread in the background to read in messages as they arrive and spins off a new thread to process each message. Messages can arrive out-of-order, especially if there are a mix of very large and very small messages. {{with-concurrent-websocket}} can be very useful if very large messages are expected since they can take a long time to unmask and UTF8 validate.
187
188Sending messages are still done in a blocking fashion but sending is relatively fast and will likely not be a problem. This could change in the future though so don't rely on it.
189
190==== {{receive-message}}
191<procedure>(receive-message #!optional (ws (current-websocket)))</procedure>
192
193Read in a message from the client. Returns two values. The first is the message and the second is the message type, either {{'text}} or {{'binary}}. Regardless of the message type the message itself will always be a string. Takes an optional {{websocket}} object that is bound to {{(current-websocket)}} by default. It will always block until a message is received but if used within {{with-concurrent-websocket}} it will not block other messages from being received or processed and will return the first message that is finished being processed.
194
195{{receive-message}} is "concurrent" aware so if it is used within {{with-websocket}} it will behave in a purely blocking fashion and when used within {{with-concurrent-websocket}} it will utilize the provided concurrency mechanism internally.
196
197Note on performance: the websocket protocol requires all messages to be valid UTF8. The current validation process is extremely fast and generates very little garbage for UTF8 messages that only contain ASCII code points (chars less than 128), but is quite slow and uses a lot of memory when it contains higher plane code points. If you are processing a high volume of messages or exceptionally large messages it may be worthwhile to try to keep all or most messages ASCII only.
198
199This method is thread safe.
200
201A list of the possible message types (symbols):
202
203* {{'text}}
204* {{'binary}}
205* {{'connection-close}}
206
207If a message is of type {{binary}} then converting it to something possibly more "binary" like, such as a u8vector, could be done with the following. It will incur one copy of the data though.
208
209<enscript highlight="scheme">
210(blob->u8vector/shared (string->blob msg))
211</enscript>
212
213You could also use a string port to read the data in different fashions.
214
215<enscript highlight="scheme">
216(with-input-from-string msg
217  (lambda ()
218    (read-byte)
219    (read-u8vector))) ; etc
220</enscript>
221
222==== {{send-message}}
223<procedure>(send-message data #!optional (message-type 'text) (ws (current-websocket)))</procedure>
224
225Send a message to the client. {{data}} is a string or u8vector to be sent to the client. {{message-type}} is one of the types listed under the {{message-types}} section and defaults to {{'text}}. Usually this will be {{'text}} or {{'binary}} but any valid type is allowed. Note that if the {{data}} argument is a u8vector it must be copied before being sent due to some CHICKEN internal limitations, strings will not be. {{send-message}} also takes an optional {{websocket}} object that is bound to {{(current-websocket)}} by default.
226
227This method is thread safe.
228
229A list of the possible message types (symbols):
230
231* {{'text}}
232* {{'binary}}
233* {{'ping}}
234
235=== Exceptions
236
237Possible exceptions and a brief description. See individual function definitions for details on when specific ones will be triggered.
238
239<table>
240<tr><th>type</th><th>description</th></tr>
241<tr><td>websocket</td><td>A part of every websocket error as a composite condition. You can use this as a catchall for all websocket related errors.</td></tr>
242<tr><td>protocol-error</td><td>Anytime something happens that violates the websocket protocol. The {{msg}} error property will contain details on the protocol broken.</td></tr>
243<tr><td>invalid-optype</td><td>If an invalid message type (optype) is passed into any method that takes it as an argument.</td></tr>
244<tr><td>reserved-bits-not-supported</td><td>A frame had a reserved bit set.</td></tr>
245<tr><td>message-too-large</td><td>A message exceeds the set {{(max-frame-size)}} or {{(max-message-size)}} parameters.</td></tr>
246<tr><td>unhandled-optype</td><td>The implementation received a frame with an opcode it could not handle. The {{optype}} property contains the unhandleable optype (as produced by {{(opcode->optype)}}).</td></tr>
247<tr><td>invalid-data</td><td>Currently only signaled when a text payload contains invalid UTF8 codepoints. The {{msg}} property will hold a description of the "invalid data".</td></tr>
248<tr><td>missing-header-upgrade</td><td>The request does not contain an "upgrade" parameter or it is not set to "websocket".</td></tr>
249<tr><td>connection-timeout</td><td>The connection has timed out.</td></tr>
250<tr><td></td><td></td></tr>
251</table>
252
253=== Examples
254
255==== echo server for the Autobahn test suite
256
257To run the tests make sure to have the test suites installed.
258
259[[http://autobahn.ws/testsuite/|Autobahn test suite]]
260
261'''echo-server.scm'''
262<enscript highlight="scheme">
263(import chicken scheme)
264(use spiffy websockets)
265
266(ping-interval 0)
267(drop-incoming-pings #f)
268
269(handle-not-found
270 (lambda (path)
271   (with-websocket
272     (lambda ()
273       (let loop ()
274         (receive (data type) (receive-message)
275                  (unless (eq? type 'connection-close)
276                          (send-message data type)
277                          (loop))))))))
278
279(start-server port: 8080)
280</enscript>
281
282'''ws-test.spec'''
283<enscript>
284{
285    "servers": [
286        {"agent": "AutobahnServer",
287            "url": "ws://localhost:8080/web-socket",
288            "options": {"version": 13}}
289        ],
290    "cases": ["1.*", "2.*", "3.*", "4.*", "5.*"],
291    "exclude-cases": [],
292    "exclude-agent-cases": {}
293}
294</enscript>
295
296Run and compile it:
297
298'''terminal'''
299<enscript>
300csc -O3 echo-server.scm
301./echo-server
302</enscript>
303
304'''terminal 2'''
305<enscript>
306wstest -m fuzzingclient -s ws-test.spec
307</enscript>
308
309=== Contributors
310
311Thanks to Seth Alves for developing the initial version. Thanks also to Andy Bennet and Peter Bex for helping solve implementation problems and providing advice as well as all the others on #chicken for their contributions.
312
313=== Versions
314
315==== 0.1.6
316
317Bug fix: flush the correct output port when sending a frame. (thanks to Alex Charlton)
318
319==== 0.1.5
320
321Bug fix: Re-exposing (current-websocket) (thanks to Alex Charlton)
322
323==== 0.1.4
324
325Numerous bug fixes including:
326
327* preventing some race conditions.
328* preventing some buffer overflows.
329* flushing the output port after each frame is sent (thanks to Mario Goulart).
330* correcting the upgrade header check (thanks to Andras Pahi).
331
332==== 0.1.3
333
334Remove reliance on C99 feature.
335
336==== 0.1.2
337
338Require spiffy 5.3.1 or newer.
339
340==== 0.1.1
341
342Fix bug where the connection was not being failed when a close reason contained an invalid UTF8 character.
343
344==== 0.1.0
345
346First release.
347
348==== 0.0.1
349
350Initial version.
351
352=== License
353
354 Copyright (c) 2014, Thomas Hintz, Seth Alves
355 All rights reserved.
356 
357 Redistribution and use in source and binary forms, with or without
358 modification, are permitted provided that the following conditions
359 are met:
360 
361 1. Redistributions of source code must retain the above copyright
362    notice, this list of conditions and the following disclaimer.
363 2. Redistributions in binary form must reproduce the above copyright
364    notice, this list of conditions and the following disclaimer in the
365    documentation and/or other materials provided with the distribution.
366 3. The name of the authors may not be used to endorse or promote products
367    derived from this software without specific prior written permission.
368 
369 THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
370 OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
371 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
372 ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
373 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
374 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
375 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
376 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
377 IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
378 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
379 IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Note: See TracBrowser for help on using the repository browser.