Compare commits

...

71 Commits
0.6 ... 0.6.12

Author SHA1 Message Date
Guillermo Rauch
de8d573948 Release 0.6.12 2011-02-18 10:36:13 -08:00
Guillermo Rauch
ea9e5ed2cc Fixed noDelay missing file descriptor problem 2011-02-18 10:34:51 -08:00
Guillermo Rauch
109a59ca9a Release 0.6.11 2011-02-15 15:43:35 -08:00
Guillermo Rauch
d304ce19d1 Fixed; Make sure to not execute any other connection operations after WebSocket write error 2011-02-15 15:41:45 -08:00
Marcus Westin
f534a260b3 Don't let errors in an application's message handling code get silently swallowed by any of the transports 2011-02-14 19:56:20 -05:00
Marcus Westin
cff4669d57 Log errors from closing the flash socket server 2011-02-14 19:54:00 -05:00
Guillermo Rauch
4b0a1f22c8 Release 0.6.10 2011-02-09 18:45:00 -08:00
Guillermo Rauch
e79bdb00e9 Added SSL chat example (make example-ssl) 2011-02-09 18:44:07 -08:00
Guillermo Rauch
d11ca00b49 Fixed; possible write errors when a connection error event fires 2011-02-09 18:41:13 -08:00
Guillermo Rauch
f3ba4173c7 Release 0.6.9 2011-02-06 10:09:13 -08:00
Guillermo Rauch
569103e19a Updated expresso 2011-02-06 09:34:16 -08:00
Guillermo Rauch
06445a0faa Added comments and version number to socket.io/index 2011-02-06 09:33:40 -08:00
Guillermo Rauch
2506b06961 Tests refactored for 0.3 and new expresso 2011-02-06 09:33:15 -08:00
Guillermo Rauch
992eda86b4 Updated socket.io client to 0.6.2 2011-02-05 11:40:39 -08:00
Guillermo Rauch
6fa8b1f051 Fixed Flash inline policy serving for Firefox 4 2011-02-05 11:40:14 -08:00
Guillermo Rauch
a91c6f26f4 Release 0.6.8 2011-01-10 01:50:26 -08:00
Guillermo Rauch
aa9f2596cb Fixed issue with terminating connection twice 2011-01-10 01:48:05 -08:00
Guillermo Rauch
e2a97588ef Removed unnecessary code 2011-01-09 19:07:11 -08:00
Guillermo Rauch
0b904d79c2 Release 0.6.7 2011-01-09 18:55:30 -08:00
Guillermo Rauch
f99ac54df5 Fixed situation where the connection drops but the client can still autoreconnect
through a different socket. In this case we still want to clear the FD but not call
onDisconnect immediately.
2011-01-09 18:53:40 -08:00
Guillermo Rauch
b306cc77d7 Release 0.6.6 2011-01-09 18:17:42 -08:00
Guillermo Rauch
ba70be4e0b Note for Flash socket and inline policy on Firefox
Destroy the fds on disconnect
Restored 20 secs of polling so that node doesn't timeout the connections
2011-01-09 18:16:24 -08:00
Guillermo Rauch
0b17ec9cb8 Release 0.6.5 2011-01-09 16:56:54 -08:00
Guillermo Rauch
71e77561bb Make sure not to trigger multiple timeouts when closing
Important fix for polling transports.
2011-01-09 16:54:35 -08:00
Guillermo Rauch
f5b2028577 Release 0.6.4 2011-01-05 11:21:37 -08:00
Fabian Jakobs
7f08d8fd59 Don't destroy the connection in _onClose. Destroying
it will prevent the buffers from being flushed and
will result in corrupted responses for the
xhr-polling transport.

According to the node documentation "destroy" is
only necessary in case of a errors.
2011-01-05 13:25:35 +01:00
Mathew Rodley
ffb0574a76 Added try/catch block around JSON.parse and return an empty object literal
if JSON parsing fails.
2010-12-30 13:18:27 +11:00
Guillermo Rauch
e57e27ee43 Added missing .connect() to example 2010-12-24 13:02:35 -08:00
Guillermo Rauch
3c76d732e6 Client updated to 0.6.1 2010-12-23 20:52:27 -08:00
Guillermo Rauch
e058ae5201 Updated history 2010-12-23 20:50:35 -08:00
Guillermo Rauch
715c46de13 Changed polling default duration to 50 seconds 2010-12-23 20:38:20 -08:00
Guillermo Rauch
eb0e1d3d2c might > will
Adjusted to 85 column limit
2010-12-23 20:11:24 -08:00
Guillermo Rauch
1d6687cecc Fixed typo 2010-12-23 20:08:36 -08:00
Guillermo Rauch
f62bae4e8b Updated client 2010-12-23 17:48:04 -08:00
Guillermo Rauch
28535071bb Support for resources that include slashes. Thanks @schamane 2010-12-23 17:10:39 -08:00
Guillermo Rauch
45f1712d57 Lazy loading of transports. Thanks @technoweenie
Fixed README transports list
2010-12-23 16:52:51 -08:00
Guillermo Rauch
3029d0cf2f OpenSSL clarifications (thanks @bmnds) 2010-12-23 16:46:17 -08:00
Guillermo Rauch
5c9fb03c50 Support for HAProxy load balancing (thanks Brian McKelvey)
Backported Parser from 0.7
2010-12-23 16:25:16 -08:00
Guillermo Rauch
39fab0ab9b Fixed HTTP API in example (was outdated). Thanks deedubs 2010-12-23 15:09:55 -08:00
Guillermo Rauch
c958a12c79 0.3 compatibility (thanks Arnout) 2010-12-23 15:08:06 -08:00
Guillermo Rauch
d298113a3e Temporarily reverting to random session id generation 2010-12-23 14:48:41 -08:00
Guillermo Rauch
3c6fc999dc client.broadcast now 300% faster
Cleaned up chat example
Implemented a simpler session id generation mechanism. Math.random is not webscale
2010-11-11 06:19:07 -03:00
jed
56205392c8 fixed bad pluralization. 2010-11-09 22:39:53 +09:00
jed
967238acb7 cleaned up grammar, missing punctuation, etc. 2010-11-09 22:37:15 +09:00
Guillermo Rauch
3c61f64d29 Prepping package.json for npm publishing 2010-11-08 20:00:15 -03:00
Guillermo Rauch
e91c785ac8 0.6.1 changelog 2010-11-08 19:59:39 -03:00
Guillermo Rauch
d0369801a1 Restored global netserver for flashsocket
Now supporting `flashPolicyServer` option (thanks Arnout)
Tests passing with and without sudo/root user
Fixed noDelay/timeout/utf-8 for draft 76 (accidental typo)
2010-11-08 18:55:27 -03:00
Guillermo Rauch
e51b37da92 Merge branch 'master' of git://github.com/3rd-Eden/Socket.IO-node 2010-11-08 17:37:32 -03:00
Arnout Kazemier
c7bec15fa9 Close the netServer when the main http server closes, this way the event loop does not keep running.
NOTE: this is patch for node 0.2.X, this is not required for node 0.3.X
2010-11-08 21:35:42 +01:00
Guillermo Rauch
4c60ed190d Merge branch 'master' of git://github.com/3rd-Eden/Socket.IO-node 2010-11-08 17:28:19 -03:00
Arnout Kazemier
d959b96363 Fallback to try{}catch handling for node < 0.2.4 , node 0.3.X seems to capture the
errors correctly using the error event.
2010-11-08 21:25:20 +01:00
Guillermo Rauch
a9dc5e081b Merge branch 'master' of git://github.com/3rd-Eden/Socket.IO-node 2010-11-08 17:17:57 -03:00
Arnout Kazemier
c168c89f1a Added the flash policy server, it's enabled by default but can be turned off if needed.
Socket.io will automatically fallback to serving the policy file inline if server is disabled or
unable to start up.
2010-11-08 21:09:52 +01:00
Guillermo Rauch
be4c847692 Misc refactor 2010-11-02 01:13:04 -03:00
Guillermo Rauch
9649b41f25 Fixed history 2010-11-01 18:50:13 -03:00
Guillermo Rauch
6a0f80e4fc Changelog 2010-11-01 18:47:08 -03:00
Guillermo Rauch
4240385f6f Make sure to only write to open transports (thanks JohnDav) 2010-11-01 17:52:23 -03:00
Guillermo Rauch
22a93d06cb Another bad onClose call 2010-11-01 17:47:05 -03:00
Guillermo Rauch
4ea687b57e Ditto 2010-11-01 17:44:41 -03:00
Guillermo Rauch
227671d41d _open is still false, so destroy the connection immediately upon websocket error 2010-11-01 17:43:24 -03:00
Guillermo Rauch
7bf4ae1f05 Make sure .connection is not null on 'end' 2010-11-01 17:27:14 -03:00
Guillermo Rauch
b28d4288d5 Proper fix for invalid websocket key 2010-11-01 17:22:43 -03:00
Guillermo Rauch
835aee46bd Bump version 2010-11-01 17:20:50 -03:00
Guillermo Rauch
054e818bd9 Fix spooky bug 2010-11-01 17:20:23 -03:00
Guillermo Rauch
107806b304 Bump version 2010-11-01 17:10:52 -03:00
Guillermo Rauch
426795395c Call onclose after destroying the connection 2010-11-01 17:10:13 -03:00
Guillermo Rauch
df929876de Updated package.json 2010-11-01 16:54:15 -03:00
Guillermo Rauch
1002105f7a added onClose skipDisconnect flag 2010-11-01 16:34:37 -03:00
Guillermo Rauch
8f4f82a413 onClose call 2010-11-01 15:35:52 -03:00
Guillermo Rauch
e1745e20ac Make sure to disconnect directly onClose if the client is not handshaked and he can't possibly reconnect 2010-11-01 15:35:06 -03:00
Guillermo Rauch
681f004fe9 Make sure to clear the FD on end 2010-11-01 15:01:24 -03:00
58 changed files with 1648 additions and 939 deletions

210
History.md Normal file
View File

@@ -0,0 +1,210 @@
0.6.12 / 2011-02-18
===================
* Fixed noDelay missing file descriptor problem
0.6.11 / 2011-02-15
===================
* Fixed; Make sure to not execute any other connection operations after WebSocket
write error.
* Added more error logging
0.6.10 / 2011-02-09
===================
* Added SSL chat example (`make example-ssl`)
* Fixed; possible write errors when a connection error event fires
0.6.9 / 2011-02-06
==================
* 0.3 compatibility
* Updated socket.io client to 0.6.2
* Fixed Flash inline policy serving for Firefox 4
* Updated expresso
* Added comments and version number to socket.io/index
0.6.8 / 2011-01-10
==================
* Fixed issue with terminating connection twice
0.6.7 / 2011-01-09
==================
* Fixed situation where the connection drops but the client can still autoreconnect
through a different socket. In this case we still want to clear the FD but not
call onDisconnect immediately.
0.6.6 / 2011-01-09
==================
* Note for Flash socket and inline policy on Firefox
* Destroy the fds on disconnect
* Restored 20 secs of polling so that node doesn't timeout the connections
0.6.5 / 2011-01-09
==================
* Make sure not to trigger multiple timeouts when closing
* Important fix for polling transports.
0.6.4 / 2011-01-05
==================
* Don't destroy the connection in _onClose. Destroying it will prevent the buffers from being flushed and will result in corrupted responses for the xhr-polling transport.
* Added try/catch block around JSON.parse and return an empty object literal if JSON parsing fails.
* Added missing .connect() to example
0.6.3 / 2010-12-23
==================
* Changed polling default duration to 50 seconds
* might > will Adjusted to 85 column limit
* Support for resources that include slashes. Thanks @schamane
* Lazy loading of transports. Thanks @technoweenie Fixed README transports list
* OpenSSL clarifications (thanks @bmnds)
* Support for HAProxy load balancing (thanks Brian McKelvey) Backported Parser from 0.7
* Fixed HTTP API in example (was outdated). Thanks deedubs
* 0.3 compatibility (thanks Arnout)
* `client.broadcast` now 300% faster Cleaned up chat example
* fixed bad pluralization.
* cleaned up grammar, missing punctuation, etc.
* Restored global `netserver` for flashsocket Now supporting `flashPolicyServer` option (thanks Arnout) Tests passing with and without sudo/root user Fixed noDelay/timeout/utf-8 for draft 76 (accidental typo)
* Close the netServer when the main http server closes, this way the event loop does not keep running. NOTE: this is patch for node 0.2.X, this is not required for node 0.3.X
* Fallback to try{}catch handling for node < 0.2.4 , node 0.3.X seems to capture the errors correctly using the error event.
* Added the flash policy server, it's enabled by default but can be turned off if needed. Socket.io will automatically fallback to serving the policy file inline if server is disabled or unable to start up.
* Make sure to only write to open transports (thanks JohnDav)
* _open is still false, so destroy the connection immediately upon websocket error
* Make sure .connection is not null on 'end'
* Proper fix for invalid websocket key
0.6.1 / 2010-11-08
* Restored flash policy server, but with these changes:
- It's contingent on the listener flashPolicyServer option
- It's started by default if socket.io is started with root access
- It correctly closes the netserver upon all the dependent http servers being closed
- The handler for the inline request is still there regardless. This is important in the following circumstances, and has no performance hit
- The port 843 is filtered
- Flash at some point enables us to skip 843 checking altogether
- Tests compatibility
* Fixed connection timeout, noDelay and socket encoding for draft 76 (had been accidentally moved into the `else` block)
* Some stylistic fixes
0.6.0 / 2010-11-01
==================
* Make sure to only write to open transports (thanks JohnDav)
* _open is still false, so destroy the connection immediately upon websocket error
* Make sure to disconnect directly onClose if the client is not handshaked and he can't possibly reconnect
* Make sure to end and destroy connection onDisconnect (for timeouts)
* Added missing .listen() call to example. Fixes #80. Thanks @machee
* Invalid transport test completed
* Initial stab at trying to detect invalid transport responses
* Make sure to provide a default for `log` if no log key was provided (internal)
* Removed unnecessary file extension verification when serving the client
* Removed unnecessary Client check upon connection
* Added support for /socket.io/WebSocketMain.swf
* Added test for /socket.io/WebSocketMain.swf
* Client serving ETag testing
* Added htmlfile transport tests
* Added extra byte to IE iframe bytes padding
* Invalid session id test
* end() before destroy()ing the socket for non-WebSocket or non-valid Upgrade requests
* Added test for non-socket.io requests
* Simplified index.js tests
* Moved listener tests into listener.js
* Make sure to call .end() when listening on connection 'end' event
* Make sure the file descriptor is destroyed on disconnection
* Fix for websocket client tracking test
* Inline (same port) flash socket policy request.
* If the server is not run with root privileges, then the flashsocket
transport will instead listen to all new connections on the main port
for policy requests. Flash policy requests happen to both port 843 and
the destination port:
http://www.lightsphere.com/dev/articles/flash_socket_policy.html
* [websocket test] Fix sending message to client upon connecting
* [websocket test] Fix for connection and handshake test
* [client files serving] Leverage end() write() call
* [client serving] Make sure to not do a useless file lookup when file is cached
* Finished json encoding test
* Look for the heartbeat in the decoded message
* Refactored websocket transports tests to match polling/multipart helpers
* Added coverage testing to Makefile
* Added heartbeat test to multipart
* Added buffered messages test for multipart
* Added assertions for `connected` property for all the tests
* Multipart clients tracking test
* Multipart client>server message sending test
* Make sure to only close the client stream when the roundtrip is complete
* Multipart connection and handshake tests:
- Implemented HTTP client on top of net.Stream with multipart boundary parsing for testing
- Test for connection / server>client message sending
* Removed unnecessary check for this.connection (since we now access the socket through req.connection for all transports)
* Test for `duration` parameter
* Added `make example` to Makefile
* Added clients tracking test for long polling
* Added message buffering test for long polling
* Improve this.request/this.response/this.connection
* Add 'end' listener onConnect, applies to all transports
* Improved error handling onConnect
* Remove legacy `flush` calls
* Removed unnecessary closeTimeout clearing in jsonp polling
* Make sure to close on disconnect if _open = true
* Clear disconnection timeout on disconnection (double check)
* Make sure to clear closeTimeout for polling transports on close.
* Replaced empty with null in log option
* Comma first style for client serving tests
* Long polling integration tests
* Test for heartbeat message
* Added heartbeat timeout test
* Support for listener#log false
* Corrected onConnect signature to support a request and a socket, or a request and a response.
* Removed error checking for non-upgradeable sockets, since they'll be destroyed, and error handling is done onConnect
* Added tests for websocket client tracking
* Added tests for websocket message buffering
* Make sure disconnect timeout is cleared on websocket re-connect
* Updated the flash socket with error detection, and readystate detection.
* This is needed because when a error occures we close down the connection,
* and the stream will become unwriteable.
* Also changed to a single write instead of multiple writes.
* Moved error handling to onConnect to avoid messing with the http.Server global error handlers
* Do special error handling for websocket
* Clearing heartbeat interval upon closing the connection
* Added error listeners, if theses errors are not correcly caught, they will leak memory.
* This caused http://speedo.no.de/ to go up from 1mb per connection after a ECONNECTRESET message
* Added encode=UTF-8 in jsonp-polling.js and xhr-polling.js since UTF-8 is the default encoding for http.ServerResponse.write
* Replaced string.length with Buffer.byteLength in jsonp-polling.js, listener.js and xhr-polling.js because content-length header requires number of bytes and not the number of symbols in string
* Fix COR headers/requests for different ports on Safari.
* Clearing the references to request, response and connection upon disconnect.
* Every require is blocking and requiring the sys module over and over and over again just makes no sense + it hurt performance.. Not to mention.. that it's already included.
* Socket.IO-node now serves the client out of the box for easier implementation
* Memory caching and ETag support for static files
* Tests
* Simplified demo even further thanks to new static file serving
* Failing to pass an origin header would throw an exception and crash the server. Added some handling.
* .connected renamed to ._open, and adopted proper `connected` (fixes #41)
* example/client updated to latest socket.io client
* Better checking of WebSocket connections
* Better handling of SSL location (thanks @jdub)
* Fix for cross-domain websocket (fixes #42)
* Removed clients/clientsIndex and only using the index (fixes #28)
* Fixed WebSocket location header for ws/wss (Thanks @jdub, Fixes #40)
* Cross domain issues with xhr-polling addressed. Thanks Niko Kaiser (@nicokaiser)
* Added origin verification for incoming data.
* Make sure pathname is set (thanks steadicat & swarmation team)
* Fix for accessing routes that being with the namespace but are not a connection attempt. Thanks @steadicat from swarmation
* JSONP-polling support
* Graceful closing of connection for invalid websocket clients
* Make it possible to just require 'socket.io'
* Make sure to abort the connect() method upon bad upgrade / origin verification
* Support for automatic JSON encoding/decoding
* Simplified chat example to take advantage of JSON encoding/decoding
* Removed fs sync call from example
* Better `how to use`
* Make sure to send content-type text/plain to `ok` POST responses

View File

@@ -7,4 +7,7 @@ test-cov:
example:
node ./example/server.js
.PHONY: example
example-ssl:
node ./example/server-ssl.js
.PHONY: example

View File

@@ -1,7 +1,7 @@
Socket.IO Server: Sockets for the rest of us
============================================
The `Socket.IO` server provides seamless supports for a variety of transports intended for realtime communication
The `Socket.IO` server provides seamless support for a variety of transports intended for realtime communication.
- WebSocket
- WebSocket over Flash (+ XML security policy support)
@@ -12,33 +12,35 @@ The `Socket.IO` server provides seamless supports for a variety of transports in
## Requirements
- Node v0.1.103+
- [Socket.IO client](http://github.com/LearnBoost/Socket.IO) to connect from the browser
- Node v0.1.103+ with `crypto` module support (make sure you have OpenSSL
headers when installing Node to get it)
- The [Socket.IO client](http://github.com/LearnBoost/Socket.IO), to connect from the browser
## How to use
To run the demo:
To run the demo, execute the following:
git clone git://github.com/LearnBoost/Socket.IO-node.git socket.io
cd socket.io/example/
sudo node server.js
and point your browser to http://localhost:8080. In addition to 8080, if the transport `flashsocket` is enabled, a server will be initialized to listen to requests on the port 843.
and point your browser to `http://localhost:8080`. In addition to `8080`, if the transport `flashsocket` is enabled, a server will be initialized to listen for requests on port `843`.
### Implementing it on your project
`Socket.IO` is designed not to take over an entire port or Node `http.Server` instance. This means that if you choose your HTTP server to listen on the port 80, `socket.io` can intercept requests directed to it and the normal requests will still be served.
`Socket.IO` is designed not to take over an entire port or Node `http.Server` instance. This means that if you choose to have your HTTP server listen on port `80`, `socket.io` can intercept requests directed to it, and normal requests will still be served.
By default, the server will intercept requests that contain `socket.io` in the path / resource part of the URI. You can change this (look at the available options below).
By default, the server will intercept requests that contain `socket.io` in the path / resource part of the URI. You can change this as shown in the available options below.
On the server:
var http = require('http'),
io = require('./path/to/socket.io'),
server = http.createServer(function(req, res){
// your normal server code
res.writeHeader(200, {'Content-Type': 'text/html'});
res.writeBody('<h1>Hello world</h1>');
res.finish();
res.writeHead(200, {'Content-Type': 'text/html'});
res.end('<h1>Hello world</h1>');
});
server.listen(80);
@@ -52,17 +54,18 @@ By default, the server will intercept requests that contain `socket.io` in the p
client.on('disconnect', function(){ … })
});
On the client side:
On the client:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = new io.Socket();
socket.connect();
socket.on('connect', function(){ … })
socket.on('message', function(){ … })
socket.on('disconnect', function(){ … })
</script>
The [client side](http://github.com/learnboost/socket.io) files will be served automatically by `Socket.IO-node`.
The [client-side](http://github.com/learnboost/socket.io) files are served automatically by `Socket.IO-node`.
## Documentation
@@ -76,15 +79,15 @@ Public Properties:
- *server*
The instance of _process.http.Server_
An instance of _process.http.Server_.
- *options*
The passed in options combined with the defaults
The passed-in options, combined with the defaults.
- *clients*
An object of clients indexed by their session ids.
An object of clients, indexed by session ID.
Methods:
@@ -94,11 +97,11 @@ Methods:
- *removeListener(event, λ)*
Remove a listener from the listener array for the specified event.
Removes a listener from the listener array for the specified event.
- *broadcast(message, [except])*
Broadcasts a message to all clients. There's an optional second argument which is an array of session ids or a single session id to avoid broadcasting to.
Broadcasts a message to all clients. Optionally, you can pass a single session ID or array of session IDs to avoid broadcasting to, as the second argument.
Options:
@@ -106,11 +109,19 @@ Options:
socket.io
The resource is what allows the `socket.io` server to identify incoming connections by `socket.io` clients. Make sure they're in sync.
The resource is what allows the `socket.io` server to identify incoming connections from `socket.io` clients. Make sure they're in sync.
- *flashPolicyServer*
true
Create a Flash Policy file server on port `843` (this is restricted port and you will need to have root permission). If you disable the FlashPolicy file server, Socket.IO will automatically fall back to serving the policy file inline.
- *transports*
['websocket', 'server-events', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling']
['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling',
'jsonp-polling']
A list of the accepted transports.
@@ -122,21 +133,21 @@ Options:
ƒ(){ sys.log }
The logging function. Defaults to outputting to stdout through `sys.log`
The logging function. Defaults to outputting to `stdout` through `sys.log`
Events:
- *clientConnect(client)*
Fired when a client is connected. Receives the Client instance as parameter
Fired when a client is connected. Receives the Client instance as parameter.
- *clientMessage(message, client)*
Fired when a message from a client is received. Receives the message and Client instance as parameter
Fired when a message from a client is received. Receives the message and Client instance as parameters.
- *clientDisconnect(client)*
Fired when a client is disconnected. Receives the Client instance as parameter
Fired when a client is disconnected. Receives the Client instance as a parameter.
Important note: `this` in the event listener refers to the `Listener` instance.
@@ -148,43 +159,43 @@ Public Properties:
- *listener*
The `Listener` instance this client belongs to.
The `Listener` instance to which this client belongs.
- *connected*
Whether the client is connected
Whether the client is connected.
- *connections*
Number of times the client connected
Number of times the client has connected.
Methods:
- *send(message)*
Sends a message to the client
Sends a message to the client.
- *broadcast(message)*
Sends a message to all other clients. Equivalent to Listener::broadcast(message, client.sessionId)
Sends a message to all other clients. Equivalent to Listener::broadcast(message, client.sessionId).
## Protocol
One of the design goals is that you should be able to implement whatever protocol you desire without `Socket.IO` getting in the way. `Socket.IO` has a minimal, unobtrusive protocol layer. It consists of two parts:
One of the design goals is that you should be able to implement whatever protocol you desire without `Socket.IO` getting in the way. `Socket.IO` has a minimal, unobtrusive protocol layer, consisting of two parts:
* Connection handshake
This is required to simulate a full duplex socket with transports such as XHR Polling or Server-sent Events (which is a "one-way socket"). The basic idea is that the first message received from the server will be a JSON object that contains a session id that will be used for further communication exchanged between the client and the server.
This is required to simulate a full duplex socket with transports such as XHR Polling or Server-sent Events (which is a "one-way socket"). The basic idea is that the first message received from the server will be a JSON object that contains a session ID used for further communications exchanged between the client and server.
The concept of session also benefits naturally full-duplex WebSocket, in the event of an accidental disconnection and a quick reconnection. Messages that the server intends to deliver to the client are cached temporarily until the reconnection.
The concept of session also naturally benefits a full-duplex WebSocket, in the event of an accidental disconnection and a quick reconnection. Messages that the server intends to deliver to the client are cached temporarily until reconnection.
The implementation of reconnection logic (potentially with retries) is left for the user. By default, transports that are keep-alive or open all the time (like WebSocket) have a timeout of 0 if a disconnection is detected.
* Message batching
In order to optimize the resources, messages are buffered. In the event of the server trying to send multiple messages while the client is temporarily disconnected (eg: xhr polling), messages are stacked, then encoded in a lightweight way and sent to the client whenever he becomes available.
Messages are buffered in order to optimize resources. In the event of the server trying to send multiple messages while a client is temporarily disconnected (eg: xhr polling), the messages are stacked and then encoded in a lightweight way, and sent to the client whenever it becomes available.
Despite this extra layer, your messages are delivered unaltered to the different event listeners. You can JSON.stringify() objects, send XML, or maybe plain text.
Despite this extra layer, the messages are delivered unaltered to the various event listeners. You can `JSON.stringify()` objects, send XML, or even plain text.
## Credits
@@ -215,4 +226,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

21
example/cert.crt Normal file
View File

@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAMUSOvlaeyQHMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTAxMTE2MDkzMjQ5WhcNMTMxMTE1MDkzMjQ5WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAz+LXZOjcQCJq3+ZKUFabj71oo/ex/XsBcFqtBThjjTw9CVEVwfPQQp4X
wtPiB204vnYXwQ1/R2NdTQqCZu47l79LssL/u2a5Y9+0NEU3nQA5qdt+1FAE0c5o
exPimXOrR3GWfKz7PmZ2O0117IeCUUXPG5U8umhDe/4mDF4ZNJiKc404WthquTqg
S7rLQZHhZ6D0EnGnOkzlmxJMYPNHSOY1/6ivdNUUcC87awNEA3lgfhy25IyBK3QJ
c+aYKNTbt70Lery3bu2wWLFGtmNiGlQTS4JsxImRsECTI727ObS7/FWAQsqW+COL
0Sa5BuMFrFIpjPrEe0ih7vRRbdmXRwIDAQABo1AwTjAdBgNVHQ4EFgQUDnV4d6mD
tOnluLoCjkUHTX/n4agwHwYDVR0jBBgwFoAUDnV4d6mDtOnluLoCjkUHTX/n4agw
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAFwV4MQfTo+qMv9JMiyno
IEiqfOz4RgtmBqRnXUffcjS2dhc7/z+FPZnM79Kej8eLHoVfxCyWRHFlzm93vEdv
wxOCrD13EDOi08OOZfxWyIlCa6Bg8cMAKqQzd2OvQOWqlRWBTThBJIhWflU33izX
Qn5GdmYqhfpc+9ZHHGhvXNydtRQkdxVK2dZNzLBvBlLlRmtoClU7xm3A+/5dddeP
AQHEPtyFlUw49VYtZ3ru6KqPms7MKvcRhYLsy9rwSfuuniMlx4d0bDR7TOkw0QQS
A0N8MGQRQpzl4mw4jLzyM5d5QtuGBh2P6hPGa0YQxtI3RPT/p6ENzzBiAKXiSfzo
xw==
-----END CERTIFICATE-----

61
example/chat-ssl.html Normal file
View File

@@ -0,0 +1,61 @@
<!doctype html>
<html>
<head>
<title>socket.io client test</title>
<script src="/json.js"></script> <!-- for ie -->
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<script>
function message(obj){
var el = document.createElement('p');
if ('announcement' in obj) el.innerHTML = '<em>' + esc(obj.announcement) + '</em>';
else if ('message' in obj) el.innerHTML = '<b>' + esc(obj.message[0]) + ':</b> ' + esc(obj.message[1]);
document.getElementById('chat').appendChild(el);
document.getElementById('chat').scrollTop = 1000000;
}
function send(){
var val = document.getElementById('text').value;
socket.send(val);
message({ message: ['you', val] });
document.getElementById('text').value = '';
}
function esc(msg){
return msg.replace(/</g, '&lt;').replace(/>/g, '&gt;');
};
var socket = new io.Socket(null, {port: 443, secure: true, rememberTransport: false});
socket.connect();
socket.on('message', function(obj){
if ('buffer' in obj){
document.getElementById('form').style.display='block';
document.getElementById('chat').innerHTML = '';
for (var i in obj.buffer) message(obj.buffer[i]);
} else message(obj);
});
</script>
<h1>Sample chat client</h1>
<div id="chat"><p>Connecting...</p></div>
<form id="form" onsubmit="send(); return false">
<input type="text" autocomplete="off" id="text"><input type="submit" value="Send">
</form>
<style>
#chat { height: 300px; overflow: auto; width: 800px; border: 1px solid #eee; font: 13px Helvetica, Arial; }
#chat p { padding: 8px; margin: 0; }
#chat p:nth-child(odd) { background: #F6F6F6; }
#form { width: 782px; background: #333; padding: 5px 10px; display: none; }
#form input[type=text] { width: 700px; padding: 5px; background: #fff; border: 1px solid #fff; }
#form input[type=submit] { cursor: pointer; background: #999; border: none; padding: 6px 8px; -moz-border-radius: 8px; -webkit-border-radius: 8px; margin-left: 5px; text-shadow: 0 1px 0 #fff; }
#form input[type=submit]:hover { background: #A2A2A2; }
#form input[type=submit]:active { position: relative; top: 2px; }
</style>
</body>
</html>

View File

@@ -58,4 +58,4 @@
</style>
</body>
</html>
</html>

27
example/key.key Normal file
View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAz+LXZOjcQCJq3+ZKUFabj71oo/ex/XsBcFqtBThjjTw9CVEV
wfPQQp4XwtPiB204vnYXwQ1/R2NdTQqCZu47l79LssL/u2a5Y9+0NEU3nQA5qdt+
1FAE0c5oexPimXOrR3GWfKz7PmZ2O0117IeCUUXPG5U8umhDe/4mDF4ZNJiKc404
WthquTqgS7rLQZHhZ6D0EnGnOkzlmxJMYPNHSOY1/6ivdNUUcC87awNEA3lgfhy2
5IyBK3QJc+aYKNTbt70Lery3bu2wWLFGtmNiGlQTS4JsxImRsECTI727ObS7/FWA
QsqW+COL0Sa5BuMFrFIpjPrEe0ih7vRRbdmXRwIDAQABAoIBAGe4+9VqZfJN+dsq
8Osyuz01uQ8OmC0sAWTIqUlQgENIyf9rCJsUBlYmwR5BT6Z69XP6QhHdpSK+TiAR
XUz0EqG9HYzcxHIBaACP7j6iRoQ8R4kbbiWKo0z3WqQGIOqFjvD/mKEuQdE5mEYw
eOUCG6BnX1WY2Yr8WKd2AA/tp0/Y4d8z04u9eodMpSTbHTzYMJb5SbBN1vo6FY7q
8zSuO0BMzXlAxUsCwHsk1GQHFr8Oh3zIR7bQGtMBouI+6Lhh7sjFYsfxJboqMTBV
IKaA216M6ggHG7MU1/jeKcMGDmEfqQLQoyWp29rMK6TklUgipME2L3UD7vTyAVzz
xbVOpZkCgYEA8CXW4sZBBrSSrLR5SB+Ubu9qNTggLowOsC/kVKB2WJ4+xooc5HQo
mFhq1v/WxPQoWIxdYsfg2odlL+JclK5Qcy6vXmRSdAQ5lK9gBDKxZSYc3NwAw2HA
zyHCTK+I0n8PBYQ+yGcrxu0WqTGnlLW+Otk4CejO34WlgHwbH9bbY5UCgYEA3ZvT
C4+OoMHXlmICSt29zUrYiL33IWsR3/MaONxTEDuvgkOSXXQOl/8Ebd6Nu+3WbsSN
bjiPC/JyL1YCVmijdvFpl4gjtgvfJifs4G+QHvO6YfsYoVANk4u6g6rUuBIOwNK4
RwYxwDc0oysp+g7tPxoSgDHReEVKJNzGBe9NGGsCgYEA4O4QP4gCEA3B9BF2J5+s
n9uPVxmiyvZUK6Iv8zP4pThTBBMIzNIf09G9AHPQ7djikU2nioY8jXKTzC3xGTHM
GJZ5m6fLsu7iH+nDvSreDSeNkTBfZqGAvoGYQ8uGE+L+ZuRfCcXYsxIOT5s6o4c3
Dle2rVFpsuKzCY00urW796ECgYBn3go75+xEwrYGQSer6WR1nTgCV29GVYXKPooy
zmmMOT1Yw80NSkEw0pFD4cTyqVYREsTrPU0mn1sPfrOXxnGfZSVFpcR/Je9QVfQ7
eW7GYxwfom335aqHVj10SxRqteP+UoWWnHujCPz94VRKZMakBddYCIGSan+G6YdS
7sdmwwKBgBc2qj0wvGXDF2kCLwSGfWoMf8CS1+5fIiUIdT1e/+7MfDdbmLMIFVjF
QKS3zVViXCbrG5SY6wS9hxoc57f6E2A8vcaX6zy2xkZlGHQCpWRtEM5R01OWJQaH
HsHMmQZGUQVoDm1oRkDhrTFK4K3ukc3rAxzeTZ96utOQN8/KJsTv
-----END RSA PRIVATE KEY-----

66
example/server-ssl.js Normal file
View File

@@ -0,0 +1,66 @@
/**
* Important note: this application is not suitable for benchmarks!
*/
var https = require('https')
, url = require('url')
, fs = require('fs')
, io = require('../')
, sys = require(process.binding('natives').util ? 'util' : 'sys')
, server;
server = https.createServer({
key: fs.readFileSync(__dirname + '/key.key')
, cert: fs.readFileSync(__dirname + '/cert.crt')
}, function(req, res){
// your normal server code
var path = url.parse(req.url).pathname;
switch (path){
case '/':
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Welcome. Try the <a href="/chat-ssl.html">SSL Chat</a> example.</h1>');
res.end();
break;
case '/json.js':
case '/chat-ssl.html':
fs.readFile(__dirname + path, function(err, data){
if (err) return send404(res);
res.writeHead(200, {'Content-Type': path == 'json.js' ? 'text/javascript' : 'text/html'})
res.write(data, 'utf8');
res.end();
});
break;
default: send404(res);
}
}),
send404 = function(res){
res.writeHead(404);
res.write('404');
res.end();
};
server.listen(443);
// socket.io, I choose you
// simplest chat application evar
var io = io.listen(server)
, buffer = [];
io.on('connection', function(client){
client.send({ buffer: buffer });
client.broadcast({ announcement: client.sessionId + ' connected' });
client.on('message', function(message){
var msg = { message: [client.sessionId, message] };
buffer.push(msg);
if (buffer.length > 15) buffer.shift();
client.broadcast(msg);
});
client.on('disconnect', function(){
client.broadcast({ announcement: client.sessionId + ' disconnected' });
});
});

View File

@@ -1,58 +1,63 @@
var http = require('http'),
url = require('url'),
fs = require('fs'),
io = require('../'),
sys = require('sys'),
/**
* Important note: this application is not suitable for benchmarks!
*/
var http = require('http')
, url = require('url')
, fs = require('fs')
, io = require('../')
, sys = require(process.binding('natives').util ? 'util' : 'sys')
, server;
server = http.createServer(function(req, res){
// your normal server code
var path = url.parse(req.url).pathname;
switch (path){
case '/':
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Welcome. Try the <a href="/chat.html">chat</a> example.</h1>');
res.end();
break;
case '/json.js':
case '/chat.html':
fs.readFile(__dirname + path, function(err, data){
if (err) return send404(res);
res.writeHead(200, {'Content-Type': path == 'json.js' ? 'text/javascript' : 'text/html'})
res.write(data, 'utf8');
res.end();
});
break;
default: send404(res);
}
// your normal server code
var path = url.parse(req.url).pathname;
switch (path){
case '/':
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Welcome. Try the <a href="/chat.html">chat</a> example.</h1>');
res.end();
break;
case '/json.js':
case '/chat.html':
fs.readFile(__dirname + path, function(err, data){
if (err) return send404(res);
res.writeHead(200, {'Content-Type': path == 'json.js' ? 'text/javascript' : 'text/html'})
res.write(data, 'utf8');
res.end();
});
break;
default: send404(res);
}
}),
send404 = function(res){
res.writeHead(404);
res.write('404');
res.end();
res.writeHead(404);
res.write('404');
res.end();
};
server.listen(8080);
// socket.io, I choose you
// simplest chat application evar
var io = io.listen(server),
buffer = [];
var io = io.listen(server)
, buffer = [];
io.on('connection', function(client){
client.send({ buffer: buffer });
client.broadcast({ announcement: client.sessionId + ' connected' });
client.send({ buffer: buffer });
client.broadcast({ announcement: client.sessionId + ' connected' });
client.on('message', function(message){
var msg = { message: [client.sessionId, message] };
buffer.push(msg);
if (buffer.length > 15) buffer.shift();
client.broadcast(msg);
});
client.on('message', function(message){
var msg = { message: [client.sessionId, message] };
buffer.push(msg);
if (buffer.length > 15) buffer.shift();
client.broadcast(msg);
});
client.on('disconnect', function(){
client.broadcast({ announcement: client.sessionId + ' disconnected' });
});
});
client.on('disconnect', function(){
client.broadcast({ announcement: client.sessionId + ' disconnected' });
});
});

View File

@@ -4,7 +4,8 @@ var urlparse = require('url').parse
, options = require('./utils').options
, encode = require('./utils').encode
, decode = require('./utils').decode
, merge = require('./utils').merge;
, merge = require('./utils').merge
, util = require(process.binding('natives').util ? 'util' : 'sys');
var Client = module.exports = function(listener, req, res, options, head){
process.EventEmitter.call(this);
@@ -22,7 +23,7 @@ var Client = module.exports = function(listener, req, res, options, head){
this._onConnect(req, res);
};
require('sys').inherits(Client, process.EventEmitter);
util.inherits(Client, process.EventEmitter);
Client.prototype.send = function(message){
if (!this._open || !(this.connection.readyState === 'open' || this.connection.readyState === 'writeOnly')){
@@ -47,7 +48,11 @@ Client.prototype._onMessage = function(data){
case '~h~':
return this._onHeartbeat(messages[i].substr(3));
case '~j~':
messages[i] = JSON.parse(messages[i].substr(3));
try {
messages[i] = JSON.parse(messages[i].substr(3));
} catch(e) {
messages[i] = {};
}
break;
}
this.emit('message', messages[i]);
@@ -61,21 +66,22 @@ Client.prototype._onConnect = function(req, res){
this.request = req;
this.response = res;
this.connection = req.connection;
this.connection.addListener('end', function(){
self._onClose();
self.connection.end();
if (self.connection)
self.connection.destroy();
});
if (req){
req.addListener('error', function(err){
req.end && req.end() || req.destroy && req.destroy();
req.destroy && req.destroy();
});
if (res) res.addListener('error', function(err){
res.end && res.end() || res.destroy && res.destroy();
res.destroy && res.destroy();
});
req.connection.addListener('error', function(err){
req.connection.end && req.connection.end() || req.connection.destroy && req.connection.destroy();
req.connection.destroy && req.connection.destroy();
});
if (this._disconnectTimeout) clearTimeout(this._disconnectTimeout);
@@ -100,7 +106,6 @@ Client.prototype._payload = function(){
if (payload.length) this._write(encode(payload));
if (this.connections === 1) this.listener._onClientConnect(this);
if (this.options.timeout) this._heartbeat();
};
@@ -121,36 +126,33 @@ Client.prototype._onHeartbeat = function(h){
}
};
Client.prototype._onClose = function(){
if (this._open){
var self = this;
if ('_heartbeatInterval' in this) clearTimeout(this._heartbeatInterval);
if ('_heartbeatTimeout' in this) clearTimeout(this._heartbeatTimeout);
this._open = false;
this._disconnectTimeout = setTimeout(function(){
self._onDisconnect();
}, this.options.closeTimeout);
Client.prototype._onClose = function(skipDisconnect){
if (!this._open) return this;
var self = this;
if (this._heartbeatInterval) clearTimeout(this._heartbeatInterval);
if (this._heartbeatTimeout) clearTimeout(this._heartbeatTimeout);
this._open = false;
this.request = null;
this.response = null;
if (skipDisconnect !== false){
if (this.handshaked){
this._disconnectTimeout = setTimeout(function(){
self._onDisconnect();
}, this.options.closeTimeout);
} else
this._onDisconnect();
}
};
Client.prototype._onDisconnect = function(){
if (this._open) this._onClose();
if (this._open) this._onClose(true);
if (this._disconnectTimeout) clearTimeout(this._disconnectTimeout);
if (this.connected){
this._writeQueue = [];
this._open = false;
this.connected = false;
this.request = null;
this.response = null;
if (this.connection){
this.connection.end();
this.connection.destroy();
this.connection = null;
}
if (this.handshaked){
this.emit('disconnect');
this.listener._onClientDisconnect(this);
}
this._writeQueue = [];
this.connected = false;
if (this.handshaked){
this.emit('disconnect');
this.listener._onClientDisconnect(this);
this.handshaked = false;
}
};
@@ -161,7 +163,6 @@ Client.prototype._queue = function(message){
};
Client.prototype._generateSessionId = function(){
if (this.sessionId) return this.listener.options.log('This client already has a session id');
this.sessionId = Math.random().toString().substr(2);
return this;
};
@@ -182,4 +183,4 @@ Client.prototype._verifyOrigin = function(origin){
return false;
};
for (var i in options) Client.prototype[i] = options[i];
for (var i in options) Client.prototype[i] = options[i];

View File

@@ -1,4 +1,26 @@
exports.Listener = require('./listener');
/**
* Listener creation shorcut
*
* @param {Server} node HTTP server
* @param {Object} options
* @api public
*/
exports.listen = function(server, options){
return new exports.Listener(server, options);
};
};
/**
* Listener constructor
*
* @api public
*/
exports.Listener = require('./listener');
/**
* Version
*/
exports.version = '0.6.12';

View File

@@ -1,17 +1,10 @@
var url = require('url')
, sys = require('sys')
, util = require(process.binding('natives').util ? 'util' : 'sys')
, fs = require('fs')
, options = require('./utils').options
, Client = require('./client')
, clientVersion = require('./../../support/socket.io-client/lib/io').io.version
, transports = {
'flashsocket': require('./transports/flashsocket')
, 'htmlfile': require('./transports/htmlfile')
, 'websocket': require('./transports/websocket')
, 'xhr-multipart': require('./transports/xhr-multipart')
, 'xhr-polling': require('./transports/xhr-polling')
, 'jsonp-polling': require('./transports/jsonp-polling')
};
, transports = {};
var Listener = module.exports = function(server, options){
process.EventEmitter.call(this);
@@ -20,14 +13,17 @@ var Listener = module.exports = function(server, options){
this.options({
origins: '*:*',
resource: 'socket.io',
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling', 'jsonp-polling'],
flashPolicyServer: true,
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart',
'xhr-polling', 'jsonp-polling'],
transportOptions: {},
log: sys.log
log: util.log
}, options);
if (!this.options.log) this.options.log = function(){};
this.clients = this.clientsIndex = {};
this._clientCount = 0;
this._clientFiles = {};
var listeners = this.server.listeners('request');
@@ -47,18 +43,22 @@ var Listener = module.exports = function(server, options){
}
});
for (var i in transports)
if ('init' in transports[i]) transports[i].init(this);
this.options.transports.forEach(function(name) {
if (!(name in transports))
transports[name] = require('./transports/' + name);
if ('init' in transports[name]) transports[name].init(self);
});
this.options.log('socket.io ready - accepting connections');
};
sys.inherits(Listener, process.EventEmitter);
util.inherits(Listener, process.EventEmitter);
for (var i in options) Listener.prototype[i] = options[i];
Listener.prototype.broadcast = function(message, except){
for (var i = 0, k = Object.keys(this.clients), l = k.length; i < l; i++){
if (this.clients[k[i]] && (!except || [].concat(except).indexOf(this.clients[k[i]].sessionId) == -1)){
if (!except || ((typeof except == 'number' || typeof except == 'string') && k[i] != except)
|| (Array.isArray(except) && except.indexOf(k[i]) == -1)){
this.clients[k[i]].send(message);
}
}
@@ -68,20 +68,20 @@ Listener.prototype.broadcast = function(message, except){
Listener.prototype.check = function(req, res, httpUpgrade, head){
var path = url.parse(req.url).pathname, parts, cn;
if (path && path.indexOf('/' + this.options.resource) === 0){
parts = path.substr(1).split('/');
if (this._serveClient(parts.slice(1).join('/'), req, res)) return true;
if (!(parts[1] in transports)) return false;
if (parts[2]){
cn = this.clients[parts[2]];
parts = path.substr(2 + this.options.resource.length).split('/');
if (this._serveClient(parts.join('/'), req, res)) return true;
if (!(parts[0] in transports)) return false;
if (parts[1]){
cn = this.clients[parts[1]];
if (cn){
cn._onConnect(req, res);
} else {
req.connection.end();
req.connection.destroy();
this.options.log('Couldnt find client with session id "' + parts[2] + '"');
this.options.log('Couldnt find client with session id "' + parts[1] + '"');
}
} else {
this._onConnection(parts[1], req, res, httpUpgrade, head);
this._onConnection(parts[0], req, res, httpUpgrade, head);
}
return true;
}
@@ -163,4 +163,4 @@ Listener.prototype._onClientDisconnect = function(client){
Listener.prototype._onConnection = function(transport, req, res, httpUpgrade, head){
this.options.log('Initializing client with transport "'+ transport +'"');
new transports[transport](this, req, res, this.options.transportOptions[transport], head);
};
};

View File

@@ -1,36 +1,81 @@
var net = require('net')
, util = require(process.binding('natives').util ? 'util' : 'sys')
, WebSocket = require('./websocket')
, listeners = []
, netserver = null;
, netserver;
var Flashsocket = module.exports = function(){
WebSocket.apply(this, arguments);
};
require('sys').inherits(Flashsocket, WebSocket);
util.inherits(Flashsocket, WebSocket);
Flashsocket.httpUpgrade = true;
Flashsocket.init = function(listener){
listeners.push(listener);
listener.server.on('close', function(){
listeners.splice(listeners.indexOf(listener), 1);
if (listeners.length === 0 && netserver){
try {
netserver.close();
} catch(e){
listener.options.log('flashsocket netserver close error - ' + e.stack)
}
}
});
if (listener.options.flashPolicyServer && netserver === undefined){
netserver = net.createServer(function(socket){
socket.addListener('error', function(err){
if (socket && socket.end){
socket.end();
socket.destroy();
}
});
if(socket && socket.readyState == 'open')
socket.end(policy(listeners));
});
try {
netserver.listen(843);
} catch(e){
if (e.errno == 13)
listener.options.log('Your node instance does not have root privileges. '
+ 'This means that the flash XML policy file will be '
+ 'served inline instead of on port 843. This will slow '
+ 'down initial connections slightly.');
netserver = null;
}
}
// Could not listen on port 843 so policy requests will be inline
listener.server.addListener('connection', function(stream){
var flashCheck = function (data) {
// Only check the initial data
stream.removeListener('data', flashCheck);
if (data[0] === 60 && data.length == 23 && data == '<policy-file-request/>\0'){
if (stream && stream.readyState == 'open'){
var xml = policy([listener]);
stream.write(xml);
stream.end();
stream.removeListener("data", flashCheck);
if (data[0] === 60 && data.length == 23) {
if (data == '<policy-file-request/>\0') {
listener.options.log("Answering flash policy request inline");
if (stream && stream.readyState == 'open'){
var xml = policy([listener]);
stream.write(xml);
stream.end();
}
}
}
};
stream.on("data", flashCheck);
stream.on('data', flashCheck);
});
};
function policy(listeners) {
var xml = '<?xml version="1.0"?>\n<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">\n<cross-domain-policy>\n';
var xml = '<?xml version="1.0"?>\n<!DOCTYPE cross-domain-policy SYSTEM'
+ ' "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">\n<cross-domain-policy>\n';
listeners.forEach(function(l){
[].concat(l.options.origins).forEach(function(origin){
@@ -41,4 +86,4 @@ function policy(listeners) {
xml += '</cross-domain-policy>\n';
return xml;
};
};

View File

@@ -1,11 +1,12 @@
var Client = require('../client')
, util = require(process.binding('natives').util ? 'util' : 'sys')
, qs = require('querystring');
var HTMLFile = module.exports = function(){
Client.apply(this, arguments);
};
require('sys').inherits(HTMLFile, Client);
util.inherits(HTMLFile, Client);
HTMLFile.prototype._onConnect = function(req, res){
var self = this, body = '';
@@ -31,7 +32,9 @@ HTMLFile.prototype._onConnect = function(req, res){
try {
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){}
} catch(e){
listener.options.log('htmlfile message handler error - ' + e.stack);
}
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('ok');
res.end();
@@ -41,5 +44,6 @@ HTMLFile.prototype._onConnect = function(req, res){
};
HTMLFile.prototype._write = function(message){
this.response.write('<script>parent.s._('+ JSON.stringify(message) +', document);</script>'); //json for escaping
};
if (this._open)
this.response.write('<script>parent.s._('+ JSON.stringify(message) +', document);</script>'); //json for escaping
};

View File

@@ -1,10 +1,11 @@
var XHRPolling = require('./xhr-polling');
var XHRPolling = require('./xhr-polling')
, util = require(process.binding('natives').util ? 'util' : 'sys');
JSONPPolling = module.exports = function(){
XHRPolling.apply(this, arguments);
};
require('sys').inherits(JSONPPolling, XHRPolling);
util.inherits(JSONPPolling, XHRPolling);
JSONPPolling.prototype.getOptions = function(){
return {
@@ -20,13 +21,15 @@ JSONPPolling.prototype._onConnect = function(req, res){
};
JSONPPolling.prototype._write = function(message){
if (this.request.headers.origin && !this._verifyOrigin(this.request.headers.origin)){
message = "alert('Cross domain security restrictions not met');";
} else {
message = "io.JSONP["+ this._index +"]._("+ JSON.stringify(message) +");";
if (this._open){
if (this.request.headers.origin && !this._verifyOrigin(this.request.headers.origin)){
message = "alert('Cross domain security restrictions not met');";
} else {
message = "io.JSONP["+ this._index +"]._("+ JSON.stringify(message) +");";
}
this.response.writeHead(200, {'Content-Type': 'text/javascript; charset=UTF-8', 'Content-Length': Buffer.byteLength(message)});
this.response.write(message);
this.response.end();
this._onClose();
}
this.response.writeHead(200, {'Content-Type': 'text/javascript; charset=UTF-8', 'Content-Length': Buffer.byteLength(message)});
this.response.write(message);
this.response.end();
this._onClose();
};
};

View File

@@ -1,33 +1,59 @@
var Client = require('../client')
, Stream = require('net').Stream
, EventEmitter = require('events').EventEmitter
, url = require('url')
, util = require(process.binding('natives').util ? 'util' : 'sys')
, crypto = require('crypto');
WebSocket = module.exports = function(){
Client.apply(this, arguments);
};
require('sys').inherits(WebSocket, Client);
util.inherits(WebSocket, Client);
WebSocket.prototype._onConnect = function(req, socket){
var self = this
, headers = [];
if (!req.connection.setTimeout){
req.connection.end();
return false;
}
this.parser = new Parser();
this.parser.on('data', self._onMessage.bind(this));
this.parser.on('error', self._onClose.bind(this));
Client.prototype._onConnect.call(this, req);
this.data = '';
if (this.request.headers.upgrade !== 'WebSocket' || !this._verifyOrigin(this.request.headers.origin)){
this.listener.options.log('WebSocket connection invalid or Origin not verified');
this.connection.destroy();
this._onClose();
return false;
}
var origin = this.request.headers.origin,
location = (origin && origin.substr(0, 5) == 'https' ? 'wss' : 'ws')
+ '://' + this.request.headers.host + this.request.url;
this.waitingForNonce = false;
if ('sec-websocket-key1' in this.request.headers){
/* We need to send the 101 response immediately when using Draft 76 with
a load balancing proxy, such as HAProxy. In order to protect an
unsuspecting non-websocket HTTP server, HAProxy will not send the
8-byte nonce through the connection until the Upgrade: WebSocket
request has been confirmed by the WebSocket server by a 101 response
indicating that the server can handle the upgraded protocol. We
therefore must send the 101 response immediately, and then wait for
the nonce to be forwarded to us afterward in order to finish the
Draft 76 handshake.
*/
// If we don't have the nonce yet, wait for it.
if (!(this.upgradeHead && this.upgradeHead.length >= 8)) {
this.waitingForNonce = true;
}
headers = [
'HTTP/1.1 101 WebSocket Protocol Handshake',
'Upgrade: WebSocket',
@@ -48,46 +74,57 @@ WebSocket.prototype._onConnect = function(req, socket){
'WebSocket-Location: ' + location
];
}
try {
this.connection.write(headers.concat('', '').join('\r\n'));
this.connection.setTimeout(0);
this.connection.setNoDelay(true);
this.connection.setEncoding('utf-8');
try {
this.connection.write(headers.concat('', '').join('\r\n'));
} catch(e){
this._onClose();
}
} catch(e){
this._onClose();
return;
}
if (this.waitingForNonce) {
// Since we will be receiving the binary nonce through the normal HTTP
// data event, set the connection to 'binary' temporarily
this.connection.setEncoding('binary');
this._headers = headers;
}
else {
if (this._proveReception(headers)) this._payload();
}
this.buffer = "";
this.connection.addListener('data', function(data){
self._handle(data);
self.buffer += data;
if (self.waitingForNonce) {
if (self.buffer.length < 8) { return; }
// Restore the connection to utf8 encoding after receiving the nonce
self.connection.setEncoding('utf8');
self.waitingForNonce = false;
// Stuff the nonce into the location where it's expected to be
self.upgradeHead = self.buffer.substr(0,8);
self.buffer = self.buffer.substr(8);
if (self.buffer.length > 0) {
self.parser.add(self.buffer);
}
if (self._proveReception(self._headers)) { self._payload(); }
return;
}
self.parser.add(data);
});
if (this._proveReception(headers)) this._payload();
};
WebSocket.prototype._handle = function(data){
var chunk, chunks, chunk_count;
this.data += data;
chunks = this.data.split('\ufffd');
chunk_count = chunks.length - 1;
for (var i = 0; i < chunk_count; i++){
chunk = chunks[i];
if (chunk[0] !== '\u0000'){
this.listener.options.log('Data incorrectly framed by UA. Dropping connection');
this.connection.end();
this.connection.destroy();
return false;
}
this._onMessage(chunk.slice(1));
}
this.data = chunks[chunks.length - 1];
};
// http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake
WebSocket.prototype._proveReception = function(headers){
var k1 = this.request.headers['sec-websocket-key1'],
k2 = this.request.headers['sec-websocket-key2'];
var self = this
, k1 = this.request.headers['sec-websocket-key1']
, k2 = this.request.headers['sec-websocket-key2'];
if (k1 && k2){
var md5 = crypto.createHash('md5');
@@ -97,11 +134,8 @@ WebSocket.prototype._proveReception = function(headers){
spaces = k.replace(/[^ ]/g, '').length;
if (spaces === 0 || n % spaces !== 0){
if (this.listener && this.listener.options){
this.listener.options.log('Invalid WebSocket key: "' + k + '". Dropping connection');
}
this.connection.end();
this.connection.destroy();
self.listener.options.log('Invalid WebSocket key: "' + k + '". Dropping connection');
self._onClose();
return false;
}
@@ -117,7 +151,7 @@ WebSocket.prototype._proveReception = function(headers){
md5.update(this.upgradeHead.toString('binary'));
try {
this.connection.write(headers.concat('', '').join('\r\n') + md5.digest('binary'), 'binary');
this.connection.write(md5.digest('binary'), 'binary');
} catch(e){
this._onClose();
}
@@ -136,4 +170,41 @@ WebSocket.prototype._write = function(message){
}
};
WebSocket.httpUpgrade = true;
WebSocket.httpUpgrade = true;
function Parser(){
this.buffer = '';
this.i = 0;
};
Parser.prototype.__proto__ = EventEmitter.prototype;
Parser.prototype.add = function(data){
this.buffer += data;
this.parse();
};
Parser.prototype.parse = function(){
for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
chr = this.buffer[i];
if (i === 0){
if (chr != '\u0000')
this.error('Bad framing. Expected null byte as first frame');
else
continue;
}
if (chr == '\ufffd'){
this.emit('data', this.buffer.substr(1, this.buffer.length - 2));
this.buffer = this.buffer.substr(i + 1);
this.i = 0;
return this.parse();
}
}
};
Parser.prototype.error = function(reason){
this.buffer = '';
this.i = 0;
this.emit('error', reason);
return this;
};

View File

@@ -1,11 +1,12 @@
var Client = require('../client')
, util = require(process.binding('natives').util ? 'util' : 'sys')
, qs = require('querystring');
var Multipart = module.exports = function(){
Client.apply(this, arguments);
};
require('sys').inherits(Multipart, Client);
util.inherits(Multipart, Client);
Multipart.prototype._onConnect = function(req, res){
var self = this, body = '', headers = {};
@@ -45,7 +46,9 @@ Multipart.prototype._onConnect = function(req, res){
try {
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){}
} catch(e){
listener.options.log('xhr-multipart message handler error - ' + e.stack);
}
res.writeHead(200, headers);
res.write('ok');
res.end();
@@ -56,8 +59,9 @@ Multipart.prototype._onConnect = function(req, res){
};
Multipart.prototype._write = function(message){
this.response.write("Content-Type: text/plain" + (message.length === 1 && message.charCodeAt(0) === 6 ? "; charset=us-ascii" : "") + "\n\n");
this.response.write(message + "\n");
this.response.write("--socketio\n");
if ('flush' in this.response) this.response.flush();
};
if (this._open){
this.response.write("Content-Type: text/plain" + (message.length === 1 && message.charCodeAt(0) === 6 ? "; charset=us-ascii" : "") + "\n\n");
this.response.write(message + "\n");
this.response.write("--socketio\n");
}
};

View File

@@ -1,11 +1,12 @@
var Client = require('../client')
, util = require(process.binding('natives').util ? 'util' : 'sys')
, qs = require('querystring');
var Polling = module.exports = function(){
Client.apply(this, arguments);
};
require('sys').inherits(Polling, Client);
util.inherits(Polling, Client);
Polling.prototype.getOptions = function(){
return {
@@ -47,7 +48,9 @@ Polling.prototype._onConnect = function(req, res){
// optimization: just strip first 5 characters here?
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){}
} catch(e){
listener.options.log('xhr-polling message handler error - ' + e.stack);
}
res.writeHead(200, headers);
res.write('ok');
res.end();
@@ -62,14 +65,16 @@ Polling.prototype._onClose = function(){
};
Polling.prototype._write = function(message){
var headers = {'Content-Type': 'text/plain; charset=UTF-8', 'Content-Length': Buffer.byteLength(message)};
// https://developer.mozilla.org/En/HTTP_Access_Control
if (this.request.headers.origin && this._verifyOrigin(this.request.headers.origin)){
headers['Access-Control-Allow-Origin'] = this.request.headers.origin;
if (this.request.headers.cookie) headers['Access-Control-Allow-Credentials'] = 'true';
if (this._open){
var headers = {'Content-Type': 'text/plain; charset=UTF-8', 'Content-Length': Buffer.byteLength(message)};
// https://developer.mozilla.org/En/HTTP_Access_Control
if (this.request.headers.origin && this._verifyOrigin(this.request.headers.origin)){
headers['Access-Control-Allow-Origin'] = this.request.headers.origin;
if (this.request.headers.cookie) headers['Access-Control-Allow-Credentials'] = 'true';
}
this.response.writeHead(200, headers);
this.response.write(message);
this.response.end();
this._onClose();
}
this.response.writeHead(200, headers);
this.response.write(message);
this.response.end();
this._onClose();
};
};

View File

@@ -1,6 +1,6 @@
{ "name" : "socket.io"
, "description" : "The cross-browser WebSocket"
, "version" : "0.6"
, "version" : "0.6.12"
, "author" : "LearnBoost"
, "licenses" :
[ { "type" : "MIT"

View File

@@ -1,4 +1,35 @@
0.7.2 / 2010-12-29
==================
* Fixed problem with `listen()` sometimes firing on the same tick [guillermo]
0.7.1 / 2010-12-28
==================
* Fixed `assert.request()` client logic into an issue() function, fired upon the `listen()` callback if the server doesn't have an assigned fd. [guillermo]
* Removed `--watch`
0.7.0 / 2010-11-19
==================
* Removed `assert` from test function signature
Just use `require('assert')` :) this will make integration
with libraries like [should](http://github.com/visionmedia/should) cleaner.
0.6.4 / 2010-11-02
==================
* Added regexp support to `assert.response()` headers
* Removed `waitForExit` code, causing issues
0.6.3 / 2010-11-02
==================
* Added `assert.response()` body RegExp support
* Fixed issue with _--serial_ not executing files sequentially. Closes #42
* Fixed hang when modules use `setInterval` - monitor running tests & force the process to quit after all have completed + timeout [Steve Mason]
0.6.2 / 2010-09-17
==================

View File

@@ -35,5 +35,27 @@ Install via npm:
$ npm install expresso
## License
(The MIT License)
Copyright (c) 2010 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
/*!
/*
* Expresso
* Copyright(c) TJ Holowaychuk <tj@vision-media.ca>
* (MIT Licensed)
@@ -23,7 +23,7 @@ var assert = require('assert'),
* Expresso version.
*/
var version = '0.6.2';
var version = '0.7.2';
/**
* Failure count.
@@ -62,12 +62,6 @@ var growl = false;
var port = 5555;
/**
* Watch mode.
*/
var watch = false;
/**
* Execute serially.
*/
@@ -80,6 +74,12 @@ var serial = false;
var timeout = 2000;
/**
* Quiet output.
*/
var quiet = false;
/**
* Usage documentation.
*/
@@ -88,9 +88,9 @@ var usage = ''
+ '[bold]{Usage}: expresso [options] <file ...>'
+ '\n'
+ '\n[bold]{Options}:'
+ '\n -w, --watch Watch for modifications and re-execute tests'
+ '\n -g, --growl Enable growl notifications'
+ '\n -c, --coverage Generate and report test coverage'
+ '\n -q, --quiet Suppress coverage report if 100%'
+ '\n -t, --timeout MS Timeout in milliseconds, defaults to 2000'
+ '\n -r, --require PATH Require the given module path'
+ '\n -o, --only TESTS Execute only the comma sperated TESTS (can be set several times)'
@@ -171,14 +171,14 @@ while (args.length) {
run(files);
})
break;
case '-q':
case '--quiet':
quiet = true;
break;
case '-b':
case '--boring':
boring = true;
break;
case '-w':
case '--watch':
watch = true;
break;
case '-g':
case '--growl':
growl = true;
@@ -339,6 +339,27 @@ assert.length = function(val, n, msg) {
*/
assert.response = function(server, req, res, msg){
// Check that the server is ready or defer
if (!server.fd) {
if (!('__deferred' in server)) {
server.__deferred = [];
}
server.__deferred.push(arguments);
if (!server.__started) {
server.listen(server.__port = port++, '127.0.0.1', function(){
if (server.__deferred) {
process.nextTick(function(){
server.__deferred.forEach(function(args){
assert.response.apply(assert, args);
});
});
}
});
server.__started = true;
}
return;
}
// Callback as third or fourth arg
var callback = typeof res === 'function'
? res
@@ -357,84 +378,92 @@ assert.response = function(server, req, res, msg){
// Create client
if (!server.fd) {
server.listen(server.__port = port++, '127.0.0.1');
server.client = http.createClient(server.__port);
server.listen(server.__port = port++, '127.0.0.1', issue);
} else {
issue();
}
// Issue request
var timer,
client = server.client,
method = req.method || 'GET',
status = res.status || res.statusCode,
data = req.data || req.body,
timeout = req.timeout || 0;
function issue(){
if (!server.client)
server.client = http.createClient(server.__port);
var request = client.request(method, req.url, req.headers);
// Issue request
var timer,
client = server.client,
method = req.method || 'GET',
status = res.status || res.statusCode,
data = req.data || req.body,
requestTimeout = req.timeout || 0;
// Timeout
if (timeout) {
timer = setTimeout(function(){
--server.__pending || server.close();
delete req.timeout;
assert.fail(msg + 'Request timed out after ' + timeout + 'ms.');
}, timeout);
}
var request = client.request(method, req.url, req.headers);
if (data) request.write(data);
request.addListener('response', function(response){
response.body = '';
response.setEncoding('utf8');
response.addListener('data', function(chunk){ response.body += chunk; });
response.addListener('end', function(){
--server.__pending || server.close();
if (timer) clearTimeout(timer);
// Timeout
if (requestTimeout) {
timer = setTimeout(function(){
--server.__pending || server.close();
delete req.timeout;
assert.fail(msg + 'Request timed out after ' + requestTimeout + 'ms.');
}, requestTimeout);
}
// Assert response body
if (res.body !== undefined) {
var eql = res.body instanceof RegExp
? res.body.test(response.body)
: res.body === response.body;
assert.ok(
eql,
msg + 'Invalid response body.\n'
+ ' Expected: ' + sys.inspect(res.body) + '\n'
+ ' Got: ' + sys.inspect(response.body)
);
}
if (data) request.write(data);
request.on('response', function(response){
response.body = '';
response.setEncoding('utf8');
response.on('data', function(chunk){ response.body += chunk; });
response.on('end', function(){
--server.__pending || server.close();
if (timer) clearTimeout(timer);
// Assert response status
if (typeof status === 'number') {
assert.equal(
response.statusCode,
status,
msg + colorize('Invalid response status code.\n'
+ ' Expected: [green]{' + status + '}\n'
+ ' Got: [red]{' + response.statusCode + '}')
);
}
// Assert response headers
if (res.headers) {
var keys = Object.keys(res.headers);
for (var i = 0, len = keys.length; i < len; ++i) {
var name = keys[i],
actual = response.headers[name.toLowerCase()],
expected = res.headers[name];
assert.equal(
actual,
expected,
msg + colorize('Invalid response header [bold]{' + name + '}.\n'
+ ' Expected: [green]{' + expected + '}\n'
+ ' Got: [red]{' + actual + '}')
// Assert response body
if (res.body !== undefined) {
var eql = res.body instanceof RegExp
? res.body.test(response.body)
: res.body === response.body;
assert.ok(
eql,
msg + 'Invalid response body.\n'
+ ' Expected: ' + sys.inspect(res.body) + '\n'
+ ' Got: ' + sys.inspect(response.body)
);
}
}
// Callback
callback(response);
// Assert response status
if (typeof status === 'number') {
assert.equal(
response.statusCode,
status,
msg + colorize('Invalid response status code.\n'
+ ' Expected: [green]{' + status + '}\n'
+ ' Got: [red]{' + response.statusCode + '}')
);
}
// Assert response headers
if (res.headers) {
var keys = Object.keys(res.headers);
for (var i = 0, len = keys.length; i < len; ++i) {
var name = keys[i],
actual = response.headers[name.toLowerCase()],
expected = res.headers[name],
eql = expected instanceof RegExp
? expected.test(actual)
: expected == actual;
assert.ok(
eql,
msg + colorize('Invalid response header [bold]{' + name + '}.\n'
+ ' Expected: [green]{' + expected + '}\n'
+ ' Got: [red]{' + actual + '}')
);
}
}
// Callback
callback(response);
});
});
});
request.end();
request.end();
}
};
/**
@@ -476,7 +505,6 @@ function rpad(str, width) {
*/
function reportCoverage(cov) {
populateCoverage(cov);
// Stats
print('\n [bold]{Test Coverage}\n');
var sep = ' +------------------------------------------+----------+------+------+--------+',
@@ -507,9 +535,11 @@ function reportCoverage(cov) {
for (var name in cov) {
if (name.match(/\.js$/)) {
var file = cov[name];
print('\n [bold]{' + name + '}:');
print(file.source);
sys.print('\n');
if ((file.coverage < 100) || !quiet) {
print('\n [bold]{' + name + '}:');
print(file.source);
sys.print('\n');
}
}
}
}
@@ -574,6 +604,25 @@ function coverage(data, val) {
return n;
}
/**
* Test if all files have 100% coverage
*
* @param {Object} cov
* @return {Boolean}
*/
function hasFullCoverage(cov) {
for (var name in cov) {
var file = cov[name];
if (file instanceof Array) {
if (file.coverage !== 100) {
return false;
}
}
}
return true;
}
/**
* Run the given test `files`, or try _test/*_.
*
@@ -581,6 +630,7 @@ function coverage(data, val) {
*/
function run(files) {
cursor(false);
if (!files.length) {
try {
files = fs.readdirSync('test').map(function(file){
@@ -592,7 +642,6 @@ function run(files) {
process.exit(1);
}
}
if (watch) watchFiles(files);
runFiles(files);
}
@@ -617,16 +666,25 @@ function cursor(show) {
*/
function runFiles(files) {
files.forEach(runFile);
if (serial) {
(function next(){
if (files.length) {
runFile(files.shift(), next);
}
})();
} else {
files.forEach(runFile);
}
}
/**
* Run tests for the given `file`.
* Run tests for the given `file`, callback `fn()` when finished.
*
* @param {String} file
* @param {Function} fn
*/
function runFile(file) {
function runFile(file, fn) {
if (file.match(/\.js$/)) {
var title = path.basename(file),
file = path.join(cwd, file),
@@ -634,7 +692,7 @@ function runFile(file) {
(function check(){
var len = Object.keys(mod).length;
if (len) {
runSuite(title, mod);
runSuite(title, mod, fn);
} else {
setTimeout(check, 20);
}
@@ -642,49 +700,6 @@ function runFile(file) {
}
}
/**
* Clear the module cache for the given `file`.
*
* @param {String} file
*/
function clearCache(file) {
var keys = Object.keys(module.moduleCache);
for (var i = 0, len = keys.length; i < len; ++i) {
var key = keys[i];
if (key.indexOf(file) === key.length - file.length) {
delete module.moduleCache[key];
}
}
}
/**
* Watch the given `files` for changes.
*
* @param {Array} files
*/
function watchFiles(files) {
var p = 0,
c = ['▫ ', '▫▫ ', '▫▫▫ ', ' ▫▫▫',
' ▫▫', ' ▫', ' ▫', ' ▫▫',
'▫▫▫ ', '▫▫ ', '▫ '],
l = c.length;
cursor(false);
setInterval(function(){
sys.print(colorize(' [green]{' + c[p++ % l] + '} watching\r'));
}, 100);
files.forEach(function(file){
fs.watchFile(file, { interval: 100 }, function(curr, prev){
if (curr.mtime > prev.mtime) {
print(' [yellow]{◦} ' + file);
clearCache(file);
runFile(file);
}
});
});
}
/**
* Report `err` for the given `test` and `suite`.
*
@@ -696,23 +711,23 @@ function watchFiles(files) {
function error(suite, test, err) {
++failures;
var name = err.name,
stack = err.stack.replace(err.name, ''),
stack = err.stack ? err.stack.replace(err.name, '') : '',
label = test === 'uncaught'
? test
: suite + ' ' + test;
print('\n [bold]{' + label + '}: [red]{' + name + '}' + stack + '\n');
if (watch) notify(label + ' failed');
}
/**
* Run the given tests.
* Run the given tests, callback `fn()` when finished.
*
* @param {String} title
* @param {Object} tests
* @param {Function} fn
*/
var dots = 0;
function runSuite(title, tests) {
function runSuite(title, tests, fn) {
// Keys
var keys = only.length
? only.slice(0)
@@ -720,7 +735,7 @@ function runSuite(title, tests) {
// Setup
var setup = tests.setup || function(fn){ fn(); };
// Iterate tests
(function next(){
if (keys.length) {
@@ -735,27 +750,25 @@ function runSuite(title, tests) {
++testcount;
assert.testTitle = key;
if (serial) {
if (!watch) {
sys.print('.');
if (++dots % 25 === 0) sys.print('\n');
}
sys.print('.');
if (++dots % 25 === 0) sys.print('\n');
setup(function(){
if (test.length < 2) {
test(assert);
if (test.length < 1) {
test();
next();
} else {
var id = setTimeout(function(){
throw new Error("'" + key + "' timed out");
}, timeout);
test(assert, function(){
test(function(){
clearTimeout(id);
next();
});
}
});
} else {
test(assert, function(fn){
process.addListener('beforeExit', function(){
test(function(fn){
process.on('beforeExit', function(){
try {
fn();
} catch (err) {
@@ -769,6 +782,8 @@ function runSuite(title, tests) {
}
}
if (!serial) next();
} else if (serial) {
fn();
}
})();
}
@@ -778,6 +793,7 @@ function runSuite(title, tests) {
*/
function report() {
cursor(true);
process.emit('beforeExit');
if (failures) {
print('\n [bold]{Failures}: [red]{' + failures + '}\n\n');
@@ -788,7 +804,10 @@ function report() {
notify('100% ok');
}
if (typeof _$jscoverage === 'object') {
reportCoverage(_$jscoverage);
populateCoverage(_$jscoverage);
if (!hasFullCoverage(_$jscoverage) || !quiet) {
reportCoverage(_$jscoverage);
}
}
}
@@ -806,14 +825,14 @@ function notify(msg) {
// Report uncaught exceptions
process.addListener('uncaughtException', function(err){
process.on('uncaughtException', function(err){
error('uncaught', 'uncaught', err);
});
// Show cursor
['INT', 'TERM', 'QUIT'].forEach(function(sig){
process.addListener('SIG' + sig, function(){
process.on('SIG' + sig, function(){
cursor(true);
process.exit(1);
});

View File

@@ -113,7 +113,7 @@ code .this { color: #19469D; }</style>
<h1>!/usr/bin/env node</h1>
</td>
<td class="code">
<pre><code>!
<pre><code>
* <span class="class">Expresso</span>
* <span class="class">Copyright</span>(<span class="variable">c</span>) <span class="class">TJ</span> <span class="class">Holowaychuk</span> &<span class="variable">lt</span>;<span class="variable">tj</span>@<span class="variable">vision</span>-<span class="variable">media</span>.<span class="variable">ca</span>&<span class="variable">gt</span>;
* (<span class="class">MIT</span> <span class="class">Licensed</span>)
@@ -142,7 +142,7 @@ code .this { color: #19469D; }</style>
</p>
</td>
<td class="code">
<pre><code><span class="keyword">var</span> <span class="variable">version</span> = <span class="string">'0.6.1'</span>;</code></pre>
<pre><code><span class="keyword">var</span> <span class="variable">version</span> = <span class="string">'0.6.4'</span>;</code></pre>
</td>
</tr>
<tr class="code">
@@ -219,6 +219,15 @@ code .this { color: #19469D; }</style>
</tr>
<tr class="code">
<td class="docs">
<p>Default timeout.
</p>
</td>
<td class="code">
<pre><code><span class="keyword">var</span> <span class="variable">timeout</span> = <span class="number integer">2000</span>;</code></pre>
</td>
</tr>
<tr class="code">
<td class="docs">
<p>Usage documentation.
</p>
</td>
@@ -230,6 +239,7 @@ code .this { color: #19469D; }</style>
+ <span class="string">'\n -w, --watch Watch for modifications and re-execute tests'</span>
+ <span class="string">'\n -g, --growl Enable growl notifications'</span>
+ <span class="string">'\n -c, --coverage Generate and report test coverage'</span>
+ <span class="string">'\n -t, --timeout MS Timeout in milliseconds, defaults to 2000'</span>
+ <span class="string">'\n -r, --require PATH Require the given module path'</span>
+ <span class="string">'\n -o, --only TESTS Execute only the comma sperated TESTS (can be set several times)'</span>
+ <span class="string">'\n -I, --include PATH Unshift the given path to require.paths'</span>
@@ -291,6 +301,14 @@ code .this { color: #19469D; }</style>
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="class">Error</span>(<span class="string">'--require requires a path'</span>);
}
<span class="keyword">break</span>;
<span class="keyword">case</span> <span class="string">'-t'</span>:
<span class="keyword">case</span> <span class="string">'--timeout'</span>:
<span class="keyword">if</span> (<span class="variable">arg</span> = <span class="variable">args</span>.<span class="variable">shift</span>()) {
<span class="variable">timeout</span> = <span class="variable">parseInt</span>(<span class="variable">arg</span>, <span class="number integer">10</span>);
} <span class="keyword">else</span> {
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="class">Error</span>(<span class="string">'--timeout requires an argument'</span>);
}
<span class="keyword">break</span>;
<span class="keyword">case</span> <span class="string">'-c'</span>:
<span class="keyword">case</span> <span class="string">'--cov'</span>:
<span class="keyword">case</span> <span class="string">'--coverage'</span>:
@@ -525,17 +543,17 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
<span class="variable">method</span> = <span class="variable">req</span>.<span class="variable">method</span> || <span class="string">'GET'</span>,
<span class="variable">status</span> = <span class="variable">res</span>.<span class="variable">status</span> || <span class="variable">res</span>.<span class="variable">statusCode</span>,
<span class="variable">data</span> = <span class="variable">req</span>.<span class="variable">data</span> || <span class="variable">req</span>.<span class="variable">body</span>,
<span class="variable">timeout</span> = <span class="variable">req</span>.<span class="variable">timeout</span> || <span class="number integer">0</span>;
<span class="variable">requestTimeout</span> = <span class="variable">req</span>.<span class="variable">timeout</span> || <span class="number integer">0</span>;
<span class="keyword">var</span> <span class="variable">request</span> = <span class="variable">client</span>.<span class="variable">request</span>(<span class="variable">method</span>, <span class="variable">req</span>.<span class="variable">url</span>, <span class="variable">req</span>.<span class="variable">headers</span>);
<span class="comment">// Timeout</span>
<span class="keyword">if</span> (<span class="variable">timeout</span>) {
<span class="keyword">if</span> (<span class="variable">requestTimeout</span>) {
<span class="variable">timer</span> = <span class="variable">setTimeout</span>(<span class="keyword">function</span>(){
--<span class="variable">server</span>.<span class="variable">__pending</span> || <span class="variable">server</span>.<span class="variable">close</span>();
<span class="keyword">delete</span> <span class="variable">req</span>.<span class="variable">timeout</span>;
<span class="variable">assert</span>.<span class="variable">fail</span>(<span class="variable">msg</span> + <span class="string">'Request timed out after '</span> + <span class="variable">timeout</span> + <span class="string">'ms.'</span>);
}, <span class="variable">timeout</span>);
<span class="variable">assert</span>.<span class="variable">fail</span>(<span class="variable">msg</span> + <span class="string">'Request timed out after '</span> + <span class="variable">requestTimeout</span> + <span class="string">'ms.'</span>);
}, <span class="variable">requestTimeout</span>);
}
<span class="keyword">if</span> (<span class="variable">data</span>) <span class="variable">request</span>.<span class="variable">write</span>(<span class="variable">data</span>);
@@ -549,9 +567,11 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
<span class="comment">// Assert response body</span>
<span class="keyword">if</span> (<span class="variable">res</span>.<span class="variable">body</span> !== <span class="variable">undefined</span>) {
<span class="variable">assert</span>.<span class="variable">equal</span>(
<span class="variable">response</span>.<span class="variable">body</span>,
<span class="variable">res</span>.<span class="variable">body</span>,
<span class="keyword">var</span> <span class="variable">eql</span> = <span class="variable">res</span>.<span class="variable">body</span> <span class="variable">instanceof</span> <span class="class">RegExp</span>
? <span class="variable">res</span>.<span class="variable">body</span>.<span class="variable">test</span>(<span class="variable">response</span>.<span class="variable">body</span>)
: <span class="variable">res</span>.<span class="variable">body</span> === <span class="variable">response</span>.<span class="variable">body</span>;
<span class="variable">assert</span>.<span class="variable">ok</span>(
<span class="variable">eql</span>,
<span class="variable">msg</span> + <span class="string">'Invalid response body.\n'</span>
+ <span class="string">' Expected: '</span> + <span class="variable">sys</span>.<span class="variable">inspect</span>(<span class="variable">res</span>.<span class="variable">body</span>) + <span class="string">'\n'</span>
+ <span class="string">' Got: '</span> + <span class="variable">sys</span>.<span class="variable">inspect</span>(<span class="variable">response</span>.<span class="variable">body</span>)
@@ -575,10 +595,12 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
<span class="keyword">for</span> (<span class="keyword">var</span> <span class="variable">i</span> = <span class="number integer">0</span>, <span class="variable">len</span> = <span class="variable">keys</span>.<span class="variable">length</span>; <span class="variable">i</span> &<span class="variable">lt</span>; <span class="variable">len</span>; ++<span class="variable">i</span>) {
<span class="keyword">var</span> <span class="variable">name</span> = <span class="variable">keys</span>[<span class="variable">i</span>],
<span class="variable">actual</span> = <span class="variable">response</span>.<span class="variable">headers</span>[<span class="variable">name</span>.<span class="variable">toLowerCase</span>()],
<span class="variable">expected</span> = <span class="variable">res</span>.<span class="variable">headers</span>[<span class="variable">name</span>];
<span class="variable">assert</span>.<span class="variable">equal</span>(
<span class="variable">actual</span>,
<span class="variable">expected</span>,
<span class="variable">expected</span> = <span class="variable">res</span>.<span class="variable">headers</span>[<span class="variable">name</span>],
<span class="variable">eql</span> = <span class="variable">expected</span> <span class="variable">instanceof</span> <span class="class">RegExp</span>
? <span class="variable">expected</span>.<span class="variable">test</span>(<span class="variable">actual</span>)
: <span class="variable">expected</span> == <span class="variable">actual</span>;
<span class="variable">assert</span>.<span class="variable">ok</span>(
<span class="variable">eql</span>,
<span class="variable">msg</span> + <span class="variable">colorize</span>(<span class="string">'Invalid response header [bold]{'</span> + <span class="variable">name</span> + <span class="string">'}.\n'</span>
+ <span class="string">' Expected: [green]{'</span> + <span class="variable">expected</span> + <span class="string">'}\n'</span>
+ <span class="string">' Got: [red]{'</span> + <span class="variable">actual</span> + <span class="string">'}'</span>)
@@ -800,20 +822,28 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
</td>
<td class="code">
<pre><code><span class="keyword">function</span> <span class="variable">runFiles</span>(<span class="variable">files</span>) {
<span class="variable">files</span>.<span class="variable">forEach</span>(<span class="variable">runFile</span>);
<span class="keyword">if</span> (<span class="variable">serial</span>) {
(<span class="keyword">function</span> <span class="variable">next</span>(){
<span class="keyword">if</span> (<span class="variable">files</span>.<span class="variable">length</span>) {
<span class="variable">runFile</span>(<span class="variable">files</span>.<span class="variable">shift</span>(), <span class="variable">next</span>);
}
})();
} <span class="keyword">else</span> {
<span class="variable">files</span>.<span class="variable">forEach</span>(<span class="variable">runFile</span>);
}
}</code></pre>
</td>
</tr>
<tr class="code">
<td class="docs">
<p>Run tests for the given <code>file</code>.</p>
<p>Run tests for the given <code>file</code>, callback <code>fn()</code> when finished.</p>
<h2></h2>
<ul><li><p><strong>param</strong>: <em>String</em> file</p></li></ul>
<ul><li><p><strong>param</strong>: <em>String</em> file</p></li><li><p><strong>param</strong>: <em>Function</em> fn</p></li></ul>
</td>
<td class="code">
<pre><code><span class="keyword">function</span> <span class="variable">runFile</span>(<span class="variable">file</span>) {
<pre><code><span class="keyword">function</span> <span class="variable">runFile</span>(<span class="variable">file</span>, <span class="variable">fn</span>) {
<span class="keyword">if</span> (<span class="variable">file</span>.<span class="variable">match</span>(<span class="regexp">/\.js$/</span>)) {
<span class="keyword">var</span> <span class="variable">title</span> = <span class="variable">path</span>.<span class="variable">basename</span>(<span class="variable">file</span>),
<span class="variable">file</span> = <span class="variable">path</span>.<span class="variable">join</span>(<span class="variable">cwd</span>, <span class="variable">file</span>),
@@ -821,7 +851,7 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
(<span class="keyword">function</span> <span class="variable">check</span>(){
<span class="keyword">var</span> <span class="variable">len</span> = <span class="class">Object</span>.<span class="variable">keys</span>(<span class="variable">mod</span>).<span class="variable">length</span>;
<span class="keyword">if</span> (<span class="variable">len</span>) {
<span class="variable">runSuite</span>(<span class="variable">title</span>, <span class="variable">mod</span>);
<span class="variable">runSuite</span>(<span class="variable">title</span>, <span class="variable">mod</span>, <span class="variable">fn</span>);
} <span class="keyword">else</span> {
<span class="variable">setTimeout</span>(<span class="variable">check</span>, <span class="number integer">20</span>);
}
@@ -904,15 +934,15 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
</tr>
<tr class="code">
<td class="docs">
<p>Run the given tests.</p>
<p>Run the given tests, callback <code>fn()</code> when finished.</p>
<h2></h2>
<ul><li><p><strong>param</strong>: <em>String</em> title</p></li><li><p><strong>param</strong>: <em>Object</em> tests</p></li></ul>
<ul><li><p><strong>param</strong>: <em>String</em> title</p></li><li><p><strong>param</strong>: <em>Object</em> tests</p></li><li><p><strong>param</strong>: <em>Function</em> fn</p></li></ul>
</td>
<td class="code">
<pre><code><span class="keyword">var</span> <span class="variable">dots</span> = <span class="number integer">0</span>;
<span class="keyword">function</span> <span class="variable">runSuite</span>(<span class="variable">title</span>, <span class="variable">tests</span>) {
<span class="keyword">function</span> <span class="variable">runSuite</span>(<span class="variable">title</span>, <span class="variable">tests</span>, <span class="variable">fn</span>) {
<span class="comment">// Keys</span>
<span class="keyword">var</span> <span class="variable">keys</span> = <span class="variable">only</span>.<span class="variable">length</span>
? <span class="variable">only</span>.<span class="variable">slice</span>(<span class="number integer">0</span>)
@@ -920,7 +950,7 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
<span class="comment">// Setup</span>
<span class="keyword">var</span> <span class="variable">setup</span> = <span class="variable">tests</span>.<span class="variable">setup</span> || <span class="keyword">function</span>(<span class="variable">fn</span>){ <span class="variable">fn</span>(); };
<span class="comment">// Iterate tests</span>
(<span class="keyword">function</span> <span class="variable">next</span>(){
<span class="keyword">if</span> (<span class="variable">keys</span>.<span class="variable">length</span>) {
@@ -940,21 +970,21 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
<span class="keyword">if</span> (++<span class="variable">dots</span> % <span class="number integer">25</span> === <span class="number integer">0</span>) <span class="variable">sys</span>.<span class="variable">print</span>(<span class="string">'\n'</span>);
}
<span class="variable">setup</span>(<span class="keyword">function</span>(){
<span class="keyword">if</span> (<span class="variable">test</span>.<span class="variable">length</span> &<span class="variable">lt</span>; <span class="number integer">2</span>) {
<span class="variable">test</span>(<span class="variable">assert</span>);
<span class="keyword">if</span> (<span class="variable">test</span>.<span class="variable">length</span> &<span class="variable">lt</span>; <span class="number integer">1</span>) {
<span class="variable">test</span>();
<span class="variable">next</span>();
} <span class="keyword">else</span> {
<span class="keyword">var</span> <span class="variable">id</span> = <span class="variable">setTimeout</span>(<span class="keyword">function</span>(){
<span class="keyword">throw</span> <span class="keyword">new</span> <span class="class">Error</span>(&<span class="variable">quot</span>;<span class="string">'&quot; + key + &quot;'</span> <span class="variable">timed</span> <span class="variable">out</span>&<span class="variable">quot</span>;);
}, <span class="number integer">2000</span>);
<span class="variable">test</span>(<span class="variable">assert</span>, <span class="keyword">function</span>(){
}, <span class="variable">timeout</span>);
<span class="variable">test</span>(<span class="keyword">function</span>(){
<span class="variable">clearTimeout</span>(<span class="variable">id</span>);
<span class="variable">next</span>();
});
}
});
} <span class="keyword">else</span> {
<span class="variable">test</span>(<span class="variable">assert</span>, <span class="keyword">function</span>(<span class="variable">fn</span>){
<span class="variable">test</span>(<span class="keyword">function</span>(<span class="variable">fn</span>){
<span class="variable">process</span>.<span class="variable">addListener</span>(<span class="string">'beforeExit'</span>, <span class="keyword">function</span>(){
<span class="keyword">try</span> {
<span class="variable">fn</span>();
@@ -969,6 +999,8 @@ the given <code>req</code> object and <code>res</code> assertions object.</p>
}
}
<span class="keyword">if</span> (!<span class="variable">serial</span>) <span class="variable">next</span>();
} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="variable">serial</span>) {
<span class="variable">fn</span>();
}
})();
}</code></pre>

View File

@@ -41,6 +41,10 @@
<div id="wrapper">
<h1>Expresso</h1>
<div class='mp'>
<h2 id="NAME">NAME</h2>
<p class="man-name">
<code>index</code>
</p>
<p><a href="http://github.com/visionmedia/expresso">Expresso</a> is a JavaScript <a href="http://en.wikipedia.org/wiki/Test-driven_development">TDD</a> framework written for <a href="http://nodejs.org">nodejs</a>. Expresso is extremely fast, and is packed with features such as additional assertion methods, code coverage reporting, CI support, and more.</p>
<h2 id="Features">Features</h2>
@@ -85,7 +89,7 @@ the command below, which will first compile node-jscoverage:</p>
<p>To define tests we simply export several functions:</p>
<pre><code>exports['test String#length'] = function(assert){
<pre><code>exports['test String#length'] = function(){
assert.equal(6, 'foobar'.length);
};
</code></pre>
@@ -95,7 +99,7 @@ export your own object containing the tests, however this
is essentially the as above:</p>
<pre><code>module.exports = {
'test String#length': function(assert){
'test String#length': function(){
assert.equal(6, 'foobar'.length);
}
};
@@ -103,16 +107,16 @@ is essentially the as above:</p>
<p>If you prefer not to use quoted keys:</p>
<pre><code>exports.testsStringLength = function(assert){
<pre><code>exports.testsStringLength = function(){
assert.equal(6, 'foobar'.length);
};
</code></pre>
<p>The second argument passed to each callback is <em>beforeExit</em>,
<p>The argument passed to each callback is <em>beforeExit</em>,
which is typically used to assert that callbacks have been
invoked.</p>
<pre><code>exports.testAsync = function(assert, beforeExit){
<pre><code>exports.testAsync = function(beforeExit){
var n = 0;
setTimeout(function(){
++n;
@@ -229,9 +233,9 @@ which is then used to perform several assertions
on the response with the following properties:</p>
<ul>
<li><em>body</em> assert response body</li>
<li><em>body</em> assert response body (regexp or string)</li>
<li><em>status</em> assert response status code</li>
<li><em>header</em> assert that all given headers match (unspecified are ignored)</li>
<li><em>header</em> assert that all given headers match (unspecified are ignored, use a regexp or string)</li>
</ul>
@@ -361,7 +365,7 @@ future <code>--cov</code> will most likely accept a path.</p>
<p>Sometimes it is useful to postpone running of tests until a callback or event has fired, currently the <em>exports.foo = function(){};</em> syntax is supported for this:</p>
<pre><code>setTimeout(function(){
exports['test async exports'] = function(assert){
exports['test async exports'] = function(){
assert.ok('wahoo');
};
}, 100);

View File

@@ -37,7 +37,7 @@ Install via npm:
To define tests we simply export several functions:
exports['test String#length'] = function(assert){
exports['test String#length'] = function(){
assert.equal(6, 'foobar'.length);
};
@@ -46,22 +46,22 @@ export your own object containing the tests, however this
is essentially the as above:
module.exports = {
'test String#length': function(assert){
'test String#length': function(){
assert.equal(6, 'foobar'.length);
}
};
If you prefer not to use quoted keys:
exports.testsStringLength = function(assert){
exports.testsStringLength = function(){
assert.equal(6, 'foobar'.length);
};
The second argument passed to each callback is _beforeExit_,
The argument passed to each callback is _beforeExit_,
which is typically used to assert that callbacks have been
invoked.
exports.testAsync = function(assert, beforeExit){
exports.testAsync = function(beforeExit){
var n = 0;
setTimeout(function(){
++n;
@@ -164,9 +164,9 @@ receives the response for assertions, or an object
which is then used to perform several assertions
on the response with the following properties:
- _body_ assert response body
- _body_ assert response body (regexp or string)
- _status_ assert response status code
- _header_ assert that all given headers match (unspecified are ignored)
- _header_ assert that all given headers match (unspecified are ignored, use a regexp or string)
When providing _res_ you may then also pass a callback function
as the fourth argument for additional assertions.
@@ -284,7 +284,7 @@ future `--cov` will most likely accept a path.
Sometimes it is useful to postpone running of tests until a callback or event has fired, currently the _exports.foo = function(){};_ syntax is supported for this:
setTimeout(function(){
exports['test async exports'] = function(assert){
exports['test async exports'] = function(){
assert.ok('wahoo');
};
}, 100);

View File

@@ -1,5 +1,5 @@
{ "name": "expresso",
"version": "0.6.2",
"version": "0.7.2",
"description": "TDD framework, light-weight, fast, CI-friendly",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"bin": {

View File

@@ -1,9 +1,16 @@
/**
* Module dependencies.
*/
var assert = require('assert');
module.exports = {
'assert.eql()': function(assert){
'assert.eql()': function(){
assert.equal(assert.deepEqual, assert.eql);
},
'assert.type()': function(assert){
'assert.type()': function(){
assert.type('foobar', 'string');
assert.type(2, 'number');
assert.throws(function(){
@@ -11,7 +18,7 @@ module.exports = {
});
},
'assert.includes()': function(assert){
'assert.includes()': function(){
assert.includes('some random string', 'dom');
assert.throws(function(){
assert.include('some random string', 'foobar');
@@ -31,7 +38,7 @@ module.exports = {
});
},
'assert.isNull()': function(assert){
'assert.isNull()': function(){
assert.isNull(null);
assert.throws(function(){
assert.isNull(undefined);
@@ -41,7 +48,7 @@ module.exports = {
});
},
'assert.isUndefined()': function(assert){
'assert.isUndefined()': function(){
assert.isUndefined(undefined);
assert.throws(function(){
assert.isUndefined(null);
@@ -51,7 +58,7 @@ module.exports = {
});
},
'assert.isNotNull()': function(assert){
'assert.isNotNull()': function(){
assert.isNotNull(false);
assert.isNotNull(undefined);
assert.throws(function(){
@@ -59,7 +66,7 @@ module.exports = {
});
},
'assert.isDefined()': function(assert){
'assert.isDefined()': function(){
assert.isDefined(false);
assert.isDefined(null);
assert.throws(function(){
@@ -67,14 +74,14 @@ module.exports = {
});
},
'assert.match()': function(assert){
'assert.match()': function(){
assert.match('foobar', /foo(bar)?/);
assert.throws(function(){
assert.match('something', /rawr/);
});
},
'assert.length()': function(assert){
'assert.length()': function(){
assert.length('test', 4);
assert.length([1,2,3,4], 4);
assert.throws(function(){

View File

@@ -1,6 +1,12 @@
/**
* Module dependencies.
*/
var assert = require('assert');
setTimeout(function(){
exports['test async exports'] = function(assert){
exports['test async exports'] = function(){
assert.ok('wahoo');
};
}, 100);

View File

@@ -3,10 +3,11 @@
* Module dependencies.
*/
var bar = require('bar');
var assert = require('assert')
, bar = require('bar');
module.exports = {
'bar()': function(assert){
'bar()': function(){
assert.equal('bar', bar.bar());
}
};

View File

@@ -3,10 +3,11 @@
* Module dependencies.
*/
var foo = require('foo');
var assert = require('assert')
, foo = require('foo');
module.exports = {
'foo()': function(assert){
'foo()': function(){
assert.equal('foo', foo.foo());
assert.equal('foo', foo.foo());
}

View File

@@ -3,80 +3,144 @@
* Module dependencies.
*/
var http = require('http');
var assert = require('assert')
, http = require('http');
var server = http.createServer(function(req, res){
if (req.method === 'GET') {
if (req.url === '/delay') {
setTimeout(function(){
res.writeHead(200, {});
res.end('delayed');
}, 200);
} else {
var body = JSON.stringify({ name: 'tj' });
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf8',
'Content-Length': body.length
});
res.end(body);
}
if (req.method === 'GET') {
if (req.url === '/delay') {
setTimeout(function(){
res.writeHead(200, {});
res.end('delayed');
}, 200);
} else {
var body = '';
req.setEncoding('utf8');
req.addListener('data', function(chunk){ body += chunk });
req.addListener('end', function(){
res.writeHead(200, {});
res.end(req.url + ' ' + body);
});
var body = JSON.stringify({ name: 'tj' });
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf8',
'Content-Length': body.length
});
res.end(body);
}
} else {
var body = '';
req.setEncoding('utf8');
req.on('data', function(chunk){ body += chunk });
req.on('end', function(){
res.writeHead(200, {});
res.end(req.url + ' ' + body);
});
}
});
module.exports = {
'test assert.response()': function(assert, beforeExit){
var called = 0;
var delayedServer = http.createServer(function(req, res){
res.writeHead(200);
res.end('it worked');
});
assert.response(server, {
url: '/',
method: 'GET'
},{
body: '{"name":"tj"}',
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf8'
}
});
assert.response(server, {
url: '/foo',
method: 'POST',
data: 'bar baz'
},{
body: '/foo bar baz',
status: 200
}, function(res){
++called;
assert.ok(res);
});
assert.response(server, {
url: '/foo'
}, function(res){
++called;
assert.ok(res.body.indexOf('tj') >= 0, 'Test assert.response() callback');
});
assert.response(server,
{ url: '/delay', timeout: 300 },
{ body: 'delayed' });
beforeExit(function(){
assert.equal(2, called);
});
},
var oldListen = delayedServer.listen;
delayedServer.listen = function(){
var args = arguments;
setTimeout(function(){
oldListen.apply(delayedServer, args);
}, 100);
};
module.exports = {
'test assert.response(req, res, fn)': function(beforeExit){
var calls = 0;
assert.response(server, {
url: '/',
method: 'GET'
},{
body: '{"name":"tj"}',
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf8'
}
}, function(res){
++calls;
assert.ok(res);
});
'test assert.response() regexp': function(assert){
assert.response(server,
{ url: '/foo', method: 'POST', data: 'foobar' },
{ body: /^\/foo foo(bar)?/ });
}
};
beforeExit(function(){
assert.equal(1, calls);
})
},
'test assert.response(req, fn)': function(beforeExit){
var calls = 0;
assert.response(server, {
url: '/foo'
}, function(res){
++calls;
assert.ok(res.body.indexOf('tj') >= 0, 'Test assert.response() callback');
});
beforeExit(function(){
assert.equal(1, calls);
});
},
'test assert.response() delay': function(beforeExit){
var calls = 0;
assert.response(server,
{ url: '/delay', timeout: 1500 },
{ body: 'delayed' },
function(){
++calls;
});
beforeExit(function(){
assert.equal(1, calls);
});
},
'test assert.response() regexp': function(beforeExit){
var calls = 0;
assert.response(server,
{ url: '/foo', method: 'POST', data: 'foobar' },
{ body: /^\/foo foo(bar)?/ },
function(){
++calls;
});
beforeExit(function(){
assert.equal(1, calls);
});
},
'test assert.response() regexp headers': function(beforeExit){
var calls = 0;
assert.response(server,
{ url: '/' },
{ body: '{"name":"tj"}', headers: { 'Content-Type': /^application\/json/ } },
function(){
++calls;
});
beforeExit(function(){
assert.equal(1, calls);
});
},
// [!] if this test doesn't pass, an uncaught ECONNREFUSED will display
'test assert.response() with deferred listen()': function(beforeExit){
var calls = 0;
assert.response(delayedServer,
{ url: '/' },
{ body: 'it worked' },
function(){
++calls;
});
beforeExit(function(){
assert.equal(1, calls);
});
}
};

View File

@@ -1,6 +1,7 @@
var setup = 0,
order = [];
var assert = require('assert')
, setup = 0
, order = [];
module.exports = {
setup: function(done){
@@ -8,7 +9,7 @@ module.exports = {
done();
},
a: function(assert, done){
a: function(done){
assert.equal(1, setup);
order.push('a');
setTimeout(function(){
@@ -16,7 +17,7 @@ module.exports = {
}, 500);
},
b: function(assert, done){
b: function(done){
assert.equal(2, setup);
order.push('b');
setTimeout(function(){
@@ -24,7 +25,7 @@ module.exports = {
}, 200);
},
c: function(assert, done){
c: function(done){
assert.equal(3, setup);
order.push('c');
setTimeout(function(){
@@ -32,7 +33,7 @@ module.exports = {
}, 1000);
},
d: function(assert){
d: function(){
assert.eql(order, ['a', 'b', 'c']);
}
};

View File

@@ -3,7 +3,8 @@
* Module dependencies.
*/
var http = require('http');
var assert = require('assert')
, http = require('http');
var server = http.createServer(function(req, res){
if (req.method === 'GET') {
@@ -32,7 +33,7 @@ var server = http.createServer(function(req, res){
});
module.exports = {
'test assert.response()': function(assert, done){
'test assert.response()': function(done){
assert.response(server, {
url: '/',
method: 'GET'

View File

@@ -1,3 +0,0 @@
[submodule "lib/vendor/web-socket-js"]
path = lib/vendor/web-socket-js
url = git://github.com/gimite/web-socket-js.git

View File

@@ -46,14 +46,6 @@ The `socket.io` client is basically a simple HTTP Socket interface implementatio
- Easy to use! See [socket.io-node](http://github.com/LearnBoost/Socket.IO-node) for the server to connect to.
### How to use
The recommended way of including the Socket.IO client is through the Socket.IO CDN:
In your &lt;head&gt;
<script src="http://cdn.socket.io/stable/socket.io.js"></script>
Then, in your code
socket = new io.Socket('localhost');
socket.connect();
@@ -163,6 +155,17 @@ Events:
Fired when the connection is established and the handshake successful
- *connecting(transport_type)*
Fired when a connection is attempted, passing the transport name
- *connect_failed*
Fired when the connection timeout occurs after the last connection attempt.
This only fires if the `connectTimeout` option is set.
If the `tryTransportsOnConnectTimeout` option is set, this only fires once all
possible transports have been tried.
- *message(message)*
Fired when a message arrives from the server
@@ -175,14 +178,6 @@ Events:
Fired when the connection is considered disconnected.
### Changelog
2010 08 02 - **0.5.4** (9.95KB)
* Added io.util.load as a reusable onload handler
* Added io.util.ios which reports if the UA is running on iPhone or iPad
* No more loading bar on iPhone: XHR-Polling now connects `onload` for the iOS WebKit, and waits 10 ms to launch the initial connection.
### Credits
Guillermo Rauch &lt;guillermo@learnboost.com&gt;
@@ -210,4 +205,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -7,12 +7,12 @@
*/
this.io = {
version: '0.6',
version: '0.6.2',
setPath: function(path){
if (window.console && console.error) console.error('io.setPath will be removed. Please set the variable WEB_SOCKET_SWF_LOCATION pointing to WebSocketMain.swf');
this.path = /\/$/.test(path) ? path : path + '/';
WEB_SOCKET_SWF_LOCATION = path + 'lib/vendor/web-socket-js/WebSocketMain.swf';
WEB_SOCKET_SWF_LOCATION = path + 'lib/vendor/web-socket-js/WebSocketMain.swf';
}
};
@@ -20,5 +20,6 @@ if ('jQuery' in this) jQuery.io = this.io;
if (typeof window != 'undefined'){
// WEB_SOCKET_SWF_LOCATION = (document.location.protocol == 'https:' ? 'https:' : 'http:') + '//cdn.socket.io/' + this.io.version + '/WebSocketMain.swf';
WEB_SOCKET_SWF_LOCATION = '/socket.io/lib/vendor/web-socket-js/WebSocketMain.swf';
}
if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined')
WEB_SOCKET_SWF_LOCATION = '/socket.io/lib/vendor/web-socket-js/WebSocketMain.swf';
}

View File

@@ -59,23 +59,25 @@
if (this.transport && !this.connected){
if (this.connecting) this.disconnect();
this.connecting = true;
this.emit('connecting', [this.transport.type]);
this.transport.connect();
if (this.options.connectTimeout){
var self = this;
setTimeout(function(){
this.connectTimeoutTimer = setTimeout(function(){
if (!self.connected){
self.disconnect();
if (self.options.tryTransportsOnConnectTimeout && !self._rememberedTransport){
var remainingTransports = [], transports = self.options.transports;
for (var i = 0, transport; transport = transports[i]; i++){
if (transport != self.transport.type) remainingTransports.push(transport);
}
if (remainingTransports.length){
self.transport = self.getTransport(remainingTransports);
if(!self._remainingTransports) self._remainingTransports = self.options.transports.slice(0);
var transports = self._remainingTransports;
while(transports.length > 0 && transports.splice(0,1)[0] != self.transport.type){}
if(transports.length){
self.transport = self.getTransport(transports);
self.connect();
}
}
if(!self._remainingTransports || self._remainingTransports.length == 0) self.emit('connect_failed');
}
if(self._remainingTransports && self._remainingTransports.length == 0) delete self._remainingTransports;
}, this.options.connectTimeout);
}
}
@@ -89,6 +91,7 @@
};
Socket.prototype.disconnect = function(){
if (this.connectTimeoutTimer) clearTimeout(this.connectTimeoutTimer);
this.transport.disconnect();
return this;
};
@@ -99,14 +102,15 @@
return this;
};
Socket.prototype.fire = function(name, args){
if (name in this._events){
for (var i = 0, ii = this._events[name].length; i < ii; i++)
this._events[name][i].apply(this, args === undefined ? [] : args);
}
return this;
};
Socket.prototype.emit = function(name, args){
if (name in this._events){
var events = this._events[name].concat();
for (var i = 0, ii = events.length; i < ii; i++)
events[i].apply(this, args === undefined ? [] : args);
}
return this;
};
Socket.prototype.removeEvent = function(name, fn){
if (name in this._events){
for (var a = 0, l = this._events[name].length; a < l; a++)
@@ -137,11 +141,11 @@
this.connecting = false;
this._doQueue();
if (this.options.rememberTransport) this.options.document.cookie = 'socketio=' + encodeURIComponent(this.transport.type);
this.fire('connect');
this.emit('connect');
};
Socket.prototype._onMessage = function(data){
this.fire('message', [data]);
this.emit('message', [data]);
};
Socket.prototype._onDisconnect = function(){
@@ -149,9 +153,11 @@
this.connected = false;
this.connecting = false;
this._queueStack = [];
if (wasConnected) this.fire('disconnect');
if (wasConnected) this.emit('disconnect');
};
Socket.prototype.fire = Socket.prototype.emit;
Socket.prototype.addListener = Socket.prototype.addEvent = Socket.prototype.addEventListener = Socket.prototype.on;
})();
})();

View File

@@ -21,6 +21,7 @@
this.socket = new WebSocket(this._prepareUrl());
this.socket.onmessage = function(ev){ self._onData(ev.data); };
this.socket.onclose = function(ev){ self._onClose(); };
this.socket.onerror = function(e){ self._onError(e); };
return this;
};
@@ -38,6 +39,10 @@
this._onDisconnect();
return this;
};
WS.prototype._onError = function(e){
this.base.emit('error', [e]);
};
WS.prototype._prepareUrl = function(){
return (this.base.options.secure ? 'wss' : 'ws')
@@ -57,4 +62,4 @@
return true;
};
})();
})();

View File

@@ -20,9 +20,9 @@
var self = this;
this._xhr = this._request('', 'GET', true);
this._xhr.onreadystatechange = function(){
if (self._xhr.readyState == 3) self._onData(self._xhr.responseText);
if (self._xhr.readyState == 4) self._onData(self._xhr.responseText);
};
this._xhr.send();
this._xhr.send(null);
};
XHRMultipart.check = function(){
@@ -33,4 +33,4 @@
return true;
};
})();
})();

View File

@@ -34,27 +34,20 @@
XHRPolling.prototype._get = function(){
var self = this;
this._xhr = this._request(+ new Date, 'GET');
if ('onload' in this._xhr){
this._xhr.onload = function(){
self._onData(this.responseText);
self._get();
};
} else {
this._xhr.onreadystatechange = function(){
var status;
if (self._xhr.readyState == 4){
self._xhr.onreadystatechange = empty;
try { status = self._xhr.status; } catch(e){}
if (status == 200){
self._onData(self._xhr.responseText);
self._get();
} else {
self._onDisconnect();
}
}
};
}
this._xhr.send();
this._xhr.onreadystatechange = function(){
var status;
if (self._xhr.readyState == 4){
self._xhr.onreadystatechange = empty;
try { status = self._xhr.status; } catch(e){}
if (status == 200){
self._onData(self._xhr.responseText);
self._get();
} else {
self._onDisconnect();
}
}
};
this._xhr.send(null);
};
XHRPolling.check = function(){
@@ -65,4 +58,4 @@
return io.Transport.XHR.xdomainCheck();
};
})();
})();

View File

@@ -92,13 +92,17 @@
XHR.prototype._onDisconnect = function(){
if (this._xhr){
this._xhr.onreadystatechange = this._xhr.onload = empty;
this._xhr.abort();
this._xhr.onreadystatechange = empty;
try {
this._xhr.abort();
} catch(e){}
this._xhr = null;
}
if (this._sendXhr){
this._sendXhr.onreadystatechange = this._sendXhr.onload = empty;
this._sendXhr.abort();
this._sendXhr.onreadystatechange = empty;
try {
this._sendXhr.abort();
} catch(e){}
this._sendXhr = null;
}
this._sendBuffer = [];
@@ -128,4 +132,4 @@
XHR.request = request;
})();
})();

View File

@@ -15,7 +15,7 @@
ios: false,
load: function(fn){
if (document.readyState == 'complete' || _pageLoaded) return fn();
if (/loaded|complete/.test(document.readyState) || _pageLoaded) return fn();
if ('attachEvent' in window){
window.attachEvent('onload', fn);
} else {
@@ -57,4 +57,4 @@
_pageLoaded = true;
});
})();
})();

View File

@@ -22,11 +22,7 @@ import com.hurlant.crypto.tls.TLSEngine;
import com.hurlant.crypto.tls.TLSSecurityParameters;
import com.gsolo.encryption.MD5;
[Event(name="message", type="flash.events.Event")]
[Event(name="open", type="flash.events.Event")]
[Event(name="close", type="flash.events.Event")]
[Event(name="error", type="flash.events.Event")]
[Event(name="stateChange", type="WebSocketStateEvent")]
[Event(name="event", type="flash.events.Event")]
public class WebSocket extends EventDispatcher {
private static var CONNECTING:int = 0;
@@ -47,10 +43,9 @@ public class WebSocket extends EventDispatcher {
private var origin:String;
private var protocol:String;
private var buffer:ByteArray = new ByteArray();
private var dataQueue:Array;
private var eventQueue:Array = [];
private var headerState:int = 0;
private var readyState:int = CONNECTING;
private var bufferedAmount:int = 0;
private var headers:String;
private var noiseChars:Array;
private var expectedDigest:String;
@@ -115,23 +110,19 @@ public class WebSocket extends EventDispatcher {
socket.flush();
main.log("sent: " + data);
return -1;
} else if (readyState == CLOSED) {
} else if (readyState == CLOSING || readyState == CLOSED) {
var bytes:ByteArray = new ByteArray();
bytes.writeUTFBytes(data);
bufferedAmount += bytes.length; // not sure whether it should include \x00 and \xff
// We use return value to let caller know bufferedAmount because we cannot fire
// stateChange event here which causes weird error:
// > You are trying to call recursively into the Flash Player which is not allowed.
return bufferedAmount;
return bytes.length; // not sure whether it should include \x00 and \xff
} else {
main.fatal("INVALID_STATE_ERR: invalid state");
main.fatal("invalid state");
return 0;
}
}
public function close():void {
main.log("close");
dataQueue = [];
eventQueue = [];
try {
if (readyState == OPEN) {
socket.writeByte(0xff);
@@ -146,14 +137,6 @@ public class WebSocket extends EventDispatcher {
// We do something equivalent in JavaScript WebSocket#close instead.
}
public function getReadyState():int {
return readyState;
}
public function getBufferedAmount():int {
return bufferedAmount;
}
private function onSocketConnect(event:Event):void {
main.log("connected");
@@ -162,7 +145,6 @@ public class WebSocket extends EventDispatcher {
tlsSocket.startTLS(rawSocket, host, tlsConfig);
}
dataQueue = [];
var hostValue:String = host + (port == 80 ? "" : ":" + port);
var cookie:String = "";
if (main.getCallerHost() == host) {
@@ -199,8 +181,7 @@ public class WebSocket extends EventDispatcher {
private function onSocketClose(event:Event):void {
main.log("closed");
readyState = CLOSED;
notifyStateChange();
dispatchEvent(new Event("close"));
fireEvent({type: "close"}, true);
}
private function onSocketIoError(event:IOErrorEvent):void {
@@ -230,8 +211,7 @@ public class WebSocket extends EventDispatcher {
if (state == CLOSED) return;
main.error(message);
close();
notifyStateChange();
dispatchEvent(new Event(state == CONNECTING ? "close" : "error"));
fireEvent({type: state == CONNECTING ? "close" : "error"}, true);
}
private function onSocketData(event:ProgressEvent):void {
@@ -248,8 +228,7 @@ public class WebSocket extends EventDispatcher {
headerState = 0;
}
if (headerState == 4) {
buffer.position = 0;
var headerStr:String = buffer.readUTFBytes(pos + 1);
var headerStr:String = readUTFBytes(buffer, 0, pos + 1);
main.log("response header:\n" + headerStr);
if (!validateHeader(headerStr)) return;
removeBufferBefore(pos + 1);
@@ -257,8 +236,7 @@ public class WebSocket extends EventDispatcher {
}
} else if (headerState == 4) {
if (pos == 15) {
buffer.position = 0;
var replyDigest:String = readBytes(buffer, 16);
var replyDigest:String = readBytes(buffer, 0, 16);
main.log("reply digest: " + replyDigest);
if (replyDigest != expectedDigest) {
onError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
@@ -268,8 +246,7 @@ public class WebSocket extends EventDispatcher {
removeBufferBefore(pos + 1);
pos = -1;
readyState = OPEN;
notifyStateChange();
dispatchEvent(new Event("open"));
fireEvent({type: "open"}, true);
}
} else {
if (buffer[pos] == 0xff && pos > 0) {
@@ -277,11 +254,9 @@ public class WebSocket extends EventDispatcher {
onError("data must start with \\x00");
return;
}
buffer.position = 1;
var data:String = buffer.readUTFBytes(pos - 1);
var data:String = readUTFBytes(buffer, 1, pos - 1);
main.log("received: " + data);
dataQueue.push(encodeURIComponent(data));
dispatchEvent(new Event("message"));
fireEvent({type: "message", data: encodeURIComponent(data)}, false);
removeBufferBefore(pos + 1);
pos = -1;
} else if (pos == 1 && buffer[0] == 0xff && buffer[1] == 0x00) { // closing
@@ -289,18 +264,15 @@ public class WebSocket extends EventDispatcher {
removeBufferBefore(pos + 1);
pos = -1;
close();
notifyStateChange();
dispatchEvent(new Event("close"));
fireEvent({type: "close"}, true);
}
}
}
}
public function readSocketData():Array {
var q:Array = dataQueue;
if (dataQueue.length > 0) {
dataQueue = [];
}
public function receiveEvents():Array {
var q:Array = eventQueue;
eventQueue = [];
return q;
}
@@ -363,8 +335,12 @@ public class WebSocket extends EventDispatcher {
buffer = nextBuffer;
}
private function notifyStateChange():void {
dispatchEvent(new WebSocketStateEvent("stateChange", readyState, bufferedAmount));
private function fireEvent(event:Object, stateChanged:Boolean):void {
if (stateChanged) {
event.readyState = readyState;
}
eventQueue.push(event);
dispatchEvent(new Event("event"));
}
private function initNoiseChars():void {
@@ -434,7 +410,8 @@ public class WebSocket extends EventDispatcher {
// Reads specified number of bytes from buffer, and returns it as special format String
// where bytes[i] is i-th byte (not i-th character).
private function readBytes(buffer:ByteArray, numBytes:int):String {
private function readBytes(buffer:ByteArray, start:int, numBytes:int):String {
buffer.position = start;
var bytes:String = "";
for (var i:int = 0; i < numBytes; ++i) {
// & 0xff is to make \x80-\xff positive number.
@@ -443,6 +420,20 @@ public class WebSocket extends EventDispatcher {
return bytes;
}
private function readUTFBytes(buffer:ByteArray, start:int, numBytes:int):String {
buffer.position = start;
var data:String = "";
for(var i:int = start; i < start + numBytes; ++i) {
// Workaround of a bug of ByteArray#readUTFBytes() that bytes after "\x00" is discarded.
if (buffer[i] == 0x00) {
data += buffer.readUTFBytes(i - buffer.position) + "\x00";
buffer.position = i + 1;
}
}
data += buffer.readUTFBytes(start + numBytes - buffer.position);
return data;
}
private function randomInt(min:uint, max:uint):uint {
return min + Math.floor(Math.random() * (Number(max) - min + 1));
}

View File

@@ -19,24 +19,14 @@ import bridge.FABridge;
public class WebSocketMain extends Sprite {
private var policyLoaded:Boolean = false;
private var callerUrl:String;
private var debug:Boolean = false;
private var manualPolicyFileLoaded:Boolean = false;
public function WebSocketMain() {
// This is to avoid "You are trying to call recursively into the Flash Player ..."
// error which (I heard) happens when you pass bunch of messages.
// This workaround was written here:
// http://www.themorphicgroup.com/blog/2009/02/14/fabridge-error-you-are-trying-to-call-recursively-into-the-flash-player-which-is-not-allowed/
FABridge.EventsToCallLater["flash.events::Event"] = "true";
FABridge.EventsToCallLater["WebSocketMessageEvent"] = "true";
FABridge.EventsToCallLater["WebSocketStateEvent"] = "true";
var fab:FABridge = new FABridge();
fab.rootObject = this;
//log("Flash initialized");
}
public function setCallerUrl(url:String):void {
@@ -51,7 +41,9 @@ public class WebSocketMain extends Sprite {
url:String, protocol:String,
proxyHost:String = null, proxyPort:int = 0,
headers:String = null):WebSocket {
loadPolicyFile(null);
if (!manualPolicyFileLoaded) {
loadDefaultPolicyFile(url);
}
return new WebSocket(this, url, protocol, proxyHost, proxyPort, headers);
}
@@ -64,14 +56,16 @@ public class WebSocketMain extends Sprite {
return URLUtil.getServerName(this.callerUrl);
}
public function loadPolicyFile(url:String):void {
if (policyLoaded && !url) return;
if (!url) {
url = "xmlsocket://" + URLUtil.getServerName(this.callerUrl) + ":843";
}
log("policy file: " + url);
Security.loadPolicyFile(url);
policyLoaded = true;
private function loadDefaultPolicyFile(wsUrl:String):void {
var policyUrl:String = "xmlsocket://" + URLUtil.getServerName(wsUrl) + ":843";
log("policy file: " + policyUrl);
Security.loadPolicyFile(policyUrl);
}
public function loadManualPolicyFile(policyUrl:String):void {
log("policy file: " + policyUrl);
Security.loadPolicyFile(policyUrl);
manualPolicyFileLoaded = true;
}
public function log(message:String):void {

View File

@@ -1,32 +0,0 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31
package {
import flash.display.*;
import flash.events.*;
import flash.external.*;
import flash.net.*;
import flash.system.*;
import flash.utils.*;
import mx.core.*;
import mx.controls.*;
import mx.events.*;
import mx.utils.*;
public class WebSocketStateEvent extends Event {
public var readyState:int;
public var bufferedAmount:int;
public function WebSocketStateEvent(type:String, readyState:int, bufferedAmount:int) {
super(type);
this.readyState = readyState;
this.bufferedAmount = bufferedAmount;
}
}
}

View File

@@ -8,8 +8,10 @@
if (window.WebSocket) return;
var console = window.console;
if (!console) console = {log: function(){ }, error: function(){ }};
if (!console || !console.log || !console.error) {
console = {log: function(){ }, error: function(){ }};
}
if (!swfobject.hasFlashPlayerVersion("9.0.0")) {
console.error("Flash Player is not installed.");
return;
@@ -31,73 +33,22 @@
WebSocket.__addTask(function() {
self.__createFlash(url, protocol, proxyHost, proxyPort, headers);
});
}, 1);
}
}, 0);
};
WebSocket.prototype.__createFlash = function(url, protocol, proxyHost, proxyPort, headers) {
var self = this;
self.__flash =
WebSocket.__flash.create(url, protocol, proxyHost || null, proxyPort || 0, headers || null);
self.__flash.addEventListener("open", function(fe) {
try {
self.readyState = self.__flash.getReadyState();
if (self.__timer) clearInterval(self.__timer);
if (window.opera) {
// Workaround for weird behavior of Opera which sometimes drops events.
self.__timer = setInterval(function () {
self.__handleMessages();
}, 500);
}
if (self.onopen) self.onopen();
} catch (e) {
console.error(e.toString());
}
self.__flash.addEventListener("event", function(fe) {
// Uses setTimeout() to workaround the error:
// > You are trying to call recursively into the Flash Player which is not allowed.
setTimeout(function() { self.__handleEvents(); }, 0);
});
self.__flash.addEventListener("close", function(fe) {
try {
self.readyState = self.__flash.getReadyState();
if (self.__timer) clearInterval(self.__timer);
if (self.onclose) self.onclose();
} catch (e) {
console.error(e.toString());
}
});
self.__flash.addEventListener("message", function() {
try {
self.__handleMessages();
} catch (e) {
console.error(e.toString());
}
});
self.__flash.addEventListener("error", function(fe) {
try {
if (self.__timer) clearInterval(self.__timer);
if (self.onerror) self.onerror();
} catch (e) {
console.error(e.toString());
}
});
self.__flash.addEventListener("stateChange", function(fe) {
try {
self.readyState = self.__flash.getReadyState();
self.bufferedAmount = fe.getBufferedAmount();
} catch (e) {
console.error(e.toString());
}
});
//console.log("[WebSocket] Flash object is ready");
};
WebSocket.prototype.send = function(data) {
if (this.__flash) {
this.readyState = this.__flash.getReadyState();
}
if (!this.__flash || this.readyState == WebSocket.CONNECTING) {
throw "INVALID_STATE_ERR: Web Socket connection has not been established";
}
@@ -111,7 +62,7 @@
if (result < 0) { // success
return true;
} else {
this.bufferedAmount = result;
this.bufferedAmount += result;
return false;
}
};
@@ -119,7 +70,6 @@
WebSocket.prototype.close = function() {
var self = this;
if (!self.__flash) return;
self.readyState = self.__flash.getReadyState();
if (self.readyState == WebSocket.CLOSED || self.readyState == WebSocket.CLOSING) return;
self.__flash.close();
// Sets/calls them manually here because Flash WebSocketConnection.close cannot fire events
@@ -130,7 +80,7 @@
if (self.onclose) {
// Make it asynchronous so that it looks more like an actual
// close event
setTimeout(self.onclose, 1);
setTimeout(self.onclose, 0);
}
};
@@ -199,30 +149,62 @@
}
};
WebSocket.prototype.__handleMessages = function() {
// Gets data using readSocketData() instead of getting it from event object
WebSocket.prototype.__handleEvents = function() {
// Gets events using receiveEvents() instead of getting it from event object
// of Flash event. This is to make sure to keep message order.
// It seems sometimes Flash events don't arrive in the same order as they are sent.
var arr = this.__flash.readSocketData();
for (var i = 0; i < arr.length; i++) {
var data = decodeURIComponent(arr[i]);
var events = this.__flash.receiveEvents();
for (var i = 0; i < events.length; i++) {
try {
if (this.onmessage) {
var e;
if (window.MessageEvent) {
e = document.createEvent("MessageEvent");
e.initMessageEvent("message", false, false, data, null, null, window, null);
} else { // IE
e = {data: data};
var event = events[i];
if ("readyState" in event) {
this.readyState = event.readyState;
}
if (event.type == "open") {
if (this.__timer) clearInterval(this.__timer);
if (window.opera) {
// Workaround for weird behavior of Opera which sometimes drops events.
this.__timer = setInterval(function () {
this.__handleEvents();
}, 500);
}
this.onmessage(e);
if (this.onopen) this.onopen();
} else if (event.type == "close") {
if (this.__timer) clearInterval(this.__timer);
if (this.onclose) this.onclose();
} else if (event.type == "message") {
if (this.onmessage) {
var data = decodeURIComponent(event.data);
var e;
if (window.MessageEvent && !window.opera) {
e = document.createEvent("MessageEvent");
e.initMessageEvent("message", false, false, data, null, null, window, null);
} else {
// IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
e = {data: data};
}
this.onmessage(e);
}
} else if (event.type == "error") {
if (this.__timer) clearInterval(this.__timer);
if (this.onerror) this.onerror();
} else {
throw "unknown event type: " + event.type;
}
} catch (e) {
console.error(e.toString());
}
}
};
/**
* @param {object} object
* @param {string} type
@@ -237,7 +219,7 @@
}
object.dispatchEvent(event, arguments);
};
}
};
/**
* Basic implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface">DOM 2 EventInterface</a>}
@@ -298,6 +280,12 @@
WebSocket.__tasks = [];
WebSocket.loadFlashPolicyFile = function(url) {
WebSocket.__addTask(function() {
WebSocket.__flash.loadManualPolicyFile(url);
});
}
WebSocket.__initialize = function() {
if (WebSocket.__swfLocation) {
// For backword compatibility.

View File

@@ -1,4 +1,4 @@
/** Socket.IO 0.6 - Built with build.js */
/** Socket.IO 0.6.2 - Built with build.js */
/**
* Socket.IO client
*
@@ -8,12 +8,12 @@
*/
this.io = {
version: '0.6',
version: '0.6.2',
setPath: function(path){
if (window.console && console.error) console.error('io.setPath will be removed. Please set the variable WEB_SOCKET_SWF_LOCATION pointing to WebSocketMain.swf');
this.path = /\/$/.test(path) ? path : path + '/';
WEB_SOCKET_SWF_LOCATION = path + 'lib/vendor/web-socket-js/WebSocketMain.swf';
WEB_SOCKET_SWF_LOCATION = path + 'lib/vendor/web-socket-js/WebSocketMain.swf';
}
};
@@ -21,8 +21,10 @@ if ('jQuery' in this) jQuery.io = this.io;
if (typeof window != 'undefined'){
// WEB_SOCKET_SWF_LOCATION = (document.location.protocol == 'https:' ? 'https:' : 'http:') + '//cdn.socket.io/' + this.io.version + '/WebSocketMain.swf';
WEB_SOCKET_SWF_LOCATION = '/socket.io/lib/vendor/web-socket-js/WebSocketMain.swf';
if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined')
WEB_SOCKET_SWF_LOCATION = '/socket.io/lib/vendor/web-socket-js/WebSocketMain.swf';
}
/**
* Socket.IO client
*
@@ -40,7 +42,7 @@ if (typeof window != 'undefined'){
ios: false,
load: function(fn){
if (document.readyState == 'complete' || _pageLoaded) return fn();
if (/loaded|complete/.test(document.readyState) || _pageLoaded) return fn();
if ('attachEvent' in window){
window.attachEvent('onload', fn);
} else {
@@ -83,6 +85,7 @@ if (typeof window != 'undefined'){
});
})();
/**
* Socket.IO client
*
@@ -318,13 +321,17 @@ if (typeof window != 'undefined'){
XHR.prototype._onDisconnect = function(){
if (this._xhr){
this._xhr.onreadystatechange = this._xhr.onload = empty;
this._xhr.abort();
this._xhr.onreadystatechange = empty;
try {
this._xhr.abort();
} catch(e){}
this._xhr = null;
}
if (this._sendXhr){
this._sendXhr.onreadystatechange = this._sendXhr.onload = empty;
this._sendXhr.abort();
this._sendXhr.onreadystatechange = empty;
try {
this._sendXhr.abort();
} catch(e){}
this._sendXhr = null;
}
this._sendBuffer = [];
@@ -355,6 +362,7 @@ if (typeof window != 'undefined'){
XHR.request = request;
})();
/**
* Socket.IO client
*
@@ -378,6 +386,7 @@ if (typeof window != 'undefined'){
this.socket = new WebSocket(this._prepareUrl());
this.socket.onmessage = function(ev){ self._onData(ev.data); };
this.socket.onclose = function(ev){ self._onClose(); };
this.socket.onerror = function(e){ self._onError(e); };
return this;
};
@@ -395,6 +404,10 @@ if (typeof window != 'undefined'){
this._onDisconnect();
return this;
};
WS.prototype._onError = function(e){
this.base.emit('error', [e]);
};
WS.prototype._prepareUrl = function(){
return (this.base.options.secure ? 'wss' : 'ws')
@@ -415,6 +428,7 @@ if (typeof window != 'undefined'){
};
})();
/**
* Socket.IO client
*
@@ -563,9 +577,9 @@ if (typeof window != 'undefined'){
var self = this;
this._xhr = this._request('', 'GET', true);
this._xhr.onreadystatechange = function(){
if (self._xhr.readyState == 3) self._onData(self._xhr.responseText);
if (self._xhr.readyState == 4) self._onData(self._xhr.responseText);
};
this._xhr.send();
this._xhr.send(null);
};
XHRMultipart.check = function(){
@@ -577,6 +591,7 @@ if (typeof window != 'undefined'){
};
})();
/**
* Socket.IO client
*
@@ -613,27 +628,20 @@ if (typeof window != 'undefined'){
XHRPolling.prototype._get = function(){
var self = this;
this._xhr = this._request(+ new Date, 'GET');
if ('onload' in this._xhr){
this._xhr.onload = function(){
self._onData(this.responseText);
self._get();
};
} else {
this._xhr.onreadystatechange = function(){
var status;
if (self._xhr.readyState == 4){
self._xhr.onreadystatechange = empty;
try { status = self._xhr.status; } catch(e){}
if (status == 200){
self._onData(self._xhr.responseText);
self._get();
} else {
self._onDisconnect();
}
}
};
}
this._xhr.send();
this._xhr.onreadystatechange = function(){
var status;
if (self._xhr.readyState == 4){
self._xhr.onreadystatechange = empty;
try { status = self._xhr.status; } catch(e){}
if (status == 200){
self._onData(self._xhr.responseText);
self._get();
} else {
self._onDisconnect();
}
}
};
this._xhr.send(null);
};
XHRPolling.check = function(){
@@ -645,6 +653,7 @@ if (typeof window != 'undefined'){
};
})();
/**
* Socket.IO client
*
@@ -822,23 +831,25 @@ JSONPPolling.xdomainCheck = function(){
if (this.transport && !this.connected){
if (this.connecting) this.disconnect();
this.connecting = true;
this.emit('connecting', [this.transport.type]);
this.transport.connect();
if (this.options.connectTimeout){
var self = this;
setTimeout(function(){
this.connectTimeoutTimer = setTimeout(function(){
if (!self.connected){
self.disconnect();
if (self.options.tryTransportsOnConnectTimeout && !self._rememberedTransport){
var remainingTransports = [], transports = self.options.transports;
for (var i = 0, transport; transport = transports[i]; i++){
if (transport != self.transport.type) remainingTransports.push(transport);
}
if (remainingTransports.length){
self.transport = self.getTransport(remainingTransports);
if(!self._remainingTransports) self._remainingTransports = self.options.transports.slice(0);
var transports = self._remainingTransports;
while(transports.length > 0 && transports.splice(0,1)[0] != self.transport.type){}
if(transports.length){
self.transport = self.getTransport(transports);
self.connect();
}
}
if(!self._remainingTransports || self._remainingTransports.length == 0) self.emit('connect_failed');
}
if(self._remainingTransports && self._remainingTransports.length == 0) delete self._remainingTransports;
}, this.options.connectTimeout);
}
}
@@ -852,6 +863,7 @@ JSONPPolling.xdomainCheck = function(){
};
Socket.prototype.disconnect = function(){
if (this.connectTimeoutTimer) clearTimeout(this.connectTimeoutTimer);
this.transport.disconnect();
return this;
};
@@ -862,14 +874,15 @@ JSONPPolling.xdomainCheck = function(){
return this;
};
Socket.prototype.fire = function(name, args){
if (name in this._events){
for (var i = 0, ii = this._events[name].length; i < ii; i++)
this._events[name][i].apply(this, args === undefined ? [] : args);
}
return this;
};
Socket.prototype.emit = function(name, args){
if (name in this._events){
var events = this._events[name].concat();
for (var i = 0, ii = events.length; i < ii; i++)
events[i].apply(this, args === undefined ? [] : args);
}
return this;
};
Socket.prototype.removeEvent = function(name, fn){
if (name in this._events){
for (var a = 0, l = this._events[name].length; a < l; a++)
@@ -900,11 +913,11 @@ JSONPPolling.xdomainCheck = function(){
this.connecting = false;
this._doQueue();
if (this.options.rememberTransport) this.options.document.cookie = 'socketio=' + encodeURIComponent(this.transport.type);
this.fire('connect');
this.emit('connect');
};
Socket.prototype._onMessage = function(data){
this.fire('message', [data]);
this.emit('message', [data]);
};
Socket.prototype._onDisconnect = function(){
@@ -912,12 +925,15 @@ JSONPPolling.xdomainCheck = function(){
this.connected = false;
this.connecting = false;
this._queueStack = [];
if (wasConnected) this.fire('disconnect');
if (wasConnected) this.emit('disconnect');
};
Socket.prototype.fire = Socket.prototype.emit;
Socket.prototype.addListener = Socket.prototype.addEvent = Socket.prototype.addEventListener = Socket.prototype.on;
})();
/* SWFObject v2.2 <http://code.google.com/p/swfobject/>
is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
*/
@@ -1537,8 +1553,10 @@ ASProxy.prototype =
if (window.WebSocket) return;
var console = window.console;
if (!console) console = {log: function(){ }, error: function(){ }};
if (!console || !console.log || !console.error) {
console = {log: function(){ }, error: function(){ }};
}
if (!swfobject.hasFlashPlayerVersion("9.0.0")) {
console.error("Flash Player is not installed.");
return;
@@ -1560,73 +1578,22 @@ ASProxy.prototype =
WebSocket.__addTask(function() {
self.__createFlash(url, protocol, proxyHost, proxyPort, headers);
});
}, 1);
}
}, 0);
};
WebSocket.prototype.__createFlash = function(url, protocol, proxyHost, proxyPort, headers) {
var self = this;
self.__flash =
WebSocket.__flash.create(url, protocol, proxyHost || null, proxyPort || 0, headers || null);
self.__flash.addEventListener("open", function(fe) {
try {
self.readyState = self.__flash.getReadyState();
if (self.__timer) clearInterval(self.__timer);
if (window.opera) {
// Workaround for weird behavior of Opera which sometimes drops events.
self.__timer = setInterval(function () {
self.__handleMessages();
}, 500);
}
if (self.onopen) self.onopen();
} catch (e) {
console.error(e.toString());
}
self.__flash.addEventListener("event", function(fe) {
// Uses setTimeout() to workaround the error:
// > You are trying to call recursively into the Flash Player which is not allowed.
setTimeout(function() { self.__handleEvents(); }, 0);
});
self.__flash.addEventListener("close", function(fe) {
try {
self.readyState = self.__flash.getReadyState();
if (self.__timer) clearInterval(self.__timer);
if (self.onclose) self.onclose();
} catch (e) {
console.error(e.toString());
}
});
self.__flash.addEventListener("message", function() {
try {
self.__handleMessages();
} catch (e) {
console.error(e.toString());
}
});
self.__flash.addEventListener("error", function(fe) {
try {
if (self.__timer) clearInterval(self.__timer);
if (self.onerror) self.onerror();
} catch (e) {
console.error(e.toString());
}
});
self.__flash.addEventListener("stateChange", function(fe) {
try {
self.readyState = self.__flash.getReadyState();
self.bufferedAmount = fe.getBufferedAmount();
} catch (e) {
console.error(e.toString());
}
});
//console.log("[WebSocket] Flash object is ready");
};
WebSocket.prototype.send = function(data) {
if (this.__flash) {
this.readyState = this.__flash.getReadyState();
}
if (!this.__flash || this.readyState == WebSocket.CONNECTING) {
throw "INVALID_STATE_ERR: Web Socket connection has not been established";
}
@@ -1640,7 +1607,7 @@ ASProxy.prototype =
if (result < 0) { // success
return true;
} else {
this.bufferedAmount = result;
this.bufferedAmount += result;
return false;
}
};
@@ -1648,7 +1615,6 @@ ASProxy.prototype =
WebSocket.prototype.close = function() {
var self = this;
if (!self.__flash) return;
self.readyState = self.__flash.getReadyState();
if (self.readyState == WebSocket.CLOSED || self.readyState == WebSocket.CLOSING) return;
self.__flash.close();
// Sets/calls them manually here because Flash WebSocketConnection.close cannot fire events
@@ -1659,7 +1625,7 @@ ASProxy.prototype =
if (self.onclose) {
// Make it asynchronous so that it looks more like an actual
// close event
setTimeout(self.onclose, 1);
setTimeout(self.onclose, 0);
}
};
@@ -1728,30 +1694,62 @@ ASProxy.prototype =
}
};
WebSocket.prototype.__handleMessages = function() {
// Gets data using readSocketData() instead of getting it from event object
WebSocket.prototype.__handleEvents = function() {
// Gets events using receiveEvents() instead of getting it from event object
// of Flash event. This is to make sure to keep message order.
// It seems sometimes Flash events don't arrive in the same order as they are sent.
var arr = this.__flash.readSocketData();
for (var i = 0; i < arr.length; i++) {
var data = decodeURIComponent(arr[i]);
var events = this.__flash.receiveEvents();
for (var i = 0; i < events.length; i++) {
try {
if (this.onmessage) {
var e;
if (window.MessageEvent) {
e = document.createEvent("MessageEvent");
e.initMessageEvent("message", false, false, data, null, null, window, null);
} else { // IE
e = {data: data};
var event = events[i];
if ("readyState" in event) {
this.readyState = event.readyState;
}
if (event.type == "open") {
if (this.__timer) clearInterval(this.__timer);
if (window.opera) {
// Workaround for weird behavior of Opera which sometimes drops events.
this.__timer = setInterval(function () {
this.__handleEvents();
}, 500);
}
this.onmessage(e);
if (this.onopen) this.onopen();
} else if (event.type == "close") {
if (this.__timer) clearInterval(this.__timer);
if (this.onclose) this.onclose();
} else if (event.type == "message") {
if (this.onmessage) {
var data = decodeURIComponent(event.data);
var e;
if (window.MessageEvent && !window.opera) {
e = document.createEvent("MessageEvent");
e.initMessageEvent("message", false, false, data, null, null, window, null);
} else {
// IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
e = {data: data};
}
this.onmessage(e);
}
} else if (event.type == "error") {
if (this.__timer) clearInterval(this.__timer);
if (this.onerror) this.onerror();
} else {
throw "unknown event type: " + event.type;
}
} catch (e) {
console.error(e.toString());
}
}
};
/**
* @param {object} object
* @param {string} type
@@ -1766,7 +1764,7 @@ ASProxy.prototype =
}
object.dispatchEvent(event, arguments);
};
}
};
/**
* Basic implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface">DOM 2 EventInterface</a>}
@@ -1827,6 +1825,12 @@ ASProxy.prototype =
WebSocket.__tasks = [];
WebSocket.loadFlashPolicyFile = function(url) {
WebSocket.__addTask(function() {
WebSocket.__flash.loadManualPolicyFile(url);
});
}
WebSocket.__initialize = function() {
if (WebSocket.__swfLocation) {
// For backword compatibility.

View File

@@ -1,9 +1,10 @@
var io = require('socket.io')
, assert = require('assert')
, Listener = io.Listener;
module.exports = {
'test server initialization': function(assert){
'test server initialization': function(){
var _server = require('http').createServer(function(){})
, _socket = io.listen(_server, { log: null });
assert.ok(_socket instanceof Listener);
@@ -14,4 +15,4 @@ module.exports = {
})
}
};
};

View File

@@ -1,6 +1,7 @@
var http = require('http')
, net = require('net')
, io = require('socket.io')
, assert = require('assert')
, decode = require('socket.io/utils').decode
, port = 7100
, Listener = io.Listener
@@ -30,7 +31,7 @@ function client(server, sessid){
module.exports = {
'test serving static javascript client': function(assert){
'test serving static javascript client': function(){
var _server = server()
, _socket = socket(_server);
assert.response(_server
@@ -50,7 +51,7 @@ module.exports = {
, { headers: { 'Content-Type': 'application/x-shockwave-flash' }});
},
'test serving non-socket.io requests': function(assert){
'test serving non-socket.io requests': function(){
var _server = server()
, _socket = socket(_server);
_server.on('request', function(req, res){
@@ -64,8 +65,9 @@ module.exports = {
, { body: 'Hello world' });
},
'test destroying an upgrade connection that is not WebSocket': function(assert){
'test destroying an upgrade connection that is not WebSocket': function(){
var _server = server()
, _socket = socket(_server);
listen(_server, function(){
var client = http.createClient(_server._port)
@@ -80,12 +82,15 @@ module.exports = {
client.addListener('end', function(){
assert.ok(! upgraded);
_server.close();
})
});
client.on('error', function (e) {
// ignore errors
});
request.end();
});
},
'test broadcasting to clients': function(assert){
'test broadcasting to clients': function(){
var _server = server()
, _socket = socket(_server);
listen(_server, function(){
@@ -123,7 +128,7 @@ module.exports = {
})
},
'test connecting with an invalid sessionid': function(assert){
'test connecting with an invalid sessionid': function(){
var _server = server()
, _socket = socket(_server);
listen(_server, function(){
@@ -139,7 +144,7 @@ module.exports = {
});
},
'test connecting to an invalid transport': function(assert){
'test connecting to an invalid transport': function(){
var _server = server(function(req, res){
res.writeHead(200);
res.end(req.url == '/socket.io/inexistent' ? 'All cool' : '');
@@ -149,4 +154,4 @@ module.exports = {
assert.response(_server, { url: '/socket.io/inexistent' }, { body: 'All cool' });
}
};
};

View File

@@ -1,6 +1,7 @@
var io = require('socket.io')
, net = require('net')
, http = require('http')
, assert = require('assert')
, querystring = require('querystring')
, port = 7700
, encode = require('socket.io/utils').encode
@@ -31,7 +32,7 @@ function listen(s, callback){
module.exports = {
'test xml policy added to connection': function(assert){
'test xml policy added to connection': function(){
var _server = server()
, _socket = socket(_server);
listen(_server, function(){
@@ -45,4 +46,4 @@ module.exports = {
});
}
}
}

View File

@@ -1,5 +1,6 @@
var io = require('socket.io')
, net = require('net')
, assert = require('assert')
, http = require('http')
, querystring = require('querystring')
, port = 7600
@@ -62,7 +63,10 @@ function get(server, url, assert, callback){
});
client.end = function(){
request.connection.destroy();
if (request.abort)
request.abort();
else
request.connection.destroy();
};
return client;
@@ -77,7 +81,7 @@ function post(client, url, data, callback){
module.exports = {
'test connection and handshake': function(assert){
'test connection and handshake': function(){
var _server = server()
, _socket = socket(_server)
, trips = 2;
@@ -105,7 +109,6 @@ module.exports = {
_socket.on('connection', function(client){
assert.ok(client instanceof HTMLFile);
client.on('message', function(msg){
assert.ok(msg == 'from client');
--trips || close();
});
});
@@ -117,7 +120,7 @@ module.exports = {
});
},
'test clients tracking': function(assert){
'test clients tracking': function(){
var _server = server()
, _socket = socket(_server);
@@ -142,7 +145,7 @@ module.exports = {
});
},
'test buffered messages': function(assert){
'test buffered messages': function(){
var _server = server()
, _socket = socket(_server, { transportOptions: {
'htmlfile': {
@@ -181,7 +184,7 @@ module.exports = {
});
},
'test hearbeat timeout': function(assert){
'test hearbeat timeout': function(){
var _server = server()
, _socket = socket(_server, {
transportOptions: {
@@ -211,4 +214,4 @@ module.exports = {
});
}
};
};

View File

@@ -1,6 +1,7 @@
var io = require('socket.io')
, http = require('http')
, querystring = require('querystring')
, assert = require('assert')
, port = 7500
, encode = require('socket.io/utils').encode
, _decode = require('socket.io/utils').decode
@@ -66,7 +67,7 @@ function post(client, url, data, callback){
module.exports = {
'test connection and handshake': function(assert){
'test connection and handshake': function(){
var _server = server()
, _socket = socket(_server)
, trips = 2;
@@ -98,7 +99,7 @@ module.exports = {
});
},
'test clients tracking': function(assert){
'test clients tracking': function(){
var _server = server()
, _socket = socket(_server);
@@ -113,7 +114,7 @@ module.exports = {
});
},
'test buffered messages': function(assert){
'test buffered messages': function(){
var _server = server()
, _socket = socket(_server, { transportOptions: {
'jsonp-polling': {
@@ -145,4 +146,4 @@ module.exports = {
});
}
};
};

View File

@@ -1,4 +1,5 @@
var io = require('socket.io')
, assert = require('assert')
, encode = require('socket.io/utils').encode
, decode = require('socket.io/utils').decode
, port = 7200
@@ -30,7 +31,7 @@ function client(server, sessid){
module.exports = {
'test connection and handshake': function(assert){
'test connection and handshake': function(){
var _server = server()
, _socket = socket(_server)
, _client
@@ -66,7 +67,7 @@ module.exports = {
});
},
'test clients tracking': function(assert){
'test clients tracking': function(){
var _server = server()
, _socket = socket(_server);
@@ -87,7 +88,7 @@ module.exports = {
});
},
'test buffered messages': function(assert){
'test buffered messages': function(){
var _server = server()
, _socket = socket(_server, {
transportOptions: {
@@ -128,7 +129,7 @@ module.exports = {
});
},
'test json encoding': function(assert){
'test json encoding': function(){
var _server = server()
, _socket = socket(_server)
, _client
@@ -162,7 +163,7 @@ module.exports = {
});
},
'test hearbeat timeout': function(assert){
'test hearbeat timeout': function(){
var _server = server()
, _socket = socket(_server, {
transportOptions: {
@@ -191,7 +192,7 @@ module.exports = {
});
},
'test client broadcast': function(assert){
'test client broadcast': function(){
var _server = server()
, _socket = socket(_server);
@@ -242,4 +243,4 @@ module.exports = {
});
}
};
};

View File

@@ -1,6 +1,7 @@
var io = require('socket.io')
, net = require('net')
, http = require('http')
, assert = require('assert')
, querystring = require('querystring')
, port = 7300
, encode = require('socket.io/utils').encode
@@ -78,7 +79,7 @@ function post(client, url, data, callback){
module.exports = {
'test connection and handshake': function(assert){
'test connection and handshake': function(){
var _server = server()
, _socket = socket(_server)
, trips = 2;
@@ -118,7 +119,7 @@ module.exports = {
});
},
'test clients tracking': function(assert){
'test clients tracking': function(){
var _server = server()
, _socket = socket(_server);
@@ -143,7 +144,7 @@ module.exports = {
});
},
'test buffered messages': function(assert){
'test buffered messages': function(){
var _server = server()
, _socket = socket(_server, { transportOptions: {
'xhr-multipart': {
@@ -182,7 +183,7 @@ module.exports = {
});
},
'test hearbeat timeout': function(assert){
'test hearbeat timeout': function(){
var _server = server()
, _socket = socket(_server, {
transportOptions: {
@@ -212,4 +213,4 @@ module.exports = {
});
}
};
};

View File

@@ -1,5 +1,6 @@
var io = require('socket.io')
, http = require('http')
, assert = require('assert')
, querystring = require('querystring')
, port = 7400
, encode = require('socket.io/utils').encode
@@ -56,7 +57,7 @@ function post(client, url, data, callback){
module.exports = {
'test connection and handshake': function(assert){
'test connection and handshake': function(){
var _server = server()
, _socket = socket(_server)
, trips = 2;
@@ -88,7 +89,7 @@ module.exports = {
});
},
'test clients tracking': function(assert){
'test clients tracking': function(){
var _server = server()
, _socket = socket(_server);
@@ -103,7 +104,7 @@ module.exports = {
});
},
'test buffered messages': function(assert){
'test buffered messages': function(){
var _server = server()
, _socket = socket(_server, { transportOptions: {
'xhr-polling': {
@@ -135,4 +136,4 @@ module.exports = {
});
}
};
};

View File

@@ -1,25 +1,26 @@
var encode = require('socket.io/utils').encode,
var assert = require('assert')
encode = require('socket.io/utils').encode,
decode = require('socket.io/utils').decode;
module.exports = {
'test decoding': function(assert){
'test decoding': function(){
var decoded = decode('~m~5~m~abcde' + '~m~9~m~123456789');
assert.equal(decoded.length, 2);
assert.equal(decoded[0], 'abcde');
assert.equal(decoded[1], '123456789');
},
'test decoding of bad framed messages': function(assert){
'test decoding of bad framed messages': function(){
var decoded = decode('~m~5~m~abcde' + '~m\uffsdaasdfd9~m~1aaa23456789');
assert.equal(decoded.length, 1);
assert.equal(decoded[0], 'abcde');
assert.equal(decoded[1], undefined);
},
'test encoding': function(assert){
'test encoding': function(){
assert.equal(encode(['abcde', '123456789']), '~m~5~m~abcde' + '~m~9~m~123456789');
assert.equal(encode('asdasdsad'), '~m~9~m~asdasdsad');
assert.equal(encode(''), '~m~0~m~');
assert.equal(encode(null), '~m~0~m~');
}
};
};