Compare commits

...

158 Commits
0.8.1 ... 0.8.6

Author SHA1 Message Date
Guillermo Rauch
abd0326b06 Release 0.8.6 2011-10-27 20:10:18 +09:00
Guillermo Rauch
3e0b4488f8 Merge pull request #599 from 3rd-Eden/jsonptests
The server expects JSON.stringify'd packages
2011-10-27 02:44:50 -07:00
Arnout Kazemier
86908c3b4d The server expects JSON.stringify'd packages 2011-10-27 09:10:27 +02:00
Guillermo Rauch
5491c2798e Merge branch 'master' of github.com:LearnBoost/socket.io 2011-10-27 12:54:03 +08:00
Guillermo Rauch
a9def6e209 Merge pull request #598 from 3rd-Eden/utf8
charset=UTF8
2011-10-26 21:53:46 -07:00
Guillermo Rauch
c7a2dc45c8 Merge pull request #591 from einaros/master
Resubmission of: Minor potential issues corrected in websocket transports
2011-10-26 21:51:52 -07:00
Guillermo Rauch
cf76b13145 Added JSON decoding on jsonp-polling transport.
This is due to browser's buggy handling of outgoing \n
2011-10-27 12:45:19 +08:00
Arnout Kazemier
db2a17f279 charset=UTF8 2011-10-26 11:48:41 +02:00
einaros
67495ad8a9 case-insensitive match for websocket upgrade, in all websocket transports 2011-10-22 10:21:10 +02:00
einaros
bee1efb11c fixes #555 2011-10-22 10:16:48 +02:00
Guillermo Rauch
120924f626 Merge pull request #582 from chees/patch-1
Changed sockets.emit to io.sockets.emit to get the example working.
2011-10-16 10:00:20 -07:00
Christiaan
acdbacb25e Changed sockets.emit to io.sockets.emit to get the example working. 2011-10-16 14:59:14 +03:00
TJ Holowaychuk
cde6a38218 Merge pull request #580 from 3rd-Eden/performance
Added a benchmark runner + profile option
2011-10-15 12:12:14 -07:00
Guillermo Rauch
e69c185e17 Merge pull request #581 from dshaw/patch/520
Fixes #520. Updates to latest node_redis.
2011-10-15 11:47:41 -07:00
Daniel Shaw
8cab86af1c Fixes #520. Updates to latest node_redis. 2011-10-15 11:43:48 -07:00
Arnout Kazemier
00557f663a Added error and heartbeat decoding 2011-10-15 15:30:23 +02:00
Arnout Kazemier
54c22aea96 Added a benchmark runner + profile option 2011-10-15 15:10:35 +02:00
Guillermo Rauch
a9929c916f Merge pull request #578 from 3rd-Eden/performance
Use benchmark.js instead, so we can benchmark 0.5.9
2011-10-13 13:42:19 -07:00
Arnout Kazemier
1d66b6b5da Some tiny optimizations 2011-10-13 22:31:52 +02:00
Arnout Kazemier
a75670c1c2 Use benchmark.js instead, so we can benchmark 0.5.9 2011-10-13 21:00:59 +02:00
Guillermo Rauch
373c729e66 Merge pull request #573 from 3rd-Eden/performance
Parser performance boost
2011-10-12 15:10:19 -07:00
Arnout Kazemier
7800003c5e Returned the switch for the decoder, optimized the switch for the encoder 2011-10-12 22:03:46 +02:00
Arnout Kazemier
b662f2e14e Optimized the loop, so the most commen packets are checked first 2011-10-12 21:53:38 +02:00
Arnout Kazemier
709c172444 http://cl.ly/402t2C133B2a1P0g1K0h <--- before http://cl.ly/363W080c2j261l3A1m3y <--- after ;o 2011-10-12 21:44:09 +02:00
Tj Holowaychuk
6d5ffa0d33 remove vbench dev dep
so people dont need node-canvas
2011-10-11 21:38:31 -07:00
Tj Holowaychuk
f8c7ff2782 Added decode/encode benchmarks 2011-10-11 16:00:15 -07:00
Guillermo Rauch
175fe8573b Merge pull request #563 from 3rd-Eden/logger
Logger
2011-10-11 08:18:16 -07:00
Guillermo Rauch
0224e4ac5f Merge pull request #569 from 3rd-Eden/blacklist
Blacklist events
2011-10-11 08:13:55 -07:00
Arnout Kazemier
ecd20b0e1f Added support for blacklisting events that are emitted from the client side.
Currently it's possible for a client do .emit('disconnect') and this will trigger
the disconnect event on the server.. Which can lead to major issues.

We should black list that by default. You can override or add more events by adding
them to the `blacklist` setting
2011-10-11 10:40:22 +02:00
Arnout Kazemier
b8f6dc7810 Inital stab at blacklisting client side events 2011-10-11 09:53:26 +02:00
Guillermo Rauch
0339e745fd Merge pull request #564 from 3rd-Eden/bug/538
fixes #538
2011-10-10 13:23:51 -07:00
Arnout Kazemier
b3740e9ab6 fixes #538 2011-10-10 21:52:16 +02:00
Arnout Kazemier
0b7ed64082 Fixed logging options, closes #540 2011-10-10 21:32:54 +02:00
Arnout Kazemier
07b84f4400 Added a test to ensure that etags do no mess up far future headers 2011-10-10 21:06:58 +02:00
Guillermo Rauch
59e4c3b46c Merge pull request #562 from 3rd-Eden/static
Hardcore caching for pro's
2011-10-10 11:20:38 -07:00
Arnout Kazemier
0e3bbd0e16 Whoops 2011-10-10 20:02:37 +02:00
Arnout Kazemier
763fdd1c4e Added more hardcore caching fixes #558
Added tests against it
Added vary header for gzip
2011-10-10 20:01:28 +02:00
Guillermo Rauch
61bd23f0f9 Merge pull request #561 from einaros/master
Proper websocket test fixes, after patching node-websocket-client
2011-10-09 01:33:13 -07:00
Arnout Kazemier
8107c1a1e2 Added support for HEAD requests, closes #557 2011-10-08 15:46:23 +02:00
Arnout Kazemier
fa5b518110 Merge branch 'master' of github.com:LearnBoost/socket.io into static 2011-10-08 15:42:21 +02:00
einaros
b3df2836e9 properly cleaned up async websocket / flashsocket tests, after patching node-websocket-client 2011-10-08 12:02:31 +02:00
einaros
08568ee49e patched to properly shut down when a finishClose call is made during connection establishment 2011-10-08 12:01:46 +02:00
einaros
aba2d5e0ef removed empty console.log 2011-10-08 12:00:55 +02:00
Guillermo Rauch
dfebed38ab Release 0.8.5 2011-10-07 11:25:39 -07:00
Guillermo Rauch
51782fc5d7 Fixed websocket tests. 2011-10-07 11:07:33 -07:00
Guillermo Rauch
11f1a7c491 Merge pull request #347 from 3rd-Eden/socket.transport
Expose socket.transport
2011-10-07 10:07:05 -07:00
Arnout Kazemier
5573f7fcdf First stab of adding Expires + cache control headers for #558 2011-10-05 20:16:20 +02:00
Guillermo Rauch
1d743cfc84 Merge branch 'master' of github.com:LearnBoost/socket.io 2011-10-04 21:21:31 -07:00
Guillermo Rauch
21c01558fd Added tip [getify] 2011-10-04 21:21:20 -07:00
Guillermo Rauch
6d57445167 Fixed readme [getify] 2011-10-04 21:18:02 -07:00
Guillermo Rauch
52f6a5b124 Merge pull request #554 from einaros/master
Fixes issue #553: Origin verification brought into the client
2011-10-02 10:59:41 -07:00
einaros
2c3c73f045 corrected origin header name in test 2011-10-02 16:37:14 +02:00
einaros
54fc513fc9 fixes #553 - (re)verify origin in websocket transport implementations 2011-10-02 16:34:09 +02:00
einaros
0b1e43cb87 added binary support to the hybi 07-12 parsers as well 2011-10-02 15:31:36 +02:00
einaros
c8dabb225c added binary support to hybi-16 parser, refactored hybi tests somewhat 2011-10-02 15:19:17 +02:00
einaros
245dc12ade added fixes from hybi-07-12 which also apply to 16 2011-10-02 00:09:21 +02:00
Guillermo Rauch
7a405232a5 Merge pull request #550 from einaros/master
HyBi-16 support, and a few bugfixes
2011-10-01 12:15:16 -07:00
einaros
00f7ca1d02 fixed incorrect space chars 2011-10-01 14:45:20 +02:00
einaros
f1cea7e788 Merge branch 'master' of github.com:einaros/socket.io 2011-10-01 14:42:07 +02:00
einaros
050fcf7a83 websocket draft HyBi-16 support 2011-10-01 14:39:27 +02:00
Guillermo Rauch
dfa350bea7 Revert "Expanded 'How to Use' in readme"
This reverts commit ed7cedd78f.
2011-09-26 13:27:52 -07:00
Guillermo Rauch
e5d5b99f0e Merge pull request #539 from AlexChesser/alex.chesser
Expanded 'How to Use' in readme
2011-09-26 13:26:42 -07:00
Alex Chesser
ed7cedd78f Expanded 'How to Use' in readme 2011-09-26 15:35:37 -04:00
einaros
a22eb70cfb Merge remote branch 'upstream/master' 2011-09-26 10:21:09 +02:00
einaros
2a81b25a5b fixed websocket continuation bugs 2011-09-26 10:14:19 +02:00
Guillermo Rauch
7db146df47 Merge pull request #457 from 3rd-Eden/ACAO
access-control-allow-origin
2011-09-20 15:04:59 -07:00
Guillermo Rauch
6182dfff39 Merge pull request #532 from einaros/master
Fixes issue 523, and (unreported?) issue where flashsocket identifies as websocket
2011-09-20 10:03:42 -07:00
einaros
1ccd8cea6b Merge remote branch 'upstream/master' 2011-09-20 18:56:08 +02:00
einaros
f9ea04eb6b redirect actual transport name (such as flashsocket), if present, to the websocket version being loaded 2011-09-20 18:53:04 +02:00
einaros
ab9a5a1578 added test to ensure that websocket still identifies as .. websocket 2011-09-20 18:51:36 +02:00
einaros
3364a73a97 added done() call to end test 2011-09-20 18:45:01 +02:00
einaros
d02a7f415b added test for flashsocket name, to catch issues with various websocket versions 2011-09-20 18:41:06 +02:00
einaros
1874fd7c30 added transport option to websocket test client (for flashsocket testing) 2011-09-20 18:40:43 +02:00
einaros
3c4a04ea02 fixes #523. private 'payload' method in transport used from manager.js, wasn't present in hybi parser 2011-09-20 17:56:32 +02:00
Guillermo Rauch
1468917743 Merge pull request #529 from dshaw/patch/redisstore
Fix RedisStore
2011-09-19 10:06:52 -07:00
Arnout Kazemier
6df152cc5d Merge branch 'master' of github.com:LearnBoost/socket.io into ACAO
Conflicts:
	lib/manager.js
2011-09-13 21:45:51 +02:00
Daniel Shaw
c6b3549b61 Minimal RedisClient configs. 2011-09-12 00:20:40 -07:00
Daniel Shaw
f80ab2aae8 Merge branch 'master' of https://github.com/LearnBoost/socket.io 2011-09-11 23:06:23 -07:00
Guillermo Rauch
b2f9f19d99 Merge pull request #391 from 3rd-Eden/static
Static
2011-09-07 09:39:30 -07:00
Guillermo Rauch
cb7304837c Ensured parser#decodePayload doesn't choke.
This addresses the situation where malformed data is supplied to the parser.
Fixes #510
2011-09-07 08:31:46 -07:00
Guillermo Rauch
9e6f58fe27 Release 0.8.4 2011-09-06 07:48:14 -07:00
Guillermo Rauch
e3fb39da3d Corrected comment; (fixes #433) 2011-09-03 14:33:00 -07:00
Guillermo Rauch
cc275813b5 Updated changelog 2011-09-03 14:06:05 -07:00
Guillermo Rauch
9d57245d65 Release 0.8.3 2011-09-03 12:42:45 -07:00
Guillermo Rauch
9a05b3597e Fixed test for default flash port. 2011-09-03 12:34:36 -07:00
Guillermo Rauch
41f38b60e8 Added parser test for decoding newline. 2011-09-03 12:23:44 -07:00
Guillermo Rauch
a9bbc38919 Fixed \n parsing for non-JSON packets (fixes #479). 2011-09-03 12:14:07 -07:00
Guillermo Rauch
e282ab0e63 Fixed transport message packet logging.
Fixed style.
2011-09-03 12:13:50 -07:00
Guillermo Rauch
546d5203d4 Added test case for #476 to prevent regressions. 2011-09-03 10:49:08 -07:00
Guillermo Rauch
69941e602b Fixed emission of error event resulting in an uncaught exception if unhandled (fixes #476). 2011-09-03 10:48:31 -07:00
Guillermo Rauch
7ac9c2e888 Fixed style 2011-09-03 10:48:23 -07:00
Guillermo Rauch
fa1f50d173 Fixed; allow for falsy values as the configuration value of log level (fixes #491). 2011-09-03 10:29:51 -07:00
Guillermo Rauch
20ddd5f11a Merge branch 'master' of github.com:LearnBoost/socket.io 2011-09-03 10:19:17 -07:00
Guillermo Rauch
e1891fd615 Fixed repository URI in package.json. Fixes #504. 2011-09-03 10:18:52 -07:00
Daniel Shaw
cc0a96a8d9 Wow, really. 2011-09-01 14:22:53 -07:00
Daniel Shaw
7b2b302022 merge learnboost/master 2011-09-01 14:00:17 -07:00
Guillermo Rauch
4d66f78ca2 Merge pull request #502 from einaros/master
Correction on previous pull req, which left out status code
2011-09-01 12:12:32 -07:00
einaros
6db6db41a2 corrected passing of status code for handshake error 2011-08-31 19:55:28 +02:00
Guillermo Rauch
713baa40e1 Merge pull request #455 from 3rd-Eden/update/package
Added socket.io-flashsocket default port
2011-08-31 09:32:16 -07:00
Guillermo Rauch
7c196f5b32 Merge pull request #501 from einaros/master
Fixes issue commented on in #377
2011-08-31 08:50:44 -07:00
einaros
bd360a15ef added text/plain content-type to handshake responses 2011-08-31 14:48:43 +02:00
einaros
ae7f25332a switched \u**** to \x** for single byte writes, as the former doesn't really make any sense 2011-08-30 11:17:32 +02:00
einaros
63fc15d276 style changes 2011-08-30 11:03:50 +02:00
Guillermo Rauch
d88575dadf Release 0.8.2 2011-08-29 17:07:29 -07:00
Guillermo Rauch
93c963e30f Release 0.8.1 2011-08-29 17:07:29 -07:00
einaros
72a79e5cec fixed utf8 bug in send framing 2011-08-29 17:07:29 -07:00
Markus Hedlund
140ed41907 Fixed typo. 2011-08-29 17:07:29 -07:00
einaros
12fc168516 fixed bug in send framing for over 64kB of data 2011-08-29 17:07:29 -07:00
einaros
2c3dc42ae8 corrected ping handling from websocket transport, and added warning output on parser error 2011-08-29 17:07:29 -07:00
einaros
48dadd8e10 joined compatible hybi protocol handlers and updated test reference 2011-08-29 17:07:29 -07:00
einaros
ce4c46b37d fixed Parser library path and did some code cleanup 2011-08-29 17:07:29 -07:00
Guillermo Rauch
c8938a99b2 Release 0.8.0 2011-08-29 17:07:29 -07:00
Guillermo Rauch
b7998e815a Renamed wsver/ -> websocket/ 2011-08-29 17:07:29 -07:00
Guillermo Rauch
444229a9dc Changed protocols require path. 2011-08-29 17:07:28 -07:00
einaros
b69dac6f4d added hybi07 tests 2011-08-29 17:07:28 -07:00
einaros
94fdbadaec added initial hybi07 protocol parser 2011-08-29 17:07:28 -07:00
Guillermo Rauch
c4b23246b4 Release 0.7.11 2011-08-29 17:07:28 -07:00
einaros
5186788969 cleanups and comments 2011-08-29 17:07:28 -07:00
Guillermo Rauch
169ad8245f Release 0.7.10 2011-08-29 17:07:28 -07:00
einaros
71e013a197 added test and support for really long messages, but capped by 32 bit 2011-08-29 17:07:28 -07:00
einaros
7e60d37171 minor cleanups 2011-08-29 17:07:28 -07:00
einaros
a9e9e64eab updated to work with two-level websocket versioning 2011-08-29 17:07:28 -07:00
einaros
39ae8d4629 added hybi10 close operation 2011-08-29 17:07:28 -07:00
einaros
a075870308 added hybi10 parser tests 2011-08-29 17:07:28 -07:00
einaros
186649102d added hybi10 support 2011-08-29 17:07:28 -07:00
einaros
bb2e100e7f Added http referrer verification to manager.js verifyOrigin + tests for origins setting 2011-08-29 17:07:28 -07:00
Guillermo Rauch
2c5fa40c0d Release 0.8.2 2011-08-29 10:36:23 -07:00
Daniel Shaw
dd30de3c5a Merge remote-tracking branch 'learnboost/master' 2011-08-16 15:26:30 -07:00
Arnout Kazemier
e269fcaf0d Fixed semicolon 2011-08-10 22:58:13 +02:00
Arnout Kazemier
a8c61b0001 undo 2011-08-10 22:57:25 +02:00
Arnout Kazemier
4708480e7d Added access control for cross domain xhr handshakes 2011-08-10 22:35:30 +02:00
Arnout Kazemier
00b75759f1 Added socket.io-flashsocket default port 2011-08-09 19:33:27 +02:00
Daniel Shaw
30284944b1 Merge remote-tracking branch 'learnboost/master' 2011-08-08 11:08:28 -07:00
Daniel Shaw
559d36601d Merge remote-tracking branch 'learnboost/master' 2011-08-06 19:07:27 -07:00
Daniel Shaw
1fa158c663 Merge with learnboost/master 2011-08-03 13:22:36 -07:00
Arnout Kazemier
a631f86b3b Merged with upstream master 2011-07-31 10:22:54 +02:00
Daniel Shaw
831f1baa4a Merge remote-tracking branch 'learnboost/master' 2011-07-28 16:38:48 -07:00
Arnout Kazemier
0d3441d8b3 Added gzip to the options list and fixed a typo 2011-07-18 00:20:05 +02:00
Arnout Kazemier
23e14223bd Added more test cases and allow a more flexible constructor for adding content 2011-07-17 23:50:06 +02:00
Arnout Kazemier
4b94f2b8bf Passes all 14 tests 2011-07-17 20:21:08 +02:00
Arnout Kazemier
f88eedc3c8 More tests 2011-07-17 11:13:12 +02:00
Arnout Kazemier
5c24bf8c1d Removed old testcases from manager and tiny fix for Static 2011-07-17 01:24:21 +02:00
Arnout Kazemier
9cd51b1f6b Passes test suite 2011-07-17 00:10:43 +02:00
Arnout Kazemier
2f8eb63557 Added support for automatic generation of socket.io files
Added support for custom socket.io files based on path structure

For example socket.io+websocket+xhr-polling.js will create a dedicated build with
only support for websockets and xhr-polling.
2011-07-16 21:55:06 +02:00
Arnout Kazemier
4bdc30734c Fixed small typos 2011-07-15 23:59:10 +02:00
Arnout Kazemier
4743744efc Expose the constructor in socket.io and have the Manager use the new API 2011-07-15 22:31:03 +02:00
Arnout Kazemier
c2d98bde72 Add now stores the request details and has returns the details for static content 2011-07-15 22:10:51 +02:00
Arnout Kazemier
9e97e6c691 Added more comments 2011-07-15 00:30:25 +02:00
Arnout Kazemier
afdfdb815e Finished the write function setup 2011-07-15 00:24:57 +02:00
Arnout Kazemier
d043c33351 Added the write and answer fn 2011-07-15 00:13:54 +02:00
Arnout Kazemier
168b207c6d Inital draft of gzip support 2011-07-14 21:24:26 +02:00
Arnout Kazemier
f6ebb7b8d6 API structure 2011-07-14 20:54:40 +02:00
Daniel Shaw
dfebed6c2f Fix typo. Also mentioned in @dluxemburg's #341. 2011-07-12 02:56:48 -07:00
Daniel Shaw
3a2545b497 merge 2011-07-12 01:22:54 -07:00
Daniel Shaw
ffa17e1205 Merge remote-tracking branch 'learnboost/master' 2011-07-11 00:16:07 -07:00
Daniel Shaw
59e250b186 Run initStore on store setting change. Fixes #367. 2011-07-11 00:14:37 -07:00
Daniel Shaw
9bf10ed051 Updated to the latest redis release. Lots of important patches. 2011-07-10 17:11:08 -07:00
Arnout Kazemier
9cfdb8ed97 Expose socket.transport 2011-07-06 00:10:25 +02:00
37 changed files with 3319 additions and 827 deletions

1
.gitignore vendored
View File

@@ -6,4 +6,5 @@ lib-cov
*.dat
*.out
*.pid
benchmarks/*.png
node_modules

View File

@@ -1,4 +1,61 @@
0.8.6 / 2011-10-27
==================
* Added JSON decoding on jsonp-polling transport.
* Fixed README example.
* Major speed optimizations [3rd-Eden] [einaros] [visionmedia]
* Added decode/encode benchmarks [visionmedia]
* Added support for black-listing client sent events.
* Fixed logging options, closes #540 [3rd-Eden]
* Added vary header for gzip [3rd-Eden]
* Properly cleaned up async websocket / flashsocket tests, after patching node-websocket-client
* Patched to properly shut down when a finishClose call is made during connection establishment
* Added support for socket.io version on url and far-future Expires [3rd-Eden] [getify]
* Began IE10 compatibility [einaros] [tbranyen]
* Misc WebSocket fixes [einaros]
* Added UTF8 to respone headers for htmlfile [3rd-Eden]
0.8.5 / 2011-10-07
==================
* Added websocket draft HyBi-16 support. [einaros]
* Fixed websocket continuation bugs. [einaros]
* Fixed flashsocket transport name.
* Fixed websocket tests.
* Ensured `parser#decodePayload` doesn't choke.
* Added http referrer verification to manager verifyOrigin.
* Added access control for cross domain xhr handshakes [3rd-Eden]
* Added support for automatic generation of socket.io files [3rd-Eden]
* Added websocket binary support [einaros]
* Added gzip support for socket.io.js [3rd-Eden]
* Expose socket.transport [3rd-Eden]
* Updated client.
0.8.4 / 2011-09-06
==================
* Client build
0.8.3 / 2011-09-03
==================
* Fixed `\n` parsing for non-JSON packets (fixes #479).
* Fixed parsing of certain unicode characters (fixes #451).
* Fixed transport message packet logging.
* Fixed emission of `error` event resulting in an uncaught exception if unhandled (fixes #476).
* Fixed; allow for falsy values as the configuration value of `log level` (fixes #491).
* Fixed repository URI in `package.json`. Fixes #504.
* Added text/plain content-type to handshake responses [einaros]
* Improved single byte writes [einaros]
* Updated socket.io-flashsocket default port from 843 to 10843 [3rd-Eden]
* Updated client.
0.8.2 / 2011-08-29
==================
* Updated client.
0.8.1 / 2011-08-29
==================

View File

@@ -1,5 +1,6 @@
ALL_TESTS = $(shell find test/ -name '*.test.js')
ALL_BENCH = $(shell find benchmarks -name '*.bench.js')
run-tests:
@./node_modules/.bin/expresso \
@@ -19,4 +20,13 @@ test-cov:
test-leaks:
@ls test/leaks/* | xargs node --expose_debug_as=debug --expose_gc
.PHONY: test
run-bench:
@node $(PROFILEFLAGS) benchmarks/runner.js
bench:
@$(MAKE) BENCHMARKS="$(ALL_BENCH)" run-bench
profile:
@PROFILEFLAGS='--prof --trace-opt --trace-bailout --trace-deopt' $(MAKE) bench
.PHONY: test bench profile

View File

@@ -67,7 +67,7 @@ io.sockets.on('connection', function (socket) {
});
socket.on('disconnect', function () {
sockets.emit('user disconnected');
io.sockets.emit('user disconnected');
});
});
```
@@ -222,7 +222,7 @@ io.sockets.on('connection', function (socket) {
```html
<script>
var socket = io.connect(); // TIP: .connect with no args does auto-discovery
socket.on('connection', function () {
socket.on('connect', function () { // TIP: you can avoid listening on `connect` and listen on events directly too!
socket.emit('ferret', 'tobi', function (data) {
console.log(data); // data will be 'woot'
});

View File

@@ -0,0 +1,64 @@
/**
* Module dependencies.
*/
var benchmark = require('benchmark')
, colors = require('colors')
, io = require('../')
, parser = io.parser
, suite = new benchmark.Suite('Decode packet');
suite.add('string', function () {
parser.decodePacket('4:::"2"');
});
suite.add('event', function () {
parser.decodePacket('5:::{"name":"woot"}');
});
suite.add('event+ack', function () {
parser.decodePacket('5:1+::{"name":"tobi"}');
});
suite.add('event+data', function () {
parser.decodePacket('5:::{"name":"edwald","args":[{"a": "b"},2,"3"]}');
});
suite.add('heartbeat', function () {
parser.decodePacket('2:::');
});
suite.add('error', function () {
parser.decodePacket('7:::2+0');
});
var payload = parser.encodePayload([
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
]);
suite.add('payload', function () {
parser.decodePayload(payload);
});
suite.on('cycle', function (bench, details) {
console.log('\n' + suite.name.grey, details.name.white.bold);
console.log([
details.hz.toFixed(2).cyan + ' ops/sec'.grey
, details.count.toString().white + ' times executed'.grey
, 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey
,
].join(', '.grey));
});
if (!module.parent) {
suite.run();
} else {
module.exports = suite;
}

View File

@@ -0,0 +1,90 @@
/**
* Module dependencies.
*/
var benchmark = require('benchmark')
, colors = require('colors')
, io = require('../')
, parser = io.parser
, suite = new benchmark.Suite('Encode packet');
suite.add('string', function () {
parser.encodePacket({
type: 'json'
, endpoint: ''
, data: '2'
});
});
suite.add('event', function () {
parser.encodePacket({
type: 'event'
, name: 'woot'
, endpoint: ''
, args: []
});
});
suite.add('event+ack', function () {
parser.encodePacket({
type: 'json'
, id: 1
, ack: 'data'
, endpoint: ''
, data: { a: 'b' }
});
});
suite.add('event+data', function () {
parser.encodePacket({
type: 'event'
, name: 'edwald'
, endpoint: ''
, args: [{a: 'b'}, 2, '3']
});
});
suite.add('heartbeat', function () {
parser.encodePacket({
type: 'heartbeat'
, endpoint: ''
})
});
suite.add('error', function () {
parser.encodePacket({
type: 'error'
, reason: 'unauthorized'
, advice: 'reconnect'
, endpoint: ''
})
})
suite.add('payload', function () {
parser.encodePayload([
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
]);
});
suite.on('cycle', function (bench, details) {
console.log('\n' + suite.name.grey, details.name.white.bold);
console.log([
details.hz.toFixed(2).cyan + ' ops/sec'.grey
, details.count.toString().white + ' times executed'.grey
, 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey
,
].join(', '.grey));
});
if (!module.parent) {
suite.run();
} else {
module.exports = suite;
}

55
benchmarks/runner.js Normal file
View File

@@ -0,0 +1,55 @@
/**
* Benchmark runner dependencies
*/
var colors = require('colors')
, path = require('path');
/**
* Find all the benchmarks
*/
var benchmarks_files = process.env.BENCHMARKS.split(' ')
, all = [].concat(benchmarks_files)
, first = all.shift()
, benchmarks = {};
// find the benchmarks and load them all in our obj
benchmarks_files.forEach(function (file) {
benchmarks[file] = require(path.join(__dirname, '..', file));
});
// setup the complete listeners
benchmarks_files.forEach(function (file) {
var benchmark = benchmarks[file]
, next_file = all.shift()
, next = benchmarks[next_file];
/**
* Generate a oncomplete function for the tests, either we are done or we
* have more benchmarks to process.
*/
function complete () {
if (!next) {
console.log(
'\n\nBenchmark completed in'.grey
, (Date.now() - start).toString().green + ' ms'.grey
);
} else {
console.log('\nStarting benchmark '.grey + next_file.yellow);
next.run();
}
}
// attach the listener
benchmark.on('complete', complete);
});
/**
* Start the benchmark
*/
var start = Date.now();
console.log('Starting benchmark '.grey + first.yellow);
benchmarks[first].run();

View File

@@ -60,6 +60,7 @@ var Logger = module.exports = function (opts) {
opts = opts || {}
this.colors = false !== opts.colors;
this.level = 3;
this.enabled = true;
};
/**
@@ -71,7 +72,7 @@ var Logger = module.exports = function (opts) {
Logger.prototype.log = function (type) {
var index = levels.indexOf(type);
if (index > this.level)
if (index > this.level || !this.enabled)
return this;
console.log.apply(

View File

@@ -18,6 +18,7 @@ var fs = require('fs')
, Socket = require('./socket')
, MemoryStore = require('./stores/memory')
, SocketNamespace = require('./namespace')
, Static = require('./static')
, EventEmitter = process.EventEmitter;
/**
@@ -61,21 +62,27 @@ function Manager (server, options) {
, log: true
, store: new MemoryStore
, logger: new Logger
, static: new Static(this)
, heartbeats: true
, resource: '/socket.io'
, transports: defaultTransports
, authorization: false
, blacklist: ['disconnect']
, 'log level': 3
, 'log colors': true
, 'close timeout': 25
, 'heartbeat timeout': 15
, 'heartbeat interval': 20
, 'polling duration': 20
, 'flash policy server': true
, 'flash policy port': 843
, 'flash policy port': 10843
, 'destroy upgrade': true
, 'browser client': true
, 'browser client cache': true
, 'browser client minification': false
, 'browser client etag': false
, 'browser client expires': 315360000
, 'browser client gzip': false
, 'browser client handler': false
, 'client store expiration': 15
};
@@ -84,14 +91,18 @@ function Manager (server, options) {
this.settings[i] = options[i];
}
var self = this;
this.initStore();
this.on('set:store', function() {
self.initStore();
});
// reset listeners
this.oldListeners = server.listeners('request');
server.removeAllListeners('request');
var self = this;
server.on('request', function (req, res) {
self.handleRequest(req, res);
});
@@ -138,14 +149,25 @@ Manager.prototype.__defineGetter__('store', function () {
*/
Manager.prototype.__defineGetter__('log', function () {
if (this.disabled('log')) return;
var logger = this.get('logger');
logger.level = this.get('log level');
logger.level = this.get('log level') || -1;
logger.colors = this.get('log colors');
logger.enabled = this.enabled('log');
return logger;
});
/**
* Static accessor.
*
* @api public
*/
Manager.prototype.__defineGetter__('static', function () {
return this.get('static');
});
/**
* Get settings.
*
@@ -516,7 +538,7 @@ Manager.prototype.handleRequest = function (req, res) {
if (data.static || !data.transport && !data.protocol) {
if (data.static && this.enabled('browser client')) {
this.handleClientRequest(req, res, data);
this.static.write(data.path, req, res);
} else {
res.writeHead(200);
res.end('Welcome to socket.io.');
@@ -652,104 +674,6 @@ Manager.prototype.handleClient = function (data, req) {
}
};
/**
* Dictionary for static file serving
*
* @api public
*/
Manager.static = {
cache: {}
, paths: {
'/static/flashsocket/WebSocketMain.swf': client.dist + '/WebSocketMain.swf'
, '/static/flashsocket/WebSocketMainInsecure.swf':
client.dist + '/WebSocketMainInsecure.swf'
, '/socket.io.js': client.dist + '/socket.io.js'
, '/socket.io.js.min': client.dist + '/socket.io.min.js'
}
, mime: {
'js': {
contentType: 'application/javascript'
, encoding: 'utf8'
}
, 'swf': {
contentType: 'application/x-shockwave-flash'
, encoding: 'binary'
}
}
};
/**
* Serves the client.
*
* @api private
*/
Manager.prototype.handleClientRequest = function (req, res, data) {
var static = Manager.static
, extension = data.path.split('.').pop()
, file = data.path + (this.enabled('browser client minification')
&& extension == 'js' ? '.min' : '')
, location = static.paths[file]
, cache = static.cache[file];
var self = this;
/**
* Writes a response, safely
*
* @api private
*/
function write (status, headers, content, encoding) {
try {
res.writeHead(status, headers || null);
res.end(content || '', encoding || null);
} catch (e) {}
}
function serve () {
if (req.headers['if-none-match'] === cache.Etag) {
return write(304);
}
var mime = static.mime[extension]
, headers = {
'Content-Type': mime.contentType
, 'Content-Length': cache.length
};
if (self.enabled('browser client etag') && cache.Etag) {
headers.Etag = cache.Etag;
}
write(200, headers, cache.content, mime.encoding);
self.log.debug('served static ' + data.path);
}
if (this.get('browser client handler')) {
this.get('browser client handler').call(this, req, res);
} else if (!cache) {
fs.readFile(location, function (err, data) {
if (err) {
write(500, null, 'Error serving static ' + data.path);
self.log.warn('Can\'t cache '+ data.path +', ' + err.message);
return;
}
cache = Manager.static.cache[file] = {
content: data
, length: data.length
, Etag: client.version
};
serve();
});
} else {
serve();
}
};
/**
* Generates a session id.
*
@@ -768,14 +692,18 @@ Manager.prototype.generateId = function () {
*/
Manager.prototype.handleHandshake = function (data, req, res) {
var self = this;
var self = this
, origin = req.headers.origin
, headers = {
'Content-Type': 'text/plain'
};
function writeErr (status, message) {
if (data.query.jsonp) {
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end('io.j[' + data.query.jsonp + '](new Error("' + message + '"));');
} else {
res.writeHead(status);
res.writeHead(status, headers);
res.end(message);
}
};
@@ -792,6 +720,15 @@ Manager.prototype.handleHandshake = function (data, req, res) {
var handshakeData = this.handshakeData(data);
if (origin) {
// https://developer.mozilla.org/En/HTTP_Access_Control
headers['Access-Control-Allow-Origin'] = '*';
if (req.headers.cookie) {
headers['Access-Control-Allow-Credentials'] = 'true';
}
}
this.authorize(handshakeData, function (err, authorized, newData) {
if (err) return error(err);
@@ -808,7 +745,7 @@ Manager.prototype.handleHandshake = function (data, req, res) {
hs = 'io.j[' + data.query.jsonp + '](' + JSON.stringify(hs) + ');';
res.writeHead(200, { 'Content-Type': 'application/javascript' });
} else {
res.writeHead(200);
res.writeHead(200, headers);
}
res.end(hs);
@@ -979,7 +916,7 @@ Manager.prototype.checkRequest = function (req) {
data.protocol = Number(pieces[1]);
data.transport = pieces[2];
data.id = pieces[3];
data.static = !!Manager.static.paths[path];
data.static = !!this.static.has(path);
};
return data;

View File

@@ -34,7 +34,7 @@ function SocketNamespace (mgr, name) {
SocketNamespace.prototype.__proto__ = EventEmitter.prototype;
/**
* Copies emit since we override it
* Copies emit since we override it.
*
* @api private
*/
@@ -103,7 +103,7 @@ SocketNamespace.prototype.__defineGetter__('volatile', function () {
});
/**
* Overrides the room to relay messages to (flag)
* Overrides the room to relay messages to (flag).
*
* @api public
*/
@@ -114,7 +114,7 @@ SocketNamespace.prototype.in = SocketNamespace.prototype.to = function (room) {
};
/**
* Adds a session id we should prevent relaying messages to (flag)
* Adds a session id we should prevent relaying messages to (flag).
*
* @api public
*/
@@ -139,7 +139,7 @@ SocketNamespace.prototype.setFlags = function () {
};
/**
* Sends out a packet
* Sends out a packet.
*
* @api private
*/
@@ -175,9 +175,9 @@ SocketNamespace.prototype.send = function (data) {
};
/**
* Emits to everyone (override)
* Emits to everyone (override).
*
* @api private
* @api public
*/
SocketNamespace.prototype.emit = function (name) {
@@ -196,7 +196,7 @@ SocketNamespace.prototype.emit = function (name) {
* Retrieves or creates a write-only socket for a client, unless specified.
*
* @param {Boolean} whether the socket will be readable when initialized
* @api private
* @api public
*/
SocketNamespace.prototype.socket = function (sid, readable) {
@@ -208,7 +208,7 @@ SocketNamespace.prototype.socket = function (sid, readable) {
};
/**
* Sets authorization for this namespace
* Sets authorization for this namespace.
*
* @api public
*/
@@ -264,6 +264,7 @@ SocketNamespace.prototype.authorize = function (data, fn) {
SocketNamespace.prototype.handlePacket = function (sessid, packet) {
var socket = this.socket(sessid)
, dataAck = packet.ack == 'data'
, manager = this.manager
, self = this;
function ack () {
@@ -296,8 +297,7 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
if (packet.endpoint == '') {
connect();
} else {
var manager = this.manager
, handshakeData = manager.handshaken[sessid];
var handshakeData = manager.handshaken[sessid];
this.authorize(handshakeData, function (err, authorized, newData) {
if (err) return error(err);
@@ -322,12 +322,18 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
break;
case 'event':
var params = [packet.name].concat(packet.args);
// check if the emitted event is not blacklisted
if (-~manager.get('blacklist').indexOf(packet.name)) {
this.log.debug('ignoring blacklisted event `' + packet.name + '`');
} else {
var params = [packet.name].concat(packet.args);
if (dataAck)
params.push(ack);
if (dataAck) {
params.push(ack);
}
socket.$emit.apply(socket, params);
socket.$emit.apply(socket, params);
}
break;
case 'disconnect':

View File

@@ -13,35 +13,38 @@
* Packet types.
*/
var packets = exports.packets = [
'disconnect'
, 'connect'
, 'heartbeat'
, 'message'
, 'json'
, 'event'
, 'ack'
, 'error'
, 'noop'
];
var packets = exports.packets = {
'disconnect': 0
, 'connect': 1
, 'heartbeat': 2
, 'message': 3
, 'json': 4
, 'event': 5
, 'ack': 6
, 'error': 7
, 'noop': 8
}
, packetslist = Object.keys(packets);
/**
* Errors reasons.
*/
var reasons = exports.reasons = [
'transport not supported'
, 'client not handshaken'
, 'unauthorized'
];
var reasons = exports.reasons = {
'transport not supported': 0
, 'client not handshaken': 1
, 'unauthorized': 2
}
, reasonslist = Object.keys(reasons);
/**
* Errors advice.
*/
var advice = exports.advice = [
'reconnect'
];
var advice = exports.advice = {
'reconnect': 0
}
, advicelist = Object.keys(advice);
/**
* Encodes a packet.
@@ -50,22 +53,13 @@ var advice = exports.advice = [
*/
exports.encodePacket = function (packet) {
var type = packets.indexOf(packet.type)
var type = packets[packet.type]
, id = packet.id || ''
, endpoint = packet.endpoint || ''
, ack = packet.ack
, data = null;
switch (packet.type) {
case 'error':
var reason = packet.reason ? reasons.indexOf(packet.reason) : ''
, adv = packet.advice ? advice.indexOf(packet.advice) : ''
if (reason !== '' || adv !== '')
data = reason + (adv !== '' ? ('+' + adv) : '')
break;
case 'message':
if (packet.data !== '')
data = packet.data;
@@ -85,30 +79,35 @@ exports.encodePacket = function (packet) {
data = JSON.stringify(packet.data);
break;
case 'connect':
if (packet.qs)
data = packet.qs;
break;
case 'ack':
data = packet.ackId
+ (packet.args && packet.args.length
? '+' + JSON.stringify(packet.args) : '');
break;
case 'connect':
if (packet.qs)
data = packet.qs;
break;
case 'error':
var reason = packet.reason ? reasons[packet.reason] : ''
, adv = packet.advice ? advice[packet.advice] : ''
if (reason !== '' || adv !== '')
data = reason + (adv !== '' ? ('+' + adv) : '')
break;
}
// construct packet with required fragments
var encoded = [
type
, id + (ack == 'data' ? '+' : '')
, endpoint
];
var encoded = type + ':' + id + (ack == 'data' ? '+' : '') + ':' + endpoint;
// data fragment is optional
if (data !== null && data !== undefined)
encoded.push(data);
encoded += ':' + data;
return encoded.join(':');
return encoded;
};
/**
@@ -138,7 +137,19 @@ exports.encodePayload = function (packets) {
* @api private
*/
var regexp = /^([^:]+):([0-9]+)?(\+)?:([^:]+)?:?(.*)?$/;
var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
/**
* Wrap the JSON.parse in a seperate function the crankshaft optimizer will
* only punish this function for the usage for try catch
*
* @api private
*/
function parse (data) {
try { return JSON.parse(data) }
catch (e) { return false }
}
exports.decodePacket = function (data) {
var pieces = data.match(regexp);
@@ -148,7 +159,7 @@ exports.decodePacket = function (data) {
var id = pieces[2] || ''
, data = pieces[5] || ''
, packet = {
type: packets[pieces[1]]
type: packetslist[pieces[1]]
, endpoint: pieces[4] || ''
};
@@ -163,30 +174,22 @@ exports.decodePacket = function (data) {
// handle different packet types
switch (packet.type) {
case 'error':
var pieces = data.split('+');
packet.reason = reasons[pieces[0]] || '';
packet.advice = advice[pieces[1]] || '';
break;
case 'message':
packet.data = data || '';
break;
case 'event':
try {
var opts = JSON.parse(data);
packet.name = opts.name;
packet.args = opts.args;
} catch (e) { }
pieces = parse(data);
if (pieces) {
packet.name = pieces.name;
packet.args = pieces.args;
}
packet.args = packet.args || [];
break;
case 'json':
try {
packet.data = JSON.parse(data);
} catch (e) { }
packet.data = parse(data);
break;
case 'connect':
@@ -194,23 +197,22 @@ exports.decodePacket = function (data) {
break;
case 'ack':
var pieces = data.match(/^([0-9]+)(\+)?(.*)/);
pieces = data.match(/^([0-9]+)(\+)?(.*)/);
if (pieces) {
packet.ackId = pieces[1];
packet.args = [];
if (pieces[3]) {
try {
packet.args = pieces[3] ? JSON.parse(pieces[3]) : [];
} catch (e) { }
packet.args = parse(pieces[3]) || [];
}
}
break;
case 'disconnect':
case 'heartbeat':
break;
};
case 'error':
pieces = data.split('+');
packet.reason = reasonslist[pieces[0]] || '';
packet.advice = advicelist[pieces[1]] || '';
}
return packet;
};
@@ -223,6 +225,10 @@ exports.decodePacket = function (data) {
*/
exports.decodePayload = function (data) {
if (undefined == data || null == data) {
return [];
}
if (data[0] == '\ufffd') {
var ret = [];

View File

@@ -15,7 +15,7 @@ var client = require('socket.io-client');
* Version.
*/
exports.version = '0.8.1';
exports.version = '0.8.6';
/**
* Supported protocol version.
@@ -95,6 +95,14 @@ exports.Transport = require('./transport');
exports.Socket = require('./socket');
/**
* Static constructor.
*
* @api public
*/
exports.Static = require('./static');
/**
* Store constructor.
*

View File

@@ -11,7 +11,7 @@
var parser = require('./parser')
, util = require('./util')
, EventEmitter = process.EventEmitter;
, EventEmitter = process.EventEmitter
/**
* Export the constructor.
@@ -19,6 +19,12 @@ var parser = require('./parser')
exports = module.exports = Socket;
/**
* Default error event listener to prevent uncaught exceptions.
*/
var defaultError = function () {};
/**
* Socket constructor.
*
@@ -39,6 +45,7 @@ function Socket (manager, id, nsp, readable) {
this.setFlags();
this.readable = readable;
this.store = this.manager.store.client(this.id);
this.on('error', defaultError);
};
/**
@@ -57,6 +64,16 @@ Socket.prototype.__defineGetter__('handshake', function () {
return this.manager.handshaken[this.id];
});
/**
* Accessor shortcut for the transport type
*
* @api private
*/
Socket.prototype.__defineGetter__('transport', function () {
return this.manager.transports[this.id].name;
});
/**
* Accessor shortcut for the logger.
*
@@ -160,7 +177,7 @@ Socket.prototype.join = function (name, fn) {
};
/**
* Joins a user to a room.
* Un-joins a user from a room.
*
* @api public
*/

395
lib/static.js Normal file
View File

@@ -0,0 +1,395 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var client = require('socket.io-client')
, cp = require('child_process')
, fs = require('fs')
, util = require('./util');
/**
* File type details.
*
* @api private
*/
var mime = {
js: {
type: 'application/javascript'
, encoding: 'utf8'
, gzip: true
}
, swf: {
type: 'application/x-shockwave-flash'
, encoding: 'binary'
, gzip: false
}
};
/**
* Regexp for matching custom transport patterns. Users can configure their own
* socket.io bundle based on the url structure. Different transport names are
* concatinated using the `+` char. /socket.io/socket.io+websocket.js should
* create a bundle that only contains support for the websocket.
*
* @api private
*/
var bundle = /\+((?:\+)?[\w\-]+)*(?:\.v\d+\.\d+\.\d+)?(?:\.js)$/
, versioning = /\.v\d+\.\d+\.\d+(?:\.js)$/;
/**
* Export the constructor
*/
exports = module.exports = Static;
/**
* Static constructor
*
* @api public
*/
function Static (manager) {
this.manager = manager;
this.cache = {};
this.paths = {};
this.init();
}
/**
* Initialize the Static by adding default file paths.
*
* @api public
*/
Static.prototype.init = function () {
/**
* Generates a unique id based the supplied transports array
*
* @param {Array} transports The array with transport types
* @api private
*/
function id (transports) {
var id = transports.join('').split('').map(function (char) {
return ('' + char.charCodeAt(0)).split('').pop();
}).reduce(function (char, id) {
return char +id;
});
return client.version + ':' + id;
}
/**
* Generates a socket.io-client file based on the supplied transports.
*
* @param {Array} transports The array with transport types
* @param {Function} callback Callback for the static.write
* @api private
*/
function build (transports, callback) {
client.builder(transports, {
minify: self.manager.enabled('browser client minification')
}, function (err, content) {
callback(err, content ? new Buffer(content) : null, id(transports));
}
);
}
var self = this;
// add our default static files
this.add('/static/flashsocket/WebSocketMain.swf', {
file: client.dist + '/WebSocketMain.swf'
});
this.add('/static/flashsocket/WebSocketMainInsecure.swf', {
file: client.dist + '/WebSocketMainInsecure.swf'
});
// generates dedicated build based on the available transports
this.add('/socket.io.js', function (path, callback) {
build(self.manager.get('transports'), callback);
});
this.add('/socket.io.v', { mime: mime.js }, function (path, callback) {
build(self.manager.get('transports'), callback);
});
// allow custom builds based on url paths
this.add('/socket.io+', { mime: mime.js }, function (path, callback) {
var available = self.manager.get('transports')
, matches = path.match(bundle)
, transports = [];
if (!matches) return callback('No valid transports');
// make sure they valid transports
matches[0].split('.')[0].split('+').slice(1).forEach(function (transport) {
if (!!~available.indexOf(transport)) {
transports.push(transport);
}
});
if (!transports.length) return callback('No valid transports');
build(transports, callback);
});
// clear cache when transports change
this.manager.on('set:transports', function (key, value) {
delete self.cache['/socket.io.js'];
Object.keys(self.cache).forEach(function (key) {
if (bundle.test(key)) {
delete self.cache[key];
}
});
});
};
/**
* Gzip compress buffers.
*
* @param {Buffer} data The buffer that needs gzip compression
* @param {Function} callback
* @api public
*/
Static.prototype.gzip = function (data, callback) {
var gzip = cp.spawn('gzip', ['-9', '-c', '-f', '-n'])
, encoding = Buffer.isBuffer(data) ? 'binary' : 'utf8'
, buffer = []
, err;
gzip.stdout.on('data', function (data) {
buffer.push(data);
});
gzip.stderr.on('data', function (data) {
err = data +'';
buffer.length = 0;
});
gzip.on('exit', function () {
if (err) return callback(err);
var size = 0
, index = 0
, i = buffer.length
, content;
while (i--) {
size += buffer[i].length;
}
content = new Buffer(size);
i = buffer.length;
buffer.forEach(function (buffer) {
var length = buffer.length;
buffer.copy(content, index, 0, length);
index += length;
});
buffer.length = 0;
callback(null, content);
});
gzip.stdin.end(data, encoding);
};
/**
* Is the path a static file?
*
* @param {String} path The path that needs to be checked
* @api public
*/
Static.prototype.has = function (path) {
// fast case
if (this.paths[path]) return this.paths[path];
var keys = Object.keys(this.paths)
, i = keys.length;
while (i--) {
if (-~path.indexOf(keys[i])) return this.paths[keys[i]];
}
return false;
};
/**
* Add new paths new paths that can be served using the static provider.
*
* @param {String} path The path to respond to
* @param {Options} options Options for writing out the response
* @param {Function} [callback] Optional callback if no options.file is
* supplied this would be called instead.
* @api public
*/
Static.prototype.add = function (path, options, callback) {
var extension = /(?:\.(\w{1,4}))$/.exec(path);
if (!callback && typeof options == 'function') {
callback = options;
options = {};
}
options.mime = options.mime || (extension ? mime[extension[1]] : false);
if (callback) options.callback = callback;
if (!(options.file || options.callback) || !options.mime) return false;
this.paths[path] = options;
return true;
};
/**
* Writes a static response.
*
* @param {String} path The path for the static content
* @param {HTTPRequest} req The request object
* @param {HTTPResponse} res The response object
* @api public
*/
Static.prototype.write = function (path, req, res) {
/**
* Write a response without throwing errors because can throw error if the
* response is no longer writable etc.
*
* @api private
*/
function write (status, headers, content, encoding) {
try {
res.writeHead(status, headers || undefined);
// only write content if it's not a HEAD request and we actually have
// some content to write (304's doesn't have content).
res.end(
req.method !== 'HEAD' && content ? content : ''
, encoding || undefined
);
} catch (e) {}
}
/**
* Answers requests depending on the request properties and the reply object.
*
* @param {Object} reply The details and content to reply the response with
* @api private
*/
function answer (reply) {
var cached = req.headers['if-none-match'] === reply.etag;
if (cached && self.manager.enabled('browser client etag')) {
return write(304);
}
var accept = req.headers['accept-encoding'] || ''
, gzip = !!~accept.toLowerCase().indexOf('gzip')
, mime = reply.mime
, versioned = reply.versioned
, headers = {
'Content-Type': mime.type
};
// check if we can add a etag
if (self.manager.enabled('browser client etag') && reply.etag && !versioned) {
headers['Etag'] = reply.etag;
}
// see if we need to set Expire headers because the path is versioned
if (versioned) {
var expires = self.manager.get('browser client expires');
headers['Cache-Control'] = 'private, x-gzip-ok="", max-age=' + expires;
headers['Date'] = new Date().toUTCString();
headers['Expires'] = new Date(Date.now() + (expires * 1000)).toUTCString();
}
if (gzip && reply.gzip) {
headers['Content-Length'] = reply.gzip.length;
headers['Content-Encoding'] = 'gzip';
headers['Vary'] = 'Accept-Encoding';
write(200, headers, reply.gzip.content, mime.encoding);
} else {
headers['Content-Length'] = reply.length;
write(200, headers, reply.content, mime.encoding);
}
self.manager.log.debug('served static content ' + path);
}
var self = this
, details;
// most common case first
if (this.manager.enabled('browser client cache') && this.cache[path]) {
return answer(this.cache[path]);
} else if (this.manager.get('browser client handler')) {
return this.manager.get('browser client handler').call(this, req, res);
} else if ((details = this.has(path))) {
/**
* A small helper function that will let us deal with fs and dynamic files
*
* @param {Object} err Optional error
* @param {Buffer} content The data
* @api private
*/
function ready (err, content, etag) {
if (err) {
self.manager.log.warn('Unable to serve file. ' + (err.message || err));
return write(500, null, 'Error serving static ' + path);
}
// store the result in the cache
var reply = self.cache[path] = {
content: content
, length: content.length
, mime: details.mime
, etag: etag || client.version
, versioned: versioning.test(path)
};
// check if gzip is enabled
if (details.mime.gzip && self.manager.enabled('browser client gzip')) {
self.gzip(content, function (err, content) {
if (!err) {
reply.gzip = {
content: content
, length: content.length
}
}
answer(reply);
});
} else {
answer(reply);
}
}
if (details.file) {
fs.readFile(details.file, ready);
} else if(details.callback) {
details.callback.call(this, path, ready);
} else {
write(404, null, 'File handle not found');
}
} else {
write(404, null, 'File not found');
}
};

View File

@@ -60,14 +60,33 @@ function Redis (opts) {
}
}
var redis = opts.redis || require('redis');
var redis = opts.redis || require('redis')
, RedisClient = redis.RedisClient;
// initialize a pubsub client and a regular client
this.pub = redis.createClient(opts.redisPub);
this.sub = redis.createClient(opts.redisSub);
this.cmd = redis.createClient(opts.redisClient);
if (opts.redisPub instanceof RedisClient) {
this.pub = opts.redisPub;
} else {
opts.redisPub || (opts.redisPub = {});
this.pub = redis.createClient(opts.redisPub.port, opts.redisPub.host, opts.redisPub);
}
if (opts.redisSub instanceof RedisClient) {
this.sub = opts.redisSub;
} else {
opts.redisSub || (opts.redisSub = {});
this.sub = redis.createClient(opts.redisSub.port, opts.redisSub.host, opts.redisSub);
}
if (opts.redisClient instanceof RedisClient) {
this.cmd = opts.redisClient;
} else {
opts.redisClient || (opts.redisClient = {});
this.cmd = redis.createClient(opts.redisClient.port, opts.redisClient.host, opts.redisClient);
}
Store.call(this, opts);
this.sub.setMaxListeners(0);
this.setMaxListeners(0);
};
/**
@@ -118,7 +137,7 @@ Redis.prototype.subscribe = function (name, consumer, fn) {
self.on('unsubscribe', function unsubscribe (ch) {
if (name == ch) {
self.sub.removeListener('message', message);
self.removeEvent('unsubscribe', unsubscribe);
self.removeListener('unsubscribe', unsubscribe);
}
});

View File

@@ -52,7 +52,7 @@ HTMLFile.prototype.handleRequest = function (req) {
if (req.method == 'GET') {
req.res.writeHead(200, {
'Content-Type': 'text/html'
'Content-Type': 'text/html; charset=UTF-8'
, 'Connection': 'keep-alive'
, 'Transfer-Encoding': 'chunked'
});

View File

@@ -56,7 +56,7 @@ HTTPTransport.prototype.handleRequest = function (req) {
req.on('end', function () {
res.writeHead(200, headers);
res.end('1');
self.onData(self.postEncoded ? qs.parse(buffer).d : buffer);
});
@@ -83,9 +83,9 @@ HTTPTransport.prototype.handleRequest = function (req) {
HTTPTransport.prototype.onData = function (data) {
var messages = parser.decodePayload(data);
this.log.debug(this.name + ' received data packet', data);
for (var i = 0, l = messages.length; i < l; i++) {
this.log.debug(this.name + ' received data packet', data);
this.onMessage(messages[i]);
}
};

View File

@@ -54,6 +54,24 @@ JSONPPolling.prototype.name = 'jsonppolling';
JSONPPolling.prototype.postEncoded = true;
/**
* Handles incoming data.
* Due to a bug in \n handling by browsers, we expect a JSONified string.
*
* @api private
*/
JSONPPolling.prototype.onData = function (data) {
try {
data = JSON.parse(data);
} catch (e) {
this.error('parse', 'reconnect');
return;
}
HTTPPolling.prototype.onData.call(this, data);
};
/**
* Performs the write.
*

View File

@@ -25,9 +25,12 @@ exports = module.exports = WebSocket;
*/
function WebSocket (mng, data, req) {
var version = req.headers['sec-websocket-version'];
var transport
, version = req.headers['sec-websocket-version'];
if (typeof version !== 'undefined' && typeof protocolVersions[version] !== 'undefined') {
return new protocolVersions[version](mng, data, req);
transport = new protocolVersions[version](mng, data, req);
}
return new protocolVersions['default'](mng, data, req);
else transport = new protocolVersions['default'](mng, data, req);
if (typeof this.name !== 'undefined') transport.name = this.name;
return transport;
};

View File

@@ -60,6 +60,14 @@ WebSocket.prototype.__proto__ = Transport.prototype;
WebSocket.prototype.name = 'websocket';
/**
* Websocket draft version
*
* @api public
*/
WebSocket.prototype.protocolVersion = 'hixie-76';
/**
* Called when the socket connects.
*
@@ -176,9 +184,9 @@ WebSocket.prototype.write = function (data) {
var length = Buffer.byteLength(data)
, buffer = new Buffer(2 + length);
buffer.write('\u0000', 'binary');
buffer.write('\x00', 'binary');
buffer.write(data, 1, 'utf8');
buffer.write('\uffff', 1 + length, 'binary');
buffer.write('\xff', 1 + length, 'binary');
try {
if (this.socket.write(buffer)) {

View File

@@ -12,6 +12,7 @@
var Transport = require('../../transport')
, EventEmitter = process.EventEmitter
, crypto = require('crypto')
, url = require('url')
, parser = require('../../parser')
, util = require('../../util');
@@ -33,13 +34,20 @@ function WebSocket (mng, data, req) {
// parser
var self = this;
this.manager = mng;
this.parser = new Parser();
this.parser.on('data', function (packet) {
self.onMessage(parser.decodePacket(packet));
});
this.parser.on('ping', function () {
// version 8 ping => pong
self.socket.write('\u008a\u0000');
try {
self.socket.write('\u008a\u0000');
}
catch (e) {
self.end();
return;
}
});
this.parser.on('close', function () {
self.end();
@@ -66,6 +74,14 @@ WebSocket.prototype.__proto__ = Transport.prototype;
WebSocket.prototype.name = 'websocket';
/**
* Websocket draft version
*
* @api public
*/
WebSocket.prototype.protocolVersion = '07-12';
/**
* Called when the socket connects.
*
@@ -75,16 +91,22 @@ WebSocket.prototype.name = 'websocket';
WebSocket.prototype.onSocketConnect = function () {
var self = this;
if (this.req.headers.upgrade !== 'websocket') {
if (this.req.headers.upgrade.toLowerCase() !== 'websocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
}
var origin = this.req.headers.origin
var origin = this.req.headers['sec-websocket-origin']
, location = (this.socket.encrypted ? 'wss' : 'ws')
+ '://' + this.req.headers.host + this.req.url;
if (!this.verifyOrigin(origin)) {
this.log.warn(this.name + ' connection invalid: origin mismatch');
this.end();
return;
}
if (!this.req.headers['sec-websocket-key']) {
this.log.warn(this.name + ' connection invalid: received no key');
this.end();
@@ -118,6 +140,40 @@ WebSocket.prototype.onSocketConnect = function () {
});
};
/**
* Verifies the origin of a request.
*
* @api private
*/
WebSocket.prototype.verifyOrigin = function (origin) {
var origins = this.manager.get('origins');
if (origin === 'null') origin = '*';
if (origins.indexOf('*:*') !== -1) {
return true;
}
if (origin) {
try {
var parts = url.parse(origin);
var ok =
~origins.indexOf(parts.hostname + ':' + parts.port) ||
~origins.indexOf(parts.hostname + ':*') ||
~origins.indexOf('*:' + parts.port);
if (!ok) this.log.warn('illegal origin: ' + origin);
return ok;
} catch (ex) {
this.log.warn('error parsing origin');
}
}
else {
this.log.warn('origin missing from websocket call, yet required by config');
}
return false;
};
/**
* Writes to the socket.
*
@@ -127,11 +183,31 @@ WebSocket.prototype.onSocketConnect = function () {
WebSocket.prototype.write = function (data) {
if (this.open) {
var buf = this.frame(0x81, data);
this.socket.write(buf, 'binary');
try {
this.socket.write(buf, 'binary');
}
catch (e) {
this.end();
return;
}
this.log.debug(this.name + ' writing', data);
}
};
/**
* Writes a payload.
*
* @api private
*/
WebSocket.prototype.payload = function (msgs) {
for (var i = 0, l = msgs.length; i < l; i++) {
this.write(msgs[i]);
}
return this;
};
/**
* Frame server-to-client output as a text packet.
*
@@ -139,9 +215,9 @@ WebSocket.prototype.write = function (data) {
*/
WebSocket.prototype.frame = function (opcode, str) {
var dataBuffer = new Buffer(str);
var dataLength = dataBuffer.length;
var startOffset = 2
var dataBuffer = new Buffer(str)
, dataLength = dataBuffer.length
, startOffset = 2
, secondByte = dataLength;
if (dataLength > 65536) {
startOffset = 10;
@@ -233,7 +309,56 @@ function Parser () {
if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
}
else if (firstLength == 127) {
self.expect('Length', 8, function(data) {
if (util.unpack(data.slice(0, 4)) != 0) {
self.error('packets with length spanning more than 32 bit is currently not supported');
return;
}
var lengthBytes = data.slice(4); // note: cap to 32 bit length
expectData(util.unpack(data));
});
}
},
// binary
'2': function(data) {
var finish = function(mask, data) {
if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list
self.currentMessage.push(self.unmask(mask, data, true));
if (self.state.lastFragment) {
self.emit('binary', self.concatBuffers(self.currentMessage));
self.currentMessage = '';
}
self.endPacket();
}
var expectData = function(length) {
if (self.state.masked) {
self.expect('Mask', 4, function(data) {
var mask = data;
self.expect('Data', length, function(data) {
finish(mask, data);
});
});
}
else {
self.expect('Data', length, function(data) {
finish(null, data);
});
}
}
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
@@ -290,7 +415,7 @@ function Parser () {
else if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
@@ -379,19 +504,24 @@ Parser.prototype.expect = function(what, length, handler) {
*/
Parser.prototype.processPacket = function (data) {
if ((data[0] & 0x70) != 0) this.error('reserved fields not empty');
if ((data[0] & 0x70) != 0) this.error('reserved fields must be empty');
this.state.lastFragment = (data[0] & 0x80) == 0x80;
this.state.masked = (data[1] & 0x80) == 0x80;
var opcode = data[0] & 0xf;
if (opcode == 0) {
if (opcode == 0) {
// continuation frame
if (this.state.opcode != 1 || this.state.opcode != 2) {
this.state.opcode = this.state.activeFragmentedOperation;
if (!(this.state.opcode == 1 || this.state.opcode == 2)) {
this.error('continuation frame cannot follow current opcode')
return;
}
}
else this.state.opcode = opcode;
this.state.opcode = data[0] & 0xf;
else {
this.state.opcode = opcode;
if (this.state.lastFragment === false) {
this.state.activeFragmentedOperation = opcode;
}
}
var handler = this.opcodeHandlers[this.state.opcode];
if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
else handler(data);
@@ -412,7 +542,7 @@ Parser.prototype.endPacket = function() {
this.state.activeFragmentedOperation = null;
}
this.state.lastFragment = false;
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
this.state.masked = false;
this.expect('Opcode', 2, this.processPacket);
}
@@ -443,15 +573,36 @@ Parser.prototype.reset = function() {
* @api private
*/
Parser.prototype.unmask = function (mask, buf) {
Parser.prototype.unmask = function (mask, buf, binary) {
if (mask != null) {
for (var i = 0, ll = buf.length; i < ll; i++) {
buf[i] ^= mask[i % 4];
}
}
if (binary) return buf;
return buf != null ? buf.toString('utf8') : '';
}
/**
* Concatenates a list of buffers.
*
* @api private
*/
Parser.prototype.concatBuffers = function(buffers) {
var length = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
length += buffers[i].length;
}
var mergedBuffer = new Buffer(length);
var offset = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
buffers[i].copy(mergedBuffer, offset);
offset += buffers[i].length;
}
return mergedBuffer;
}
/**
* Handles an error
*

View File

@@ -0,0 +1,616 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module requirements.
*/
var Transport = require('../../transport')
, EventEmitter = process.EventEmitter
, crypto = require('crypto')
, url = require('url')
, parser = require('../../parser')
, util = require('../../util');
/**
* Export the constructor.
*/
exports = module.exports = WebSocket;
exports.Parser = Parser;
/**
* HTTP interface constructor. Interface compatible with all transports that
* depend on request-response cycles.
*
* @api public
*/
function WebSocket (mng, data, req) {
// parser
var self = this;
this.manager = mng;
this.parser = new Parser();
this.parser.on('data', function (packet) {
self.onMessage(parser.decodePacket(packet));
});
this.parser.on('ping', function () {
// version 8 ping => pong
try {
self.socket.write('\u008a\u0000');
}
catch (e) {
self.end();
return;
}
});
this.parser.on('close', function () {
self.end();
});
this.parser.on('error', function (reason) {
self.log.warn(self.name + ' parser error: ' + reason);
self.end();
});
Transport.call(this, mng, data, req);
};
/**
* Inherits from Transport.
*/
WebSocket.prototype.__proto__ = Transport.prototype;
/**
* Transport name
*
* @api public
*/
WebSocket.prototype.name = 'websocket';
/**
* Websocket draft version
*
* @api public
*/
WebSocket.prototype.protocolVersion = '16';
/**
* Called when the socket connects.
*
* @api private
*/
WebSocket.prototype.onSocketConnect = function () {
var self = this;
if (this.req.headers.upgrade.toLowerCase() !== 'websocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
}
var origin = this.req.headers['origin']
, location = (this.socket.encrypted ? 'wss' : 'ws')
+ '://' + this.req.headers.host + this.req.url;
if (!this.verifyOrigin(origin)) {
this.log.warn(this.name + ' connection invalid: origin mismatch');
this.end();
return;
}
if (!this.req.headers['sec-websocket-key']) {
this.log.warn(this.name + ' connection invalid: received no key');
this.end();
return;
}
// calc key
var key = this.req.headers['sec-websocket-key'];
var shasum = crypto.createHash('sha1');
shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
key = shasum.digest('base64');
var headers = [
'HTTP/1.1 101 Switching Protocols'
, 'Upgrade: websocket'
, 'Connection: Upgrade'
, 'Sec-WebSocket-Accept: ' + key
];
try {
this.socket.write(headers.concat('', '').join('\r\n'));
this.socket.setTimeout(0);
this.socket.setNoDelay(true);
} catch (e) {
this.end();
return;
}
this.socket.on('data', function (data) {
self.parser.add(data);
});
};
/**
* Verifies the origin of a request.
*
* @api private
*/
WebSocket.prototype.verifyOrigin = function (origin) {
var origins = this.manager.get('origins');
if (origin === 'null') origin = '*';
if (origins.indexOf('*:*') !== -1) {
return true;
}
if (origin) {
try {
var parts = url.parse(origin);
var ok =
~origins.indexOf(parts.hostname + ':' + parts.port) ||
~origins.indexOf(parts.hostname + ':*') ||
~origins.indexOf('*:' + parts.port);
if (!ok) this.log.warn('illegal origin: ' + origin);
return ok;
} catch (ex) {
this.log.warn('error parsing origin');
}
}
else {
this.log.warn('origin missing from websocket call, yet required by config');
}
return false;
};
/**
* Writes to the socket.
*
* @api private
*/
WebSocket.prototype.write = function (data) {
if (this.open) {
var buf = this.frame(0x81, data);
try {
this.socket.write(buf, 'binary');
}
catch (e) {
this.end();
return;
}
this.log.debug(this.name + ' writing', data);
}
};
/**
* Writes a payload.
*
* @api private
*/
WebSocket.prototype.payload = function (msgs) {
for (var i = 0, l = msgs.length; i < l; i++) {
this.write(msgs[i]);
}
return this;
};
/**
* Frame server-to-client output as a text packet.
*
* @api private
*/
WebSocket.prototype.frame = function (opcode, str) {
var dataBuffer = new Buffer(str)
, dataLength = dataBuffer.length
, startOffset = 2
, secondByte = dataLength;
if (dataLength > 65536) {
startOffset = 10;
secondByte = 127;
}
else if (dataLength > 125) {
startOffset = 4;
secondByte = 126;
}
var outputBuffer = new Buffer(dataLength + startOffset);
outputBuffer[0] = opcode;
outputBuffer[1] = secondByte;
dataBuffer.copy(outputBuffer, startOffset);
switch (secondByte) {
case 126:
outputBuffer[2] = dataLength >>> 8;
outputBuffer[3] = dataLength % 256;
break;
case 127:
var l = dataLength;
for (var i = 1; i <= 8; ++i) {
outputBuffer[startOffset - i] = l & 0xff;
l >>>= 8;
}
}
return outputBuffer;
};
/**
* Closes the connection.
*
* @api private
*/
WebSocket.prototype.doClose = function () {
this.socket.end();
};
/**
* WebSocket parser
*
* @api public
*/
function Parser () {
this.state = {
activeFragmentedOperation: null,
lastFragment: false,
masked: false,
opcode: 0
};
this.overflow = null;
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
this.currentMessage = '';
var self = this;
this.opcodeHandlers = {
// text
'1': function(data) {
var finish = function(mask, data) {
self.currentMessage += self.unmask(mask, data);
if (self.state.lastFragment) {
self.emit('data', self.currentMessage);
self.currentMessage = '';
}
self.endPacket();
}
var expectData = function(length) {
if (self.state.masked) {
self.expect('Mask', 4, function(data) {
var mask = data;
self.expect('Data', length, function(data) {
finish(mask, data);
});
});
}
else {
self.expect('Data', length, function(data) {
finish(null, data);
});
}
}
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
}
else if (firstLength == 127) {
self.expect('Length', 8, function(data) {
if (util.unpack(data.slice(0, 4)) != 0) {
self.error('packets with length spanning more than 32 bit is currently not supported');
return;
}
var lengthBytes = data.slice(4); // note: cap to 32 bit length
expectData(util.unpack(data));
});
}
},
// binary
'2': function(data) {
var finish = function(mask, data) {
if (typeof self.currentMessage == 'string') self.currentMessage = []; // build a buffer list
self.currentMessage.push(self.unmask(mask, data, true));
if (self.state.lastFragment) {
self.emit('binary', self.concatBuffers(self.currentMessage));
self.currentMessage = '';
}
self.endPacket();
}
var expectData = function(length) {
if (self.state.masked) {
self.expect('Mask', 4, function(data) {
var mask = data;
self.expect('Data', length, function(data) {
finish(mask, data);
});
});
}
else {
self.expect('Data', length, function(data) {
finish(null, data);
});
}
}
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
}
else if (firstLength == 127) {
self.expect('Length', 8, function(data) {
if (util.unpack(data.slice(0, 4)) != 0) {
self.error('packets with length spanning more than 32 bit is currently not supported');
return;
}
var lengthBytes = data.slice(4); // note: cap to 32 bit length
expectData(util.unpack(data));
});
}
},
// close
'8': function(data) {
self.emit('close');
self.reset();
},
// ping
'9': function(data) {
if (self.state.lastFragment == false) {
self.error('fragmented ping is not supported');
return;
}
var finish = function(mask, data) {
self.emit('ping', self.unmask(mask, data));
self.endPacket();
}
var expectData = function(length) {
if (self.state.masked) {
self.expect('Mask', 4, function(data) {
var mask = data;
self.expect('Data', length, function(data) {
finish(mask, data);
});
});
}
else {
self.expect('Data', length, function(data) {
finish(null, data);
});
}
}
// decode length
var firstLength = data[1] & 0x7f;
if (firstLength == 0) {
finish(null, null);
}
else if (firstLength < 126) {
expectData(firstLength);
}
else if (firstLength == 126) {
self.expect('Length', 2, function(data) {
expectData(util.unpack(data));
});
}
else if (firstLength == 127) {
self.expect('Length', 8, function(data) {
expectData(util.unpack(data));
});
}
}
}
this.expect('Opcode', 2, this.processPacket);
};
/**
* Inherits from EventEmitter.
*/
Parser.prototype.__proto__ = EventEmitter.prototype;
/**
* Add new data to the parser.
*
* @api public
*/
Parser.prototype.add = function(data) {
if (this.expectBuffer == null) {
this.addToOverflow(data);
return;
}
var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset);
data.copy(this.expectBuffer, this.expectOffset, 0, toRead);
this.expectOffset += toRead;
if (toRead < data.length) {
// at this point the overflow buffer shouldn't at all exist
this.overflow = new Buffer(data.length - toRead);
data.copy(this.overflow, 0, toRead, toRead + this.overflow.length);
}
if (this.expectOffset == this.expectBuffer.length) {
var bufferForHandler = this.expectBuffer;
this.expectBuffer = null;
this.expectOffset = 0;
this.expectHandler.call(this, bufferForHandler);
}
}
/**
* Adds a piece of data to the overflow.
*
* @api private
*/
Parser.prototype.addToOverflow = function(data) {
if (this.overflow == null) this.overflow = data;
else {
var prevOverflow = this.overflow;
this.overflow = new Buffer(this.overflow.length + data.length);
prevOverflow.copy(this.overflow, 0);
data.copy(this.overflow, prevOverflow.length);
}
}
/**
* Waits for a certain amount of bytes to be available, then fires a callback.
*
* @api private
*/
Parser.prototype.expect = function(what, length, handler) {
this.expectBuffer = new Buffer(length);
this.expectOffset = 0;
this.expectHandler = handler;
if (this.overflow != null) {
var toOverflow = this.overflow;
this.overflow = null;
this.add(toOverflow);
}
}
/**
* Start processing a new packet.
*
* @api private
*/
Parser.prototype.processPacket = function (data) {
if ((data[0] & 0x70) != 0) this.error('reserved fields must be empty');
this.state.lastFragment = (data[0] & 0x80) == 0x80;
this.state.masked = (data[1] & 0x80) == 0x80;
var opcode = data[0] & 0xf;
if (opcode == 0) {
// continuation frame
this.state.opcode = this.state.activeFragmentedOperation;
if (!(this.state.opcode == 1 || this.state.opcode == 2)) {
this.error('continuation frame cannot follow current opcode')
return;
}
}
else {
this.state.opcode = opcode;
if (this.state.lastFragment === false) {
this.state.activeFragmentedOperation = opcode;
}
}
var handler = this.opcodeHandlers[this.state.opcode];
if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode);
else handler(data);
}
/**
* Endprocessing a packet.
*
* @api private
*/
Parser.prototype.endPacket = function() {
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) {
// end current fragmented operation
this.state.activeFragmentedOperation = null;
}
this.state.lastFragment = false;
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
this.state.masked = false;
this.expect('Opcode', 2, this.processPacket);
}
/**
* Reset the parser state.
*
* @api private
*/
Parser.prototype.reset = function() {
this.state = {
activeFragmentedOperation: null,
lastFragment: false,
masked: false,
opcode: 0
};
this.expectOffset = 0;
this.expectBuffer = null;
this.expectHandler = null;
this.overflow = null;
this.currentMessage = '';
}
/**
* Unmask received data.
*
* @api private
*/
Parser.prototype.unmask = function (mask, buf, binary) {
if (mask != null) {
for (var i = 0, ll = buf.length; i < ll; i++) {
buf[i] ^= mask[i % 4];
}
}
if (binary) return buf;
return buf != null ? buf.toString('utf8') : '';
}
/**
* Concatenates a list of buffers.
*
* @api private
*/
Parser.prototype.concatBuffers = function(buffers) {
var length = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
length += buffers[i].length;
}
var mergedBuffer = new Buffer(length);
var offset = 0;
for (var i = 0, l = buffers.length; i < l; ++i) {
buffers[i].copy(mergedBuffer, offset);
offset += buffers[i].length;
}
return mergedBuffer;
}
/**
* Handles an error
*
* @api private
*/
Parser.prototype.error = function (reason) {
this.reset();
this.emit('error', reason);
return this;
};

View File

@@ -6,5 +6,6 @@
module.exports = {
7: require('./hybi-07-12'),
8: require('./hybi-07-12'),
13: require('./hybi-16'),
default: require('./default')
};

View File

@@ -1,6 +1,6 @@
{
"name": "socket.io"
, "version": "0.8.1"
, "version": "0.8.6"
, "description": "Real-time apps made cross-browser & easy with a WebSocket-like API"
, "homepage": "http://socket.io"
, "keywords": ["websocket", "socket", "realtime", "socket.io", "comet", "ajax"]
@@ -13,17 +13,20 @@
]
, "repository":{
"type": "git"
, "url": "https://github.com/LearnBoost/Socket.IO-node.git"
, "url": "https://github.com/LearnBoost/socket.io.git"
}
, "dependencies": {
"socket.io-client": "0.8.1"
"socket.io-client": "0.8.6"
, "policyfile": "0.0.4"
, "redis": "0.6.6"
, "redis": "0.6.7"
}
, "devDependencies": {
"expresso": "0.7.7"
, "should": "0.0.4"
, "assertvanish": "0.0.3-1"
, "benchmark": "0.2.2"
, "microtime": "0.1.3-1"
, "colors": "0.5.1"
}
, "main": "index"
, "engines": { "node": ">= 0.4.0" }

View File

@@ -494,6 +494,13 @@ var WebSocket = function(url, proto, opts) {
return function(req, s, head) {
stream = s;
if (readyState == CLOSED) {
stream.end();
stream.destroy();
stream = undefined;
return;
}
stream.on('data', function(d) {
if (d.length <= 0) {
return;

View File

@@ -147,6 +147,24 @@ HTTPClient.prototype.post = function (path, data, opts, fn) {
return this.request(path, opts, fn);
};
/**
* Issue a HEAD request
*
* @api private
*/
HTTPClient.prototype.head = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.method = 'HEAD';
return this.request(path, opts, fn);
};
/**
* Performs a handshake (GET) request
*
@@ -179,7 +197,6 @@ client = function (port) {
*/
create = function (cl) {
console.log('');
var manager = io.listen(cl.port);
manager.set('client store expiration', 0);
return manager;
@@ -191,14 +208,14 @@ create = function (cl) {
* @api private
*/
function WSClient (port, sid) {
function WSClient (port, sid, transport) {
this.sid = sid;
this.port = port;
this.transportName = transport || 'websocket';
WebSocket.call(
this
, 'ws://localhost:' + port + '/socket.io/'
+ io.protocol + '/websocket/' + sid
+ io.protocol + '/' + this.transportName + '/' + sid
);
};
@@ -239,6 +256,6 @@ WSClient.prototype.packet = function (pack) {
* @api public
*/
websocket = function (cl, sid) {
return new WSClient(cl.port, sid);
websocket = function (cl, sid, transport) {
return new WSClient(cl.port, sid, transport);
};

99
test/hybi-common.js Normal file
View File

@@ -0,0 +1,99 @@
/**
* Returns a Buffer from a "ff 00 ff"-type hex string.
*/
getBufferFromHexString = function(byteStr) {
var bytes = byteStr.split(' ');
var buf = new Buffer(bytes.length);
for (var i = 0; i < bytes.length; ++i) {
buf[i] = parseInt(bytes[i], 16);
}
return buf;
}
/**
* Returns a hex string from a Buffer.
*/
getHexStringFromBuffer = function(data) {
var s = '';
for (var i = 0; i < data.length; ++i) {
s += padl(data[i].toString(16), 2, '0') + ' ';
}
return s.trim();
}
/**
* Splits a buffer in two parts.
*/
splitBuffer = function(buffer) {
var b1 = new Buffer(Math.ceil(buffer.length / 2));
buffer.copy(b1, 0, 0, b1.length);
var b2 = new Buffer(Math.floor(buffer.length / 2));
buffer.copy(b2, 0, b1.length, b1.length + b2.length);
return [b1, b2];
}
/**
* Performs hybi07+ type masking on a hex string or buffer.
*/
mask = function(buf, maskString) {
if (typeof buf == 'string') buf = new Buffer(buf);
var mask = getBufferFromHexString(maskString || '34 83 a8 68');
for (var i = 0; i < buf.length; ++i) {
buf[i] ^= mask[i % 4];
}
return buf;
}
/**
* Returns a hex string representing the length of a message
*/
getHybiLengthAsHexString = function(len, masked) {
if (len < 126) {
var buf = new Buffer(1);
buf[0] = (masked ? 0x80 : 0) | len;
}
else if (len < 65536) {
var buf = new Buffer(3);
buf[0] = (masked ? 0x80 : 0) | 126;
getBufferFromHexString(pack(4, len)).copy(buf, 1);
}
else {
var buf = new Buffer(9);
buf[0] = (masked ? 0x80 : 0) | 127;
getBufferFromHexString(pack(16, len)).copy(buf, 1);
}
return getHexStringFromBuffer(buf);
}
/**
* Unpacks a Buffer into a number.
*/
unpack = function(buffer) {
var n = 0;
for (var i = 0; i < buffer.length; ++i) {
n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
}
return n;
}
/**
* Returns a hex string, representing a specific byte count 'length', from a number.
*/
pack = function(length, number) {
return padl(number.toString(16), length, '0').replace(/(\d\d)/g, '$1 ').trim();
}
/**
* Left pads the string 's' to a total length of 'n' with char 'c'.
*/
padl = function(s, n, c) {
return new Array(1 + n - s.length).join(c) + s;
}

View File

@@ -147,236 +147,6 @@ module.exports = {
});
},
'test that the client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that the client etag is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.enable('browser client etag');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that the cached client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
var static = sio.Manager.static;
static.cache['/socket.io.js'].content.should.match(/XMLHttpRequest/);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
});
},
'test that the cached client etag is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.enable('browser client etag');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
var static = sio.Manager.static
, cache = static.cache['/socket.io.js'];
cache.content.toString().should.match(/XMLHttpRequest/);
Buffer.isBuffer(cache.content).should.be.true;
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
});
},
'test that the cached client sends a 304 header': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.enable('browser client etag');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
cl.get('/socket.io/socket.io.js', {headers:{'if-none-match':res.headers.etag}}, function (res, data) {
res.statusCode.should.eql(304);
cl.end();
io.server.close();
done();
});
});
},
'test that client minification works': function (done) {
// server 1
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
// server 2
var port = ++ports
, io2 = sio.listen(port)
, cl2 = client(port);
io.configure(function () {
io.enable('browser client minification');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
var length = data.length;
cl.end();
io.server.close();
cl2.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
data.length.should.be.greaterThan(length);
cl2.end();
io2.server.close();
done();
});
});
},
'test that the WebSocketMain.swf is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/static/flashsocket/WebSocketMain.swf', function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
var static = sio.Manager.static
, cache = static.cache['/static/flashsocket/WebSocketMain.swf'];
Buffer.isBuffer(cache.content).should.be.true;
cl.end();
io.server.close();
done();
});
},
'test that the WebSocketMainInsecure.swf is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/static/flashsocket/WebSocketMainInsecure.swf', function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
var static = sio.Manager.static
, cache = static.cache['/static/flashsocket/WebSocketMain.swf'];
Buffer.isBuffer(cache.content).should.be.true;
cl.end();
io.server.close();
done();
});
},
'test that you can serve custom clients': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.configure(function () {
io.set('browser client handler', function (req, res) {
res.writeHead(200, {
'Content-Type': 'application/javascript'
, 'Content-Length': 13
, 'ETag': '1.0'
});
res.end('custom_client');
});
});
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.eql(13);
res.headers.etag.should.eql('1.0');
data.should.eql('custom_client');
cl.end();
io.server.close();
done();
});
},
'test that you can disable clients': function (done) {
var port = ++ports
, io = sio.listen(port)
@@ -557,6 +327,26 @@ module.exports = {
});
},
'test handshake cross domain access control': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port)
, headers = {
Origin: 'http://example.org:1337'
, Cookie: 'name=value'
};
cl.get('/socket.io/{protocol}/', { headers:headers }, function (res, data) {
res.statusCode.should.eql(200);
res.headers['access-control-allow-origin'].should.eql('*');
res.headers['access-control-allow-credentials'].should.eql('true');
cl.end();
io.server.close();
done();
});
},
'test limiting the supported transports for a manager': function (done) {
var port = ++ports
, io = sio.listen(port)
@@ -725,6 +515,49 @@ module.exports = {
io.get('resource').should.equal('/my resource');
io.get('custom').should.equal('opt');
io.server.close();
done();
},
'test disabling the log': function (done) {
var port = ++ports
, io = sio.listen(port, { log: false })
, _console = console.log
, calls = 0;
// the logger uses console.log to output data, override it to see if get's
// used
console.log = function () { ++calls };
io.log.debug('test');
io.log.log('testing');
console.log = _console;
calls.should.equal(0);
io.server.close();
done();
},
'test disabling logging with colors': function (done) {
var port = ++ports
, io = sio.listen(port, { 'log colors': false })
, _console = console.log
, calls = 0;
// the logger uses console.log to output data, override it to see if get's
// used
console.log = function (data) {
++calls;
data.indexOf('\033').should.equal(-1);
};
io.log.debug('test');
io.log.log('testing');
console.log = _console;
calls.should.equal(2);
io.server.close();
done();
}

View File

@@ -243,5 +243,44 @@ module.exports = {
}
});
})
},
'ignoring blacklisted events': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, ws;
io.set('heartbeat interval', 1);
io.set('blacklist', ['foobar']);
io.sockets.on('connection', function (socket) {
socket.on('foobar', function () {
calls++;
});
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function (){
ws.packet({
type: 'event'
, name: 'foobar'
, endpoint: ''
});
});
ws.on('message', function (data) {
if (data.type === 'heartbeat') {
cl.end();
ws.finishClose();
io.server.close();
calls.should.equal(0);
done();
}
});
});
}
};

View File

@@ -343,6 +343,14 @@ module.exports = {
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
]).should.eql('\ufffd5\ufffd3:::5\ufffd7\ufffd3:::53d')
},
'test decoding newline': function () {
parser.decodePacket('3:::\n').should.eql({
type: 'message'
, endpoint: ''
, data: '\n'
});
}
};

545
test/static.test.js Normal file
View File

@@ -0,0 +1,545 @@
/*!
* socket.io-node
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Test dependencies.
*/
var sio = require('socket.io')
, should = require('./common')
, ports = 15400;
/**
* Test.
*/
module.exports = {
'test that the default static files are available': function (done) {
var port = ++ports
, io = sio.listen(port);
(!!io.static.has('/socket.io.js')).should.be.true;
(!!io.static.has('/socket.io.v1.0.0.js')).should.be.true;
(!!io.static.has('/socket.io+xhr-polling.js')).should.be.true;
(!!io.static.has('/socket.io+xhr-polling.v1.0.0.js')).should.be.true;
(!!io.static.has('/static/flashsocket/WebSocketMain.swf')).should.be.true;
(!!io.static.has('/static/flashsocket/WebSocketMainInsecure.swf')).should.be.true;
io.server.close();
done();
},
'test that static files are correctly looked up': function (done) {
var port = ++ports
, io = sio.listen(port);
(!!io.static.has('/socket.io.js')).should.be.true;
(!!io.static.has('/invalidfilehereplease.js')).should.be.false;
io.server.close();
done();
},
'test that the client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that the custom build client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io+websocket.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
cl.end();
io.server.close();
done();
});
},
'test that the client is build with the enabled transports': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('transports', ['websocket']);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
cl.end();
io.server.close();
done();
});
},
'test that the client cache is cleared when transports change': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('transports', ['websocket']);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
io.set('transports', ['xhr-polling']);
should.strictEqual(io.static.cache['/socket.io.js'], undefined);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.match(/XMLHttpRequest/);
data.should.match(/XHRPolling\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/WS\.prototype\.name/);
cl.end();
io.server.close();
done();
});
});
},
'test that the client etag is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that the client etag is changed for new transports': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('transports', ['websocket']);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.js', function (res, data) {
var wsEtag = res.headers.etag;
io.set('transports', ['xhr-polling']);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers.etag.should.not.equal(wsEtag);
cl.end();
io.server.close();
done();
});
});
},
'test that the client is served with gzip': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client gzip');
cl.get('/socket.io/socket.io.js', {
headers: {
'accept-encoding': 'deflate, gzip'
}
}
, function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-encoding'].should.eql('gzip');
res.headers['content-length'].should.match(/([0-9]+)/);
cl.end();
io.server.close();
done();
}
);
},
'test that the cached client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
var static = io.static;
static.cache['/socket.io.js'].content.should.match(/XMLHttpRequest/);
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
});
},
'test that the client is not cached': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.static.add('/random.js', function (path, callback) {
var random = Math.floor(Date.now() * Math.random()).toString();
callback(null, new Buffer(random));
});
io.disable('browser client cache');
cl.get('/socket.io/random.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
cl.get('/socket.io/random.js', function (res, random) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.not.equal(random);
cl.end();
io.server.close();
done();
});
});
},
'test that the cached client etag is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
var static = io.static
, cache = static.cache['/socket.io.js'];
cache.content.toString().should.match(/XMLHttpRequest/);
Buffer.isBuffer(cache.content).should.be.true;
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers.etag.should.match(/([0-9]+)\.([0-9]+)\.([0-9]+)/);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
});
},
'test that the cached client sends a 304 header': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.js', function (res, data) {
cl.get('/socket.io/socket.io.js', {
headers: {
'if-none-match': res.headers.etag
}
}, function (res, data) {
res.statusCode.should.eql(304);
cl.end();
io.server.close();
done();
}
);
});
},
'test that client minification works': function (done) {
// server 1
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
// server 2
var port = ++ports
, io2 = sio.listen(port)
, cl2 = client(port);
io.enable('browser client minification');
cl.get('/socket.io/socket.io.js', function (res, data) {
var length = data.length;
cl.end();
io.server.close();
cl2.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
data.should.match(/XMLHttpRequest/);
data.length.should.be.greaterThan(length);
cl2.end();
io2.server.close();
done();
});
});
},
'test that the WebSocketMain.swf is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/static/flashsocket/WebSocketMain.swf', function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
var static = io.static
, cache = static.cache['/static/flashsocket/WebSocketMain.swf'];
Buffer.isBuffer(cache.content).should.be.true;
cl.end();
io.server.close();
done();
});
},
'test that the WebSocketMainInsecure.swf is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/static/flashsocket/WebSocketMainInsecure.swf', function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers.etag, undefined);
var static = io.static
, cache = static.cache['/static/flashsocket/WebSocketMainInsecure.swf'];
Buffer.isBuffer(cache.content).should.be.true;
cl.end();
io.server.close();
done();
});
},
'test that swf files are not served with gzip': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client gzip');
cl.get('/socket.io/static/flashsocket/WebSocketMain.swf', {
headers: {
'accept-encoding': 'deflate, gzip'
}
}
, function (res, data) {
res.headers['content-type'].should.eql('application/x-shockwave-flash');
res.headers['content-length'].should.match(/([0-9]+)/);
should.strictEqual(res.headers['content-encoding'], undefined);
cl.end();
io.server.close();
done();
}
);
},
'test that you can serve custom clients': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('browser client handler', function (req, res) {
res.writeHead(200, {
'Content-Type': 'application/javascript'
, 'Content-Length': 13
, 'ETag': '1.0'
});
res.end('custom_client');
});
cl.get('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.eql(13);
res.headers.etag.should.eql('1.0');
data.should.eql('custom_client');
cl.end();
io.server.close();
done();
});
},
'test that HEAD requests work': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.head('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.eql('');
cl.end();
io.server.close()
done();
});
},
'test that a versioned client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.v0.8.9.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that a custom versioned build client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('browser client expires', 1337);
cl.get('/socket.io/socket.io+websocket.v0.8.10.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
cl.end();
io.server.close();
done();
});
},
'test that etags are ignored for versioned requests': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.v0.8.9.js', function (res, data) {
should.strictEqual(res.headers.etag, undefined);
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
};

View File

@@ -77,7 +77,7 @@ module.exports = {
var io = sio.listen(http.createServer())
, port = ++ports;
io.get('flash policy port').should.eql(843);
io.get('flash policy port').should.eql(10843);
io.set('flash policy port', port);
io.get('flash policy port').should.eql(port);
@@ -163,6 +163,25 @@ module.exports = {
io.flashPolicyServer.close();
done();
}
},
'flashsocket identifies as flashsocket': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.set('transports', ['flashsocket']);
io.sockets.on('connection', function (socket) {
socket.manager.transports[socket.id].name.should.equal('flashsocket');
ws.finishClose();
cl.end();
io.flashPolicyServer.close();
io.server.close();
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid, 'flashsocket');
});
}
};

View File

@@ -323,7 +323,7 @@ module.exports = {
io.sockets.on('connection', function (client) {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'disconnect' })
, JSON.stringify(parser.encodePacket({ type: 'disconnect' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -390,11 +390,11 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePayload([
, JSON.stringify(parser.encodePayload([
parser.encodePacket({ type: 'message', data: 'a' })
, parser.encodePacket({ type: 'message', data: 'b' })
, parser.encodePacket({ type: 'disconnect' })
])
]))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -504,7 +504,7 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/woot' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/woot' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -585,21 +585,21 @@ module.exports = {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function () {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/woot' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/woot' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'disconnect', endpoint: '/woot' })
, JSON.stringify(parser.encodePacket({ type: 'disconnect', endpoint: '/woot' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', data: 'ferret' })
, JSON.stringify(parser.encodePacket({ type: 'message', data: 'ferret' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -656,7 +656,7 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/a' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/a' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -665,7 +665,7 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/b' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/b' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -721,7 +721,7 @@ module.exports = {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', data: '' })
, JSON.stringify(parser.encodePacket({ type: 'message', data: '' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -730,14 +730,14 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/a' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/a' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', endpoint: '/a', data: 'a' })
, JSON.stringify(parser.encodePacket({ type: 'message', endpoint: '/a', data: 'a' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -748,14 +748,14 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/b' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/b' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', endpoint: '/b', data: 'b' })
, JSON.stringify(parser.encodePacket({ type: 'message', endpoint: '/b', data: 'b' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');

View File

@@ -4,84 +4,7 @@
var assert = require('assert');
var Parser = require('../lib/transports/websocket/hybi-07-12.js').Parser;
/**
* Returns a Buffer from a "ff 00 ff"-type hex string.
*/
function makeBufferFromHexString(byteStr) {
var bytes = byteStr.split(' ');
var buf = new Buffer(bytes.length);
for (var i = 0; i < bytes.length; ++i) {
buf[i] = parseInt(bytes[i], 16);
}
return buf;
}
/**
* Splits a buffer in two parts.
*/
function splitBuffer(buffer) {
var b1 = new Buffer(Math.ceil(buffer.length / 2));
buffer.copy(b1, 0, 0, b1.length);
var b2 = new Buffer(Math.floor(buffer.length / 2));
buffer.copy(b2, 0, b1.length, b1.length + b2.length);
return [b1, b2];
}
/**
* Performs hybi07+ type masking on a hex string.
*/
function mask(str, maskString) {
var buf = new Buffer(str);
var mask = makeBufferFromHexString(maskString || '34 83 a8 68');
for (var i = 0; i < buf.length; ++i) {
buf[i] ^= mask[i % 4];
}
return buf;
}
/**
* Unpacks a Buffer into a number.
*/
function unpack(buffer) {
var n = 0;
for (var i = 0; i < buffer.length; ++i) {
n = (i == 0) ? buffer[i] : (n * 256) + buffer[i];
}
return n;
}
/**
* Returns a hex string, representing a specific byte count 'length', from a number.
*/
function pack(length, number) {
return padl(number.toString(16), length, '0').replace(/(\d\d)/g, '$1 ').trim();
}
/**
* Left pads the string 's' to a total length of 'n' with char 'c'.
*/
function padl(s, n, c) {
return new Array(1 + n - s.length).join(c) + s;
}
/**
* Returns a hex string from a Buffer.
*/
function dump(data) {
var s = '';
for (var i = 0; i < data.length; ++i) {
s += padl(data[i].toString(16), 2, '0') + ' ';
}
return s.trim();
}
require('./hybi-common');
/**
* Tests.
@@ -98,7 +21,7 @@ module.exports = {
assert.equal('Hello', data);
});
p.add(makeBufferFromHexString(packet));
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse close message': function() {
@@ -110,7 +33,7 @@ module.exports = {
gotClose = true;
});
p.add(makeBufferFromHexString(packet));
p.add(getBufferFromHexString(packet));
assert.ok(gotClose);
},
'can parse masked text message': function() {
@@ -123,14 +46,14 @@ module.exports = {
assert.equal('5:::{"name":"echo"}', data);
});
p.add(makeBufferFromHexString(packet));
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a masked text message longer than 125 bytes': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
@@ -138,14 +61,14 @@ module.exports = {
assert.equal(message, data);
});
p.add(makeBufferFromHexString(packet));
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a really long masked text message': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString();
var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
@@ -153,7 +76,7 @@ module.exports = {
assert.equal(message, data);
});
p.add(makeBufferFromHexString(packet));
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a fragmented masked text message of 300 bytes': function() {
@@ -162,8 +85,8 @@ module.exports = {
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var msgpiece2 = message.substr(150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece1, '34 83 a8 68'));
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
@@ -171,14 +94,14 @@ module.exports = {
assert.equal(message, data);
});
p.add(makeBufferFromHexString(packet1));
p.add(makeBufferFromHexString(packet2));
p.add(getBufferFromHexString(packet1));
p.add(getBufferFromHexString(packet2));
assert.ok(gotData);
},
'can parse a ping message': function() {
var p = new Parser();
var message = 'Hello';
var packet = '89 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + dump(mask(message, '34 83 a8 68'));
var packet = '89 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotPing = false;
p.on('ping', function(data) {
@@ -186,7 +109,7 @@ module.exports = {
assert.equal(message, data);
});
p.add(makeBufferFromHexString(packet));
p.add(getBufferFromHexString(packet));
assert.ok(gotPing);
},
'can parse a ping with no data': function() {
@@ -198,7 +121,7 @@ module.exports = {
gotPing = true;
});
p.add(makeBufferFromHexString(packet));
p.add(getBufferFromHexString(packet));
assert.ok(gotPing);
},
'can parse a fragmented masked text message of 300 bytes with a ping in the middle': function() {
@@ -207,13 +130,13 @@ module.exports = {
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece1, '34 83 a8 68'));
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + dump(mask(pingMessage, '34 83 a8 68'));
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
@@ -226,9 +149,9 @@ module.exports = {
assert.equal(pingMessage, data);
});
p.add(makeBufferFromHexString(packet1));
p.add(makeBufferFromHexString(pingPacket));
p.add(makeBufferFromHexString(packet2));
p.add(getBufferFromHexString(packet1));
p.add(getBufferFromHexString(pingPacket));
p.add(getBufferFromHexString(packet2));
assert.ok(gotData);
assert.ok(gotPing);
},
@@ -238,13 +161,13 @@ module.exports = {
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece1, '34 83 a8 68'));
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + dump(mask(pingMessage, '34 83 a8 68'));
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '81 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + dump(mask(msgpiece2, '34 83 a8 68'));
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
@@ -258,14 +181,82 @@ module.exports = {
});
var buffers = [];
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(packet1)));
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(pingPacket)));
buffers = buffers.concat(splitBuffer(makeBufferFromHexString(packet2)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet1)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(pingPacket)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet2)));
for (var i = 0; i < buffers.length; ++i) {
p.add(buffers[i]);
}
assert.ok(gotData);
assert.ok(gotPing);
},
'can parse a 100 byte long masked binary message': function() {
var p = new Parser();
var length = 100;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 256 byte long masked binary message': function() {
var p = new Parser();
var length = 256;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 200kb long masked binary message': function() {
var p = new Parser();
var length = 200 * 1024;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 200kb long unmasked binary message': function() {
var p = new Parser();
var length = 200 * 1024;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message);
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
};

View File

@@ -0,0 +1,262 @@
/**
* Test dependencies.
*/
var assert = require('assert');
var Parser = require('../lib/transports/websocket/hybi-16.js').Parser;
require('./hybi-common');
/**
* Tests.
*/
module.exports = {
'can parse unmasked text message': function() {
var p = new Parser();
var packet = '81 05 48 65 6c 6c 6f';
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal('Hello', data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse close message': function() {
var p = new Parser();
var packet = '88 00';
var gotClose = false;
p.on('close', function(data) {
gotClose = true;
});
p.add(getBufferFromHexString(packet));
assert.ok(gotClose);
},
'can parse masked text message': function() {
var p = new Parser();
var packet = '81 93 34 83 a8 68 01 b9 92 52 4f a1 c6 09 59 e6 8a 52 16 e6 cb 00 5b a1 d5';
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal('5:::{"name":"echo"}', data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a masked text message longer than 125 bytes': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var packet = '81 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a really long masked text message': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 64*1024; ++i) message += (i % 5).toString();
var packet = '81 FF ' + pack(16, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a fragmented masked text message of 300 bytes': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var msgpiece2 = message.substr(150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet1));
p.add(getBufferFromHexString(packet2));
assert.ok(gotData);
},
'can parse a ping message': function() {
var p = new Parser();
var message = 'Hello';
var packet = '89 FE ' + pack(4, message.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(message, data);
});
p.add(getBufferFromHexString(packet));
assert.ok(gotPing);
},
'can parse a ping with no data': function() {
var p = new Parser();
var packet = '89 00';
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
});
p.add(getBufferFromHexString(packet));
assert.ok(gotPing);
},
'can parse a fragmented masked text message of 300 bytes with a ping in the middle': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(pingMessage, data);
});
p.add(getBufferFromHexString(packet1));
p.add(getBufferFromHexString(pingPacket));
p.add(getBufferFromHexString(packet2));
assert.ok(gotData);
assert.ok(gotPing);
},
'can parse a fragmented masked text message of 300 bytes with a ping in the middle, which is delievered over sevaral tcp packets': function() {
var p = new Parser();
var message = 'A';
for (var i = 0; i < 300; ++i) message += (i % 5).toString();
var msgpiece1 = message.substr(0, 150);
var packet1 = '01 FE ' + pack(4, msgpiece1.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece1, '34 83 a8 68'));
var pingMessage = 'Hello';
var pingPacket = '89 FE ' + pack(4, pingMessage.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(pingMessage, '34 83 a8 68'));
var msgpiece2 = message.substr(150);
var packet2 = '80 FE ' + pack(4, msgpiece2.length) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(msgpiece2, '34 83 a8 68'));
var gotData = false;
p.on('data', function(data) {
gotData = true;
assert.equal(message, data);
});
var gotPing = false;
p.on('ping', function(data) {
gotPing = true;
assert.equal(pingMessage, data);
});
var buffers = [];
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet1)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(pingPacket)));
buffers = buffers.concat(splitBuffer(getBufferFromHexString(packet2)));
for (var i = 0; i < buffers.length; ++i) {
p.add(buffers[i]);
}
assert.ok(gotData);
assert.ok(gotPing);
},
'can parse a 100 byte long masked binary message': function() {
var p = new Parser();
var length = 100;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 256 byte long masked binary message': function() {
var p = new Parser();
var length = 256;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 200kb long masked binary message': function() {
var p = new Parser();
var length = 200 * 1024;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, true) + ' 34 83 a8 68 ' + getHexStringFromBuffer(mask(message, '34 83 a8 68'));
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
'can parse a 200kb long unmasked binary message': function() {
var p = new Parser();
var length = 200 * 1024;
var message = new Buffer(length);
for (var i = 0; i < length; ++i) message[i] = i % 256;
var originalMessage = getHexStringFromBuffer(message);
var packet = '82 ' + getHybiLengthAsHexString(length, false) + ' ' + getHexStringFromBuffer(message);
var gotData = false;
p.on('binary', function(data) {
gotData = true;
assert.equal(originalMessage, getHexStringFromBuffer(data));
});
p.add(getBufferFromHexString(packet));
assert.ok(gotData);
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -950,6 +950,44 @@ module.exports = {
});
},
'test that emitting an error event doesnt throw': function (done) {
var cl = client(++ports)
, io = create(cl)
io.configure(function () {
io.set('polling duration', .05);
io.set('close timeout', .05);
});
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function () {
cl.end();
io.server.close();
done();
});
});
cl.handshake(function (sid) {
cl.get('/socket.io/{protocol}/xhr-polling/' + sid, function (res, msgs) {
res.statusCode.should.equal(200);
msgs.should.have.length(1);
msgs[0].should.eql({ type: 'noop', endpoint: '' });
cl.post(
'/socket.io/{protocol}/xhr-polling/' + sid
, parser.encodePacket({
type: 'event'
, name: 'error'
})
, function (res, data) {
res.statusCode.should.eql(200);
data.should.equal('1');
}
);
});
});
},
'test emitting an event to the server with data': function (done) {
var cl = client(++ports)
, io = create(cl)