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

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

Anonymous wiki edit for IP [208.54.5.164]: Fix a couple wiki-misinterpretations.

File size: 20.3 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
165==== {{with-websocket}}
166<procedure>(with-websocket [procedure])</procedure>
167
168{{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.
169
170{{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}}.
171
172Unless 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.
173
174==== {{with-concurrent-websocket}}
175<procedure>(with-concurrent-websocket [procedure])</procedure>
176
177This 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.
178
179{{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.
180
181Sending 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.
182
183==== {{receive-message}}
184<procedure>(receive-message #!optional (ws (current-websocket)))</procedure>
185
186Read 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.
187
188{{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.
189
190It is thread safe.
191
192If 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.
193
194<enscript highlight="scheme">
195(blob->u8vector/shared (string->blob msg))
196</enscript>
197
198You could also use a string port to read the data in different fashions.
199
200<enscript highlight="scheme">
201(with-input-from-string msg
202  (lambda ()
203    (read-byte)
204    (read-u8vector))) ; etc
205</enscript>
206
207==== {{send-message}}
208<procedure>(send-message data #!optional (message-type 'text) (ws (current-websocket)))</procedure>
209
210Send 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.
211
212It is thread safe.
213
214=== Low level interface
215
216An 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.
217
218==== {{upgrade-to-websocket}}
219<procedure>(upgrade-to-websocket)</procedure>
220
221Transforms 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.
222
223Can signal an error of condition type {{missing-upgrade-header}}.
224
225==== {{close-websocket}}
226<procedure>(close-websocket #!optional (ws (current-websocket)) #!key (close-reason 'normal) (data ""))</procedure>
227
228Closes 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}}.
229
230
231<table>
232<tr><th>close-reason (symbol)</th><th>code</th></tr>
233<tr><td>normal</td><td>1000</td></tr>
234<tr><td>going-away</td><td>1001</td></tr>
235<tr><td>protocol-error</td><td>1002</td></tr>
236<tr><td>unknown-data-type</td><td>1003</td></tr>
237<tr><td>invalid-data</td><td>1007</td></tr>
238<tr><td>violated-policy</td><td>1008</td></tr>
239<tr><td>message-too-large</td><td>1009</td></tr>
240<tr><td>extension-negotiation-failed</td><td>1010</td></tr>
241<tr><td>unexpected-error</td><td>1011</td></tr>
242<tr><td>unknown</td><td>3000-5000</td></tr>
243</table>
244
245{{close-websocket}} will wait up to {{(close-connection-timeout)}}, if specified.
246
247==== {{read-frame}}
248<procedure>(read-frame total-size websocket)</procedure>
249
250Read 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.
251
252A composite condition of types {{websocket}}, {{reserved-bits-not-supported}}, and {{protocol-error}} will be signaled if the frame has any reserved bits set.
253
254A 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.
255
256A 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}}.
257
258This is '''not thread safe'''.
259
260==== {{receive-fragments}}
261<procedure>(receive-fragments #!optional (ws (current-websocket)))</procedure>
262
263A 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.
264
265Can 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.
266
267This is thread safe.
268
269==== {{process-fragments}}
270<procedure>(process-fragments fragments message-type #!optional (ws (current-websocket)))</procedure>
271
272Takes 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.
273
274Can signal a composite condition of types {{websocket}} and {{invalid-data}} if the UTF8 validation fails.
275
276This is thread safe assuming the same fragments are not being processed at the same time.
277
278==== {{unmask}}
279<procedure>(unmask fragment)</procedure>
280
281Takes 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.
282
283==== {{valid-utf8?}}
284<procedure>(valid-utf8? s)</procedure>
285
286Takes in a string and returns a boolean #t if valid and #f if not.
287
288==== {{control-frame?}}
289<procedure>(control-frame? [symbol])</procedure>
290
291Takes in a symbol representing the message type and returns #t if it is a control frame and #f if it isn't.
292
293==== {{send-frame}}
294<procedure>(send-frame websocket message-type data last-frame)</procedure>
295
296websocket 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.
297
298=== Fragments
299
300Fragments 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.
301
302All of the following methods in the "Fragments" section take a fragment object as the only input argument.
303
304==== {{fragment?}}
305<procedure>(fragment? fragment)</procedure>
306
307Is {{fragment}} a fragment.
308
309==== {{fragment-payload}}
310<procedure>(fragment-payload fragment)</procedure>
311
312Returns the payload of the fragment. It may or may not be masked.
313
314==== {{fragment-length}}
315<procedure>(fragment-length fragment)</procedure>
316
317Returns the length of the fragment payload.
318
319==== {{masked?}}
320<procedure>(masked? fragment)</procedure>
321
322Returns whether the fragment payload is currently masked.
323
324==== {{fragment-masking-key}}
325<procedure>(fragment-masking-key fragment)</procedure>
326
327Returns a four element vector containing the masking key.
328
329==== {{fragment-last?}}
330<procedure>(fragment-last? fragment)</procedure>
331
332Returns #t if the fragment is the last fragment in a message otherwise #f.
333
334==== {{fragment-optype}}
335<procedure>(fragment-optype fragment)</procedure>
336
337Returns the optype (or message type) of the fragment as a symbol.
338
339=== Contributors
340
341Thanks to Seth Alves for developing the initial version.
342
343=== Versions
344
345==== 0.0.2
346
347First release.
348
349==== 0.0.1
350
351Initial version.
352
353=== License
354
355<nowiki>
356Copyright (c) 2014, Thomas Hintz, Seth Alves
357All rights reserved.
358
359Redistribution and use in source and binary forms, with or without
360modification, are permitted provided that the following conditions
361are met:
3621. Redistributions of source code must retain the above copyright
363   notice, this list of conditions and the following disclaimer.
3642. Redistributions in binary form must reproduce the above copyright
365   notice, this list of conditions and the following disclaimer in the
366   documentation and/or other materials provided with the distribution.
3673. The name of the authors may not be used to endorse or promote products
368   derived from this software without specific prior written permission.
369
370THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
371OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
372WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
373ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
374DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
375DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
376GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
377INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
378IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
379OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
380IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
381</nowiki>
Note: See TracBrowser for help on using the repository browser.