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

Last change on this file since 31627 was 31627, checked in by svnwiki, 6 years ago

Anonymous wiki edit for IP [50.185.254.109]: Add list of errors and message types.

File size: 22.1 KB
Line 
1== websockets
2
3[[toc:]]
4
5=== Description
6
7{{websockets}} is a fast, lightweight, and simple implementation of the websockets protocol. It currently only supports version 13 of the websocket protocol and no extensions.
8
9{{websockets}} includes both a high level and low level API. The high level API provides both a blocking and concurrent interface. Note that contrary to most or even all other websocket implementations it does not provide an asynchronous interface but it does however provide a concurrent backed interface. See the section on the high level interface for more details. The low level API provides all of the primitives for working directly with websocket connections, messages, and fragments. It can be used for special circumstances, like when more fine grained control is desired for accepting or processing fragments, or for building a different high level API. The provided high level API is based on the exposed low level API.
10
11All high level procedures are thread safe. See the low level interface for details on which procedures are and are not thread safe at that level.
12
13All errors triggered by the library are of condition type {{websocket}} and all are continuable (not that they should be continued in all cases).
14
15=== Author
16
17[[http://thintz.com|Thomas Hintz]] with contributions from Seth Alves.
18
19Please send an email to t@thintz.com or chicken-users@nongnu.org with questions, bug reports, or feature requests.
20
21=== Repository
22
23The git repository for the websockets source code is hosted by bitbucket:
24[[https://bitbucket.org/thomashintz/websockets|https://bitbucket.org/thomashintz/websockets]].
25
26=== Requirements
27
28The following eggs are required:
29
30* [[/egg/spiffy|spiffy]]
31* [[/egg/intarweb|intarweb]]
32* [[/egg/uri-common|uri-common]]
33* [[/egg/base64|base64]]
34* [[/egg/simple-sha1|simple-sha1]]
35* [[/egg/srfi-18|srfi-18]]
36* [[/egg/srfi-13|srfi-13]]
37* [[/egg/mailbox|mailbox]]
38
39=== Quick start example
40
41Put these two files in the same folder.
42
43'''index.html'''
44<enscript highlight="html">
45<html>
46  <body>
47    <script type="text/javascript">
48      var ws = new WebSocket("ws://localhost:8080/web-socket");
49      ws.onmessage = function(evt) {
50        alert(evt.data);
51      };
52      ws.onopen = function() {
53        ws.send('Hello!');
54      }
55    </script>
56  </body>
57</html>
58</enscript>
59
60'''echo.scm'''
61<enscript highlight="scheme">
62(import chicken scheme)
63(use spiffy websockets)
64
65(handle-not-found
66 (lambda (path)
67   (when (string= path "/web-socket")
68         (with-websocket
69          (lambda ()
70            (send-message (string-append "you said: " (receive-message))))))))
71
72(root-path ".")
73(start-server port: 8080)
74</enscript>
75
76Then, in the same directory, run:
77
78<enscript>
79csi -s echo.scm
80</enscript>
81
82You should see an alert saying "you said: Hello!".
83
84=== Parameters
85
86===== {{current-websocket}}
87<parameter>(current-websocket [websocket])</parameter>
88
89Only available with {{with-websocket}} or {{with-concurrent-websocket}}. {{(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.
90
91===== {{ping-interval}}
92<parameter>(ping-interval [number])</parameter>
93
94How often to ping the client, in seconds, in the background. If {{0}} then automatic pinging will be disabled. This defaults to 15 seconds.
95
96If 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 a short while. Receiving pongs in response to a ping will also reset the connection close timer.
97
98pong responses to ping messages are not passed through to the user when using the high level API but they are used to update the timestamp that the connection timeout thread uses to decide if it should kill a connection.
99
100===== {{close-timeout}}
101<parameter>(close-timeout [number])</parameter>
102
103How 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.
104
105===== {{connection-timeout}}
106<parameter>(connection-timeout [number])</parameter>
107
108The 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.
109
110===== {{accept-connection}}
111<parameter>(accept-connection [procedure])</parameter>
112
113A one-argument (URL path of the current page) procedure which tells
114whether 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.
115
116===== {{access-denied}}
117<parameter>(access-denied [procedure])</parameter>
118
119A procedure to be called when the {{origin}} header value is rejected by the {{accept-connection}} procedure.
120
121The default is:
122
123<enscript highlight="scheme">
124(lambda () (send-status 'forbidden "<h1>Access denied</h1>"))
125</enscript>
126
127===== {{drop-incoming-pings}}
128<parameter>(drop-incoming-pings [boolean])</parameter>
129
130Clients 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.
131
132===== {{propagate-common-errors}}
133<parameter>(propagate-common-errors [boolean])</parameter>
134
135A 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.
136
137All 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).
138
139Note 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.
140
141
142===== {{max-frame-size}}
143<parameter>(max-frame-size [number])</parameter>
144
145The 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.
146
147The maximum frame size supported by the current {{websockets}} implementation is {{1GiB}}.
148
149
150===== {{max-message-size}}
151<parameter>(max-message-size [number])</parameter>
152
153The 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.
154
155The maximum message size supported by the current {{websockets}} implementation is {{1GiB}}.
156
157=== High level interface
158
159As noted in the description, the high level 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.
160
161Note that if you don't use {{with-websocket}} or {{with-concurrent-websocket}} then you will need to manually handle opening and closing the websocket connection as well as all error and protocol violations. See the low level interface section for more details.
162
163The 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 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.
164
165A list of possible message types and their respective websocket codes:
166
167<table>
168<tr><th>message type (symbol)</th><th>code</th></tr>
169<tr><td>continuation</td><td>0</td></tr>
170<tr><td>text</td><td>1</td></tr>
171<tr><td>binary</td><td>2</td></tr>
172<tr><td>connection-close</td><td>8</td></tr>
173<tr><td>ping</td><td>9</td></tr>
174<tr><td>pong</td><td>10</td></tr>
175</table>
176
177==== {{with-websocket}}
178<procedure>(with-websocket [procedure])</procedure>
179
180{{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.
181
182{{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}}.
183
184Unless 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.
185
186==== {{with-concurrent-websocket}}
187<procedure>(with-concurrent-websocket [procedure])</procedure>
188
189This 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.
190
191{{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.
192
193Sending 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.
194
195==== {{receive-message}}
196<procedure>(receive-message #!optional (ws (current-websocket)))</procedure>
197
198Read 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.
199
200{{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.
201
202It is thread safe.
203
204If 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.
205
206<enscript highlight="scheme">
207(blob->u8vector/shared (string->blob msg))
208</enscript>
209
210You could also use a string port to read the data in different fashions.
211
212<enscript highlight="scheme">
213(with-input-from-string msg
214  (lambda ()
215    (read-byte)
216    (read-u8vector))) ; etc
217</enscript>
218
219==== {{send-message}}
220<procedure>(send-message data #!optional (message-type 'text) (ws (current-websocket)))</procedure>
221
222Send 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 string it must be copied before being sent due to some CHICKEN internal limitations, strings will not. {{send-message}} also takes an optional {{websocket}} object that is bound to {{(current-websocket)}} by default.
223
224It is thread safe.
225
226=== Low level interface
227
228An object of type {{websocket}} is used throughout. For purposes of this library it is an opaque object and none of its properties or methods are exposed.
229
230==== {{upgrade-to-websocket}}
231<procedure>(upgrade-to-websocket)</procedure>
232
233Transforms the current request into a websocket request. Returns a {{websocket}} object. When this procedure completes successfully the websocket has been setup with the client and is ready to start sending and receiving messages.
234
235Can signal an error of condition type {{missing-upgrade-header}}.
236
237==== {{close-websocket}}
238<procedure>(close-websocket #!optional (ws (current-websocket)) #!key (close-reason 'normal) (data ""))</procedure>
239
240Closes off the websocket connection. {{ws}} can be a {{websocket}} object that defaults to {{(current-websocket)}}. {{data}} can be a string or u8vector and will be sent in the close frame payload and defaults to an empty body. Note that according to the specification control frame bodies must be less than 126 octets. {{close-reason}} may be one of the following symbols and defaults to {{'normal}}.
241
242
243<table>
244<tr><th>close-reason (symbol)</th><th>code</th></tr>
245<tr><td>normal</td><td>1000</td></tr>
246<tr><td>going-away</td><td>1001</td></tr>
247<tr><td>protocol-error</td><td>1002</td></tr>
248<tr><td>unknown-data-type</td><td>1003</td></tr>
249<tr><td>invalid-data</td><td>1007</td></tr>
250<tr><td>violated-policy</td><td>1008</td></tr>
251<tr><td>message-too-large</td><td>1009</td></tr>
252<tr><td>extension-negotiation-failed</td><td>1010</td></tr>
253<tr><td>unexpected-error</td><td>1011</td></tr>
254<tr><td>unknown</td><td>3000-5000</td></tr>
255</table>
256
257{{close-websocket}} will wait up to {{(close-connection-timeout)}}, if specified.
258
259==== {{read-frame}}
260<procedure>(read-frame total-size websocket)</procedure>
261
262Read in a frame. {{total-size}} is an integer used to track the total received message size in multi-frame messages. It will be checked against {{(max-message-size)}}. Note that you can read in frames larger than the noted maximum message size listed under {{max-message-size}} but you may get get unexpected and potentially harmful results if a message too large is passed into the {{unmask}} or {{valid-utf8?}} method. {{websocket}} is of type websocket. Returns an object of type {{fragment}}. {{read-frame}} does read in the frame payload but does not unmask or UTF8 validate it.
263
264A composite condition of types {{websocket}}, {{reserved-bits-not-supported}}, and {{protocol-error}} will be signaled if the frame has any reserved bits set.
265
266A composite condition of types {{websocket}} and {{message-too-large}} will be signaled if the message or frame payload exceeds {{(max-message-size)}} or {{(max-frame-size)}} respectively.
267
268A composite condition of types {{websocket}} and {{unhandled-opcode}} will be signaled if an unsupported opcode is specified in the frame. The condition property {{optype}} will contain the {{opcode}}.
269
270This is '''not thread safe'''.
271
272==== {{receive-fragments}}
273<procedure>(receive-fragments #!optional (ws (current-websocket)))</procedure>
274
275A higher level procedure that reads in all message fragments and checks for protocol violations. Returns two values: The first is a list of {{fragment}} objects that make up the message and the second is the type of the message.
276
277Can signal various composite conditions of types {{websocket}} and {{protocol-error}} with the {{protocol-error}} condition's {{msg}} property set to a description of the violation.
278
279This is thread safe.
280
281==== {{process-fragments}}
282<procedure>(process-fragments fragments message-type #!optional (ws (current-websocket)))</procedure>
283
284Takes in a list made of {{fragment}}s and unmasks and UTF8 validates the message. Returns two values. The first is a string containing the whole message and the second is the type of the message. It is possible for the return value to share memory with message fragment's payloads. {{message-type}} is the type for the list of fragments. Only messages of type {{'text}} are UTF8 validated.
285
286Can signal a composite condition of types {{websocket}} and {{invalid-data}} if the UTF8 validation fails.
287
288This is thread safe assuming the same fragments are not being processed at the same time.
289
290==== {{unmask}}
291<procedure>(unmask fragment)</procedure>
292
293Takes in a {{fragment}} and returns an unmasked version of the frame payload (not the {{fragment}}). The unmasked value will share memory with the payload slot in the passed in {{fragment}}. {{unmask}} will not attempt to unmask a fragment's payload that is not masked.
294
295==== {{valid-utf8?}}
296<procedure>(valid-utf8? s)</procedure>
297
298Takes in a string and returns a boolean #t if valid and #f if not.
299
300==== {{control-frame?}}
301<procedure>(control-frame? [symbol])</procedure>
302
303Takes in a symbol representing the message type and returns #t if it is a control frame and #f if it isn't.
304
305==== {{send-frame}}
306<procedure>(send-frame websocket message-type data last-frame)</procedure>
307
308websocket is a {{websocket}} object. message-type is a symbol representing the optype of the message. data is a string or u8vector to be sent and last-frame is a boolean, #t if it is the last frame otherwise #f.
309
310=== Fragments
311
312Fragments are an opaque object representing a partially or full processed fragment of a message. Messages may be contained in one fragment or spread across multiple fragments. See the websocket specification for more information on fragments. Fragments cannot be constructed but are returned by methods related to reading and processing fragments and messages.
313
314All of the following methods in the "Fragments" section take a fragment object as the only input argument.
315
316==== {{fragment?}}
317<procedure>(fragment? fragment)</procedure>
318
319Is {{fragment}} a fragment.
320
321==== {{fragment-payload}}
322<procedure>(fragment-payload fragment)</procedure>
323
324Returns the payload of the fragment. It may or may not be masked.
325
326==== {{fragment-length}}
327<procedure>(fragment-length fragment)</procedure>
328
329Returns the length of the fragment payload.
330
331==== {{masked?}}
332<procedure>(masked? fragment)</procedure>
333
334Returns whether the fragment payload is currently masked.
335
336==== {{fragment-masking-key}}
337<procedure>(fragment-masking-key fragment)</procedure>
338
339Returns a four element vector containing the masking key.
340
341==== {{fragment-last?}}
342<procedure>(fragment-last? fragment)</procedure>
343
344Returns #t if the fragment is the last fragment in a message otherwise #f.
345
346==== {{fragment-optype}}
347<procedure>(fragment-optype fragment)</procedure>
348
349Returns the optype (or message type) of the fragment as a symbol.
350
351=== Exceptions
352
353Possible exceptions and a brief description. See individual function definitions for details on when specific ones will be triggered.
354
355<table>
356<tr><th>type</th><th>description</th></tr>
357<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>
358<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>
359<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>
360<tr><td>reserved-bits-not-supported</td><td>A frame had a reserved bit set.</td></tr>
361<tr><td>message-too-large</td><td>A message exceeds the set {{(max-frame-size)}} or {{(max-message-size)}} parameters.</td></tr>
362<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>
363<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>
364<tr><td>missing-header-upgrade</td><td>The request does not contain an "upgrade" parameter or it is not set to "websocket".</td></tr>
365<tr><td>connection-timeout</td><td>The connection has timed out.</td></tr>
366<tr><td></td><td></td></tr>
367</table>
368
369=== Contributors
370
371Thanks to Seth Alves for developing the initial version.
372
373=== Versions
374
375==== 0.0.2
376
377First release.
378
379==== 0.0.1
380
381Initial version.
382
383=== License
384
385<nowiki>
386Copyright (c) 2014, Thomas Hintz, Seth Alves
387All rights reserved.
388
389Redistribution and use in source and binary forms, with or without
390modification, are permitted provided that the following conditions
391are met:
3921. Redistributions of source code must retain the above copyright
393   notice, this list of conditions and the following disclaimer.
3942. Redistributions in binary form must reproduce the above copyright
395   notice, this list of conditions and the following disclaimer in the
396   documentation and/or other materials provided with the distribution.
3973. The name of the authors may not be used to endorse or promote products
398   derived from this software without specific prior written permission.
399
400THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
401OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
402WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
403ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
404DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
405DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
406GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
407INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
408IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
409OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
410IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
411</nowiki>
Note: See TracBrowser for help on using the repository browser.