pax_global_header00006660000000000000000000000064141522737560014526gustar00rootroot0000000000000052 comment=a01674d8bd1e10550bec397a27ba01643dd5ac73 sockjs-node-0.3.24/000077500000000000000000000000001415227375600140335ustar00rootroot00000000000000sockjs-node-0.3.24/.gitignore000066400000000000000000000000661415227375600160250ustar00rootroot00000000000000.pidfile.pid node_modules lib/*.js *~ sockjs-protocol sockjs-node-0.3.24/.npmignore000066400000000000000000000001231415227375600160260ustar00rootroot00000000000000.gitignore lib/.placeholder Makefile src examples tests scripts *~ sockjs-protocol sockjs-node-0.3.24/COPYING000066400000000000000000000003561415227375600150720ustar00rootroot00000000000000Parts of the code are derived from various open source projects. For code derived from Socket.IO by Guillermo Rauch see https://github.com/LearnBoost/socket.io/tree/0.6.17#readme. All other code is released on MIT license, see LICENSE. sockjs-node-0.3.24/Changelog000066400000000000000000000174511415227375600156550ustar00rootroot000000000000000.3.24 ====== * Remove excess file from npm package 0.3.23 ====== * Fix `uuid` usage 0.3.22 ====== * Update `uuid` 0.3.21 ====== * Update `faye-websocket` and `websocket-driver` to address DDoS vulnerability #275 0.3.20 ====== * Updated `node-uuid` and `coffeescript` * Exclude `examples`, `tests`, and `Makefile` from npm package * Update examples to use latest jQuery and sockjs-client #271 * Don't call `res.end` in `writeHead` #266 * Pin `websocket-driver` as later versions cause some tests from `sockjs-protocol` to fail 0.3.19 ====== * Update `node-uuid` version #224 * Add `disable_cors` option to prevent CORS headers from being added to responses #218 * Add `dnt` header to whitelist #212 * Add `x-forwarded-host` and `x-forwarded-port` headers to whitelist #208 * Update `sockjs_url` default to latest 1.x target #223 * Updated hapi.js example #216 0.3.18 ====== * Change to using `res.statusCode` instead of manual parsing of `res._header` #213 * Update sockjs-protocol filename in README #203 0.3.17 ====== * Fix usage of undefined `session` in `heartbeat_timeout` #179 0.3.16 ====== * Fix CORS response for null origin #177 * Add websocket ping-pong and close if no response #129, #162, #169 * Update sockjs-client version in examples #182 * Add koa example #180 * Disable raw websocket endpoint when websocket = false #183 * Upgrade to faye-websocket 0.10.0 and use proper close code * When connection is aborted, don't delay the teardown * Forward additional headers #188 * Add `no-transform` to Cache-Control headers #189 * Update documentation about heartbeats #192 0.3.15 ====== * Remove usage of naked '@' function params to be compatible with coffeescript 1.9.0 #175 0.3.14 ====== * Re-publish to npm because of build issue in 0.3.13 0.3.13 ====== * Upgrade faye-websocket to 0.9.3 to fix #171 0.3.12 ====== * Allow Faye socket constructor options to be passed with faye_server_options option to createServer * Fix websocket bad json tests * Upgrade Faye to allow 0.9.* 0.3.11 ====== * #133 - only delay disconnect on non-websocket transports * Upgrade Faye to 0.8.0 0.3.10 ====== * #168 - Add CORS headers for eventsource * #158 - schedule heartbeat timer even if send_buffer is not empty * #96 - remove rbytes dependency * #83 - update documentation for prefix * #163 - add protection to JSON for SWF exploit * #104 - delete unused parameters in code * #106 - update CDN urls * #79 - Don't remove stream listeners until after end so 'close' event is heard * Get rid of need for _sockjs_onload global variable * Use Faye for websocket request validation * Upgrade Faye to 0.7.3 * Upgrade node-uuid to 1.4.1 0.3.9 ===== * #130 - Set Vary: Origin on CORS requests * Upgrade Faye to 0.7.2 from 0.7.0 0.3.8 ===== * #118 - Allow servers to specify a base URL in /info * #131 - Don't look up session id undefined * #124 - Small grammar updates for ReadMe * Upgrade Faye to 0.7.0 from 0.4.0 0.3.7 ===== * Expose "protocol" on raw websocket connection instance, correctly 0.3.6 ===== * When the server closes a connection, make sure the send buffer still gets flushed. * Expose "protocol" on raw websocket connection instance * #105, #109, #113 - expose 'host', 'user-agent', and 'accept-language' headers * Serve SockJS over https CDN by default * Upgrade Faye to 0.4.4 from 0.4.0 0.3.5 ===== * #103 - connection.protocol might have been empty on some rare occasions. * #99 - faye-websocket was leaking sockets in "closed" state when dealing with rfc websockets 0.3.4 ===== * #73 - apparently 'package' is a reserved keyword (use 'pkg' instead) * #93 - Coffescript can leak a variable when the same name is used in catch statement. Let's always use 'x' as the variable in catch. * #76 - decorateConnection could throw an error if remote connection was closed before setup was complete * #90 - Fix "TypeError: 'addListener'" exception (via @pl). * remove 'optionalDependencies' section from package.json, 'rbytes' was always optional. * #91 - Fix rare null exception. 0.3.3 ===== * sockjs/sockjs-protocol#56, #88 Fix for iOS 6 caching POSTs 0.3.1 ===== * #58 - websocket transport emitted an array instead of a string during onmessage event. * Running under node.js 0.7 caused infinite recursion (Stephan Kochen) * #59 - restrict characters allowed in callback parameter * Updated readme - rbytes package is optional * Updated readme WRT deployments on heroku * Add minimalistic license block to every source file. 0.3.0 ===== * Sending JSESSIONID cookie is now *disabled* by default. * sockjs/sockjs-protocol#46 - introduce new service required for protocol tests "/cookie_needed_echo" * Initial work towards better integration with "connect" (Stephan Kochen). See discusion: https://github.com/senchalabs/connect/pull/506 * More documentation about the Cookie and Origin headers. * #51 - expose "readyState" on connection instance * #53 - expose "protocol" on connection instance * #52 - Some protocols may not emit 'close' event with IE. * sockjs/sockjs-client#49 - Support 'null' origin - aka: allow SockJS client to be served from file:// paths. 0.2.1 ===== * Bumped "faye-websocket" dependency to 0.4. Updated code to take advantage of introduced changes. * Pinned "node-static" and bumped "node-uuid" dependencies. * Removed "Origin" header list of headers exposed to the user. This header is not really meaningful in sockjs context. * Header "Access-Control-Allow-Methods" was misspelled. 0.2.0 ===== * #36, #3 - Replace a custom WebSocket server implementation with faye-websocket-node. * Multiple changes to support SockJS-protocol 0.2. * The session is now closed on network errors immediately (instead of waiting 5 seconds) * Raw websocket interface available - to make it easier to write command line SockJS clients. * Support '/info' url. * The test server got moved from SockJS-client to SockJS-node. * Dropped deprecated Server API (use createServer method instead). * Option `websocket` is now used instead of `disabled_transports`. 0.1.2 ===== * #27 - Allow all unicode characters to be send over SockJS. * #14 - Make it possible to customize JSESSIONID cookie logic. 0.1.1 ===== * #32 Expose various request headers on connection. * #30 Expose request path on connection. 0.1.0 ===== * The API changed, there is now an idiomatic API, modelled on node.js Stream API. The old API is deprecated and there is a dummy wrapper that emulates it. Please do upgrade to the new idiomatic API. * #22 Initial support for hybi13 (stephank) * New options accepted by the `Server` constructor: `log`, `heartbeat_delay` and `disconnect_delay`. * SockJS is now not able to send rich data structures - all data passed to `write` is converted to a string. * #23 `Connection.remoteAddress` property introduced (Stéphan Kochen) * Loads of small changes in order to adhere to protocol spec. 0.0.5 ===== * #20: `npm submodule sockjs` didn't work due to outdated github path. 0.0.4 ===== * Support for htmlfile transport, used by IE in a deployment dependent on cookies. * Added /chunking_test API, used to detect support for HTTP chunking on client side. * Unified code logic for all the chunking transports - the same code is reused for polling versions. * All the chunking transports are closed by the server after 128K was send, in order to force client to GC and reconnect. * Don't distribute source coffeescript with npm. * Minor fixes in websocket code. * Dropped jQuery dependency. * Unicode encoding could been garbled during XHR upload. * Other minor fixes. 0.0.3 ====== * EventSource transport didn't emit 'close' event. 0.0.2 ===== * By default set JSESSIONID cookie, useful for load balancing. 0.0.1 ===== * Initial release. sockjs-node-0.3.24/LICENSE000066400000000000000000000020671415227375600150450ustar00rootroot00000000000000The MIT License (MIT) Copyright (C) 2011 VMware, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sockjs-node-0.3.24/Makefile000066400000000000000000000012371415227375600154760ustar00rootroot00000000000000.PHONY: all serve clean COFFEE:=./node_modules/.bin/coffee #### General all: build build: src/*coffee @$(COFFEE) -v > /dev/null $(COFFEE) -o lib/ -c src/*.coffee clean: rm -f lib/*.js #### Testing test_server: build node tests/test_server/server.js serve: @if [ -e .pidfile.pid ]; then \ kill `cat .pidfile.pid`; \ rm .pidfile.pid; \ fi @while [ 1 ]; do \ make build; \ echo " [*] Running http server"; \ make test_server & \ SRVPID=$$!; \ echo $$SRVPID > .pidfile.pid; \ echo " [*] Server pid: $$SRVPID"; \ inotifywait -r -q -e modify .; \ kill `cat .pidfile.pid`; \ rm -f .pidfile.pid; \ sleep 0.1; \ done sockjs-node-0.3.24/README.md000066400000000000000000000404251415227375600153170ustar00rootroot00000000000000[![NPM version](https://badge.fury.io/js/sockjs.svg)](http://badge.fury.io/js/sockjs) SockJS family: * [SockJS-client](https://github.com/sockjs/sockjs-client) JavaScript client library * [SockJS-node](https://github.com/sockjs/sockjs-node) Node.js server * [SockJS-erlang](https://github.com/sockjs/sockjs-erlang) Erlang server * [SockJS-tornado](https://github.com/MrJoes/sockjs-tornado) Python/Tornado server * [vert.x](https://github.com/eclipse/vert.x) Java/vert.x server Work in progress: * [SockJS-ruby](https://github.com/nyarly/sockjs-ruby) * [SockJS-netty](https://github.com/cgbystrom/sockjs-netty) * [SockJS-gevent](https://github.com/sdiehl/sockjs-gevent) ([and a fork](https://github.com/njoyce/sockjs-gevent)) * [pyramid-SockJS](https://github.com/fafhrd91/pyramid_sockjs) * [wildcloud-websockets](https://github.com/wildcloud/wildcloud-websockets) * [SockJS-cyclone](https://github.com/flaviogrossi/sockjs-cyclone) * [SockJS-twisted](https://github.com/Fugiman/sockjs-twisted/) * [wai-SockJS](https://github.com/Palmik/wai-sockjs) * [SockJS-perl](https://github.com/vti/sockjs-perl) * [SockJS-go](https://github.com/igm/sockjs-go/) What is SockJS? =============== SockJS is a JavaScript library (for browsers) that provides a WebSocket-like object. SockJS gives you a coherent, cross-browser, Javascript API which creates a low latency, full duplex, cross-domain communication channel between the browser and the web server, with WebSockets or without. This necessitates the use of a server, which this is one version of, for Node.js. SockJS-node server ================== SockJS-node is a Node.js server side counterpart of [SockJS-client browser library](https://github.com/sockjs/sockjs-client) written in CoffeeScript. To install `sockjs-node` run: npm install sockjs A simplified echo SockJS server could look more or less like: ```javascript var http = require('http'); var sockjs = require('sockjs'); var echo = sockjs.createServer(); echo.on('connection', function(conn) { conn.on('data', function(message) { conn.write(message); }); conn.on('close', function() {}); }); var server = http.createServer(); echo.installHandlers(server, {prefix:'/echo'}); server.listen(9999, '0.0.0.0'); ``` (Take look at [examples](https://github.com/sockjs/sockjs-node/tree/master/examples/echo) directory for a complete version.) Subscribe to [SockJS mailing list](https://groups.google.com/forum/#!forum/sockjs) for discussions and support. SockJS-node API --------------- The API design is based on common Node APIs like the [Streams API](http://nodejs.org/docs/v0.5.8/api/streams.html) or the [Http.Server API](http://nodejs.org/docs/v0.5.8/api/http.html#http.Server). ### Server class SockJS module is generating a `Server` class, similar to [Node.js http.createServer](http://nodejs.org/docs/v0.5.8/api/http.html#http.createServer) module. ```javascript var sockjs_server = sockjs.createServer(options); ``` Where `options` is a hash which can contain:
sockjs_url (string)
Transports which don't support cross-domain communication natively ('eventsource' to name one) use an iframe trick. A simple page is served from the SockJS server (using its foreign domain) and is placed in an invisible iframe. Code run from this iframe doesn't need to worry about cross-domain issues, as it's being run from domain local to the SockJS server. This iframe also does need to load SockJS javascript client library, and this option lets you specify its url (if you're unsure, point it to the latest minified SockJS client release, this is the default). You must explicitly specify this url on the server side for security reasons - we don't want the possibility of running any foreign javascript within the SockJS domain (aka cross site scripting attack). Also, sockjs javascript library is probably already cached by the browser - it makes sense to reuse the sockjs url you're using in normally.
prefix (string regex)
A url prefix for the server. All http requests which paths begins with selected prefix will be handled by SockJS. All other requests will be passed through, to previously registered handlers.
response_limit (integer)
Most streaming transports save responses on the client side and don't free memory used by delivered messages. Such transports need to be garbage-collected once in a while. `response_limit` sets a minimum number of bytes that can be send over a single http streaming request before it will be closed. After that client needs to open new request. Setting this value to one effectively disables streaming and will make streaming transports to behave like polling transports. The default value is 128K.
websocket (boolean)
Some load balancers don't support websockets. This option can be used to disable websockets support by the server. By default websockets are enabled.
jsessionid (boolean or function)
Some hosting providers enable sticky sessions only to requests that have JSESSIONID cookie set. This setting controls if the server should set this cookie to a dummy value. By default setting JSESSIONID cookie is disabled. More sophisticated behaviour can be achieved by supplying a function.
log (function(severity, message))
It's quite useful, especially for debugging, to see some messages printed by a SockJS-node library. This is done using this `log` function, which is by default set to `console.log`. If this behaviour annoys you for some reason, override `log` setting with a custom handler. The following `severities` are used: `debug` (miscellaneous logs), `info` (requests logs), `error` (serious errors, consider filing an issue).
heartbeat_delay (milliseconds)
In order to keep proxies and load balancers from closing long running http requests we need to pretend that the connection is active and send a heartbeat packet once in a while. This setting controls how often this is done. By default a heartbeat packet is sent every 25 seconds.
disconnect_delay (milliseconds)
The server sends a `close` event when a client receiving connection have not been seen for a while. This delay is configured by this setting. By default the `close` event will be emitted when a receiving connection wasn't seen for 5 seconds.
disable_cors (boolean)
Enabling this option will prevent CORS headers from being included in the HTTP response. Can be used when the sockjs client is known to be connecting from the same origin as the sockjs server.
### Server instance Once you have create `Server` instance you can hook it to the [http.Server instance](http://nodejs.org/docs/v0.5.8/api/http.html#http.createServer). ```javascript var http_server = http.createServer(); sockjs_server.installHandlers(http_server, options); http_server.listen(...); ``` Where `options` can overshadow options given when creating `Server` instance. `Server` instance is an [EventEmitter](http://nodejs.org/docs/v0.4.10/api/events.html#events.EventEmitter), and emits following event:
Event: connection (connection)
A new connection has been successfully opened.
All http requests that don't go under the path selected by `prefix` will remain unanswered and will be passed to previously registered handlers. You must install your custom http handlers before calling `installHandlers`. ### Connection instance A `Connection` instance supports [Node Stream API](http://nodejs.org/docs/v0.5.8/api/streams.html) and has following methods and properties:
Property: readable (boolean)
Is the stream readable?
Property: writable (boolean)
Is the stream writable?
Property: remoteAddress (string)
Last known IP address of the client.
Property: remotePort (number)
Last known port number of the client.
Property: address (object)
Hash with 'address' and 'port' fields.
Property: headers (object)
Hash containing various headers copied from last receiving request on that connection. Exposed headers include: `origin`, `referer` and `x-forwarded-for` (and friends). We explicitly do not grant access to `cookie` header, as using it may easily lead to security issues (for details read the section "Authorisation").
Property: url (string)
Url property copied from last request.
Property: pathname (string)
`pathname` from parsed url, for convenience.
Property: prefix (string)
Prefix of the url on which the request was handled.
Property: protocol (string)
Protocol used by the connection. Keep in mind that some protocols are indistinguishable - for example "xhr-polling" and "xdr-polling".
Property: readyState (integer)
Current state of the connection: 0-connecting, 1-open, 2-closing, 3-closed.
write(message)
Sends a message over opened connection. A message must be a non-empty string. It's illegal to send a message after the connection was closed (either after 'close' or 'end' method or 'close' event).
close([code], [reason])
Asks the remote client to disconnect. 'code' and 'reason' parameters are optional and can be used to share the reason of disconnection.
end()
Asks the remote client to disconnect with default 'code' and 'reason' values.
A `Connection` instance emits the following events:
Event: data (message)
A message arrived on the connection. Message is a unicode string.
Event: close ()
Connection was closed. This event is triggered exactly once for every connection.
For example: ```javascript sockjs_server.on('connection', function(conn) { console.log('connection' + conn); conn.on('close', function() { console.log('close ' + conn); }); conn.on('data', function(message) { console.log('message ' + conn, message); }); }); ``` ### Footnote A fully working echo server does need a bit more boilerplate (to handle requests unanswered by SockJS), see the [`echo` example](https://github.com/sockjs/sockjs-node/tree/master/examples/echo) for a complete code. ### Examples If you want to see samples of running code, take a look at: * [./examples/echo](https://github.com/sockjs/sockjs-node/tree/master/examples/echo) directory, which contains a full example of a echo server. * [./examples/test_server](https://github.com/sockjs/sockjs-node/tree/master/examples/test_server) a standard SockJS test server. Connecting to SockJS-node without the client -------------------------------------------- Although the main point of SockJS it to enable browser-to-server connectivity, it is possible to connect to SockJS from an external application. Any SockJS server complying with 0.3 protocol does support a raw WebSocket url. The raw WebSocket url for the test server looks like: * ws://localhost:8081/echo/websocket You can connect any WebSocket RFC 6455 compliant WebSocket client to this url. This can be a command line client, external application, third party code or even a browser (though I don't know why you would want to do so). Note: This endpoint will *not send any heartbeat packets*. Deployment and load balancing ----------------------------- There are two issues that need to be considered when planning a non-trivial SockJS-node deployment: WebSocket-compatible load balancer and sticky sessions (aka session affinity). ### WebSocket compatible load balancer Often WebSockets don't play nicely with proxies and load balancers. Deploying a SockJS server behind Nginx or Apache could be painful. Fortunately recent versions of an excellent load balancer [HAProxy](http://haproxy.1wt.eu/) are able to proxy WebSocket connections. We propose to put HAProxy as a front line load balancer and use it to split SockJS traffic from normal HTTP data. Take a look at the sample [SockJS HAProxy configuration](https://github.com/sockjs/sockjs-node/blob/master/examples/haproxy.cfg). The config also shows how to use HAproxy balancing to split traffic between multiple Node.js servers. You can also do balancing using dns names. ### Sticky sessions If you plan deploying more than one SockJS server, you must make sure that all HTTP requests for a single session will hit the same server. SockJS has two mechanisms that can be useful to achieve that: * Urls are prefixed with server and session id numbers, like: `/resource///transport`. This is useful for load balancers that support prefix-based affinity (HAProxy does). * `JSESSIONID` cookie is being set by SockJS-node. Many load balancers turn on sticky sessions if that cookie is set. This technique is derived from Java applications, where sticky sessions are often necessary. HAProxy does support this method, as well as some hosting providers, for example CloudFoundry. In order to enable this method on the client side, please supply a `cookie:true` option to SockJS constructor. Development and testing ----------------------- If you want to work on SockJS-node source code, you need to clone the git repo and follow these steps. First you need to install dependencies: cd sockjs-node npm install npm install --dev ln -s .. node_modules/sockjs You're ready to compile CoffeeScript: make build If compilation succeeds you may want to test if your changes pass all the tests. Currently, there are two separate test suites. For both of them you need to start a SockJS-node test server (by default listening on port 8081): make test_server ### SockJS-protocol Python tests To run it run something like: cd sockjs-protocol make test_deps ./venv/bin/python sockjs-protocol.py For details see [SockJS-protocol README](https://github.com/sockjs/sockjs-protocol#readme). ### SockJS-client QUnit tests You need to start a second web server (by default listening on 8080) that is serving various static html and javascript files: cd sockjs-client make test At that point you should have two web servers running: sockjs-node on 8081 and sockjs-client on 8080. When you open the browser on [http://localhost:8080/](http://localhost:8080/) you should be able run the QUnit tests against your sockjs-node server. For details see [SockJS-client README](https://github.com/sockjs/sockjs-client#readme). Additionally, if you're doing more serious development consider using `make serve`, which will automatically the server when you modify the source code. Various issues and design considerations ---------------------------------------- ### Authorisation SockJS-node does not expose cookies to the application. This is done deliberately as using cookie-based authorisation with SockJS simply doesn't make sense and will lead to security issues. Cookies are a contract between a browser and an http server, and are identified by a domain name. If a browser has a cookie set for particular domain, it will pass it as a part of all http requests to the host. But to get various transports working, SockJS uses a middleman - an iframe hosted from target SockJS domain. That means the server will receive requests from the iframe, and not from the real domain. The domain of an iframe is the same as the SockJS domain. The problem is that any website can embed the iframe and communicate with it - and request establishing SockJS connection. Using cookies for authorisation in this scenario will result in granting full access to SockJS communication with your website from any website. This is a classic CSRF attack. Basically - cookies are not suited for SockJS model. If you want to authorise a session - provide a unique token on a page, send it as a first thing over SockJS connection and validate it on the server side. In essence, this is how cookies work. ### Deploying SockJS on Heroku Long polling is known to cause problems on Heroku, but [workaround for SockJS is available](https://github.com/sockjs/sockjs-node/issues/57#issuecomment-5242187). sockjs-node-0.3.24/examples/000077500000000000000000000000001415227375600156515ustar00rootroot00000000000000sockjs-node-0.3.24/examples/echo/000077500000000000000000000000001415227375600165675ustar00rootroot00000000000000sockjs-node-0.3.24/examples/echo/README.md000066400000000000000000000005431415227375600200500ustar00rootroot00000000000000SockJS-node Echo example ======================== To run this example, first install dependencies: npm install And run a server: node server.js That will spawn an http server at http://127.0.0.1:9999/ which will serve both html (served from the current directory) and also SockJS server (under the [/echo](http://127.0.0.1:9999/echo) path). sockjs-node-0.3.24/examples/echo/index.html000066400000000000000000000036151415227375600205710ustar00rootroot00000000000000

SockJS Echo example

sockjs-node-0.3.24/examples/echo/package.json000066400000000000000000000002661415227375600210610ustar00rootroot00000000000000{ "name": "sockjs-echo", "version": "0.0.0-unreleasable", "dependencies": { "node-static": "0.5.9", "sockjs": "*" } } sockjs-node-0.3.24/examples/echo/server.js000066400000000000000000000013411415227375600204320ustar00rootroot00000000000000var http = require('http'); var sockjs = require('sockjs'); var node_static = require('node-static'); // 1. Echo sockjs server var sockjs_echo = sockjs.createServer(); sockjs_echo.on('connection', function(conn) { conn.on('data', function(message) { conn.write(message); }); }); // 2. Static files server var static_directory = new node_static.Server(__dirname); // 3. Usual http stuff var server = http.createServer(); server.addListener('request', function(req, res) { static_directory.serve(req, res); }); server.addListener('upgrade', function(req,res){ res.end(); }); sockjs_echo.installHandlers(server, {prefix:'/echo'}); console.log(' [*] Listening on 0.0.0.0:9999' ); server.listen(9999, '0.0.0.0'); sockjs-node-0.3.24/examples/express-3.x/000077500000000000000000000000001415227375600177505ustar00rootroot00000000000000sockjs-node-0.3.24/examples/express-3.x/index.html000066400000000000000000000036201415227375600217460ustar00rootroot00000000000000

SockJS Express example

sockjs-node-0.3.24/examples/express-3.x/package.json000066400000000000000000000002631415227375600222370ustar00rootroot00000000000000{ "name": "sockjs-express", "version": "0.0.0-unreleasable", "dependencies": { "express": "~3*", "sockjs": "*" } } sockjs-node-0.3.24/examples/express-3.x/server.js000066400000000000000000000012001415227375600216050ustar00rootroot00000000000000var express = require('express'); var sockjs = require('sockjs'); var http = require('http'); // 1. Echo sockjs server var sockjs_echo = sockjs.createServer(); sockjs_echo.on('connection', function(conn) { conn.on('data', function(message) { conn.write(message); }); }); // 2. Express server var app = express(); /* express.createServer will not work here */ var server = http.createServer(app); sockjs_echo.installHandlers(server, {prefix:'/echo'}); console.log(' [*] Listening on 0.0.0.0:9999' ); server.listen(9999, '0.0.0.0'); app.get('/', function (req, res) { res.sendfile(__dirname + '/index.html'); }); sockjs-node-0.3.24/examples/express/000077500000000000000000000000001415227375600173425ustar00rootroot00000000000000sockjs-node-0.3.24/examples/express/index.html000066400000000000000000000036201415227375600213400ustar00rootroot00000000000000

SockJS Express example

sockjs-node-0.3.24/examples/express/package.json000066400000000000000000000002621415227375600216300ustar00rootroot00000000000000{ "name": "sockjs-express", "version": "0.0.0-unreleasable", "dependencies": { "express": "<3", "sockjs": "*" } } sockjs-node-0.3.24/examples/express/server.js000066400000000000000000000010241415227375600212030ustar00rootroot00000000000000var express = require('express'); var sockjs = require('sockjs'); // 1. Echo sockjs server var sockjs_echo = sockjs.createServer(); sockjs_echo.on('connection', function(conn) { conn.on('data', function(message) { conn.write(message); }); }); // 2. Express server var app = express.createServer(); sockjs_echo.installHandlers(app, {prefix:'/echo'}); console.log(' [*] Listening on 0.0.0.0:9999' ); app.listen(9999, '0.0.0.0'); app.get('/', function (req, res) { res.sendfile(__dirname + '/index.html'); }); sockjs-node-0.3.24/examples/hapi/000077500000000000000000000000001415227375600165725ustar00rootroot00000000000000sockjs-node-0.3.24/examples/hapi/html/000077500000000000000000000000001415227375600175365ustar00rootroot00000000000000sockjs-node-0.3.24/examples/hapi/html/index.html000066400000000000000000000036151415227375600215400ustar00rootroot00000000000000

SockJS Echo example

sockjs-node-0.3.24/examples/hapi/package.json000066400000000000000000000002261415227375600210600ustar00rootroot00000000000000{ "name": "sockjs-hapi", "version": "0.0.0-unreleasable", "dependencies": { "hapi": "15.x.x", "inert": "4.x.x", "sockjs": "*" } } sockjs-node-0.3.24/examples/hapi/server.js000066400000000000000000000015751415227375600204460ustar00rootroot00000000000000/** * Created by Tony on 11/1/13. */ var http = require('http'); var sockjs = require('sockjs'); var Hapi = require('hapi'); // 1. Echo sockjs server var sockjs_echo = sockjs.createServer(); sockjs_echo.on('connection', function(conn) { conn.on('data', function(message) { conn.write(message); }); }); // Create a server and set port (default host 0.0.0.0) var hapi_server = new Hapi.Server(); hapi_server.connection({ port: 9999 }); hapi_server.register(require('inert'), (err) => { hapi_server.route({ method: 'GET', path: '/{path*}', handler: function(request, reply) { reply.file('./html/index.html'); } }); }); //hapi_server.listener is the http listener hapi uses sockjs_echo.installHandlers(hapi_server.listener, { prefix: '/echo' }); console.log(' [*] Listening on 0.0.0.0:9999'); hapi_server.start(); sockjs-node-0.3.24/examples/haproxy.cfg000066400000000000000000000021021415227375600200170ustar00rootroot00000000000000# Requires recent Haproxy to work with websockets (for example 1.4.16). defaults mode http # Set timeouts to your needs timeout client 5s timeout connect 5s timeout server 5s frontend all 0.0.0.0:8888 mode http timeout client 120s option forwardfor # Fake connection:close, required in this setup. option http-server-close option http-pretend-keepalive acl is_sockjs path_beg /echo /broadcast /close acl is_stats path_beg /stats use_backend sockjs if is_sockjs use_backend stats if is_stats default_backend static backend sockjs # Load-balance according to hash created from first two # directories in url path. For example requests going to /1/ # should be handled by single server (assuming resource prefix is # one-level deep, like "/echo"). balance uri depth 2 timeout server 120s server srv_sockjs1 127.0.0.1:9999 # server srv_sockjs2 127.0.0.1:9998 backend static balance roundrobin server srv_static 127.0.0.1:8000 backend stats stats uri /stats stats enable sockjs-node-0.3.24/examples/koa/000077500000000000000000000000001415227375600164235ustar00rootroot00000000000000sockjs-node-0.3.24/examples/koa/index.html000066400000000000000000000036201415227375600204210ustar00rootroot00000000000000

SockJS Express example

sockjs-node-0.3.24/examples/koa/package.json000066400000000000000000000001771415227375600207160ustar00rootroot00000000000000{ "name": "sockjs-koa", "version": "0.0.0-unreleasable", "dependencies": { "koa": "^0.21.0", "sockjs": "*" } } sockjs-node-0.3.24/examples/koa/server.js000066400000000000000000000013311415227375600202650ustar00rootroot00000000000000var koa = require('koa'); var sockjs = require('sockjs'); var http = require('http'); var fs = require('fs'); var path = require('path'); // 1. Echo sockjs server var sockjs_echo = sockjs.createServer(); sockjs_echo.on('connection', function(conn) { conn.on('data', function(message) { conn.write(message); }); }); // 2. koa server var app = koa(); app.use(function *() { var filePath = __dirname + '/index.html'; this.type = path.extname(filePath); this.body = fs.createReadStream(filePath); }); var server = http.createServer(app.callback()); sockjs_echo.installHandlers(server, {prefix:'/echo'}); server.listen(9999, '0.0.0.0'); console.log(' [*] Listening on 0.0.0.0:9999' ); sockjs-node-0.3.24/examples/multiplex/000077500000000000000000000000001415227375600176745ustar00rootroot00000000000000sockjs-node-0.3.24/examples/multiplex/README.md000066400000000000000000000012711415227375600211540ustar00rootroot00000000000000WebSocket-multiplex SockJS example ================================== This example is a copy of example from [websocket-multiplex](https://github.com/sockjs/websocket-multiplex/) project: * https://github.com/sockjs/websocket-multiplex/ To run this example, first install dependencies: npm install And run a server: node server.js That will spawn an http server at http://127.0.0.1:9999/ which will serve both html (served from the current directory) and also SockJS service (under the [/multiplex](http://127.0.0.1:9999/multiplex) path). With that set up, WebSocket-multiplex is able to push three virtual connections over a single SockJS connection. See the code for details. sockjs-node-0.3.24/examples/multiplex/index.html000066400000000000000000000055271415227375600217020ustar00rootroot00000000000000

SockJS Multiplex example

sockjs-node-0.3.24/examples/multiplex/package.json000066400000000000000000000003501415227375600221600ustar00rootroot00000000000000{ "name": "sockjs-multiplex", "version": "0.0.0-unreleasable", "dependencies": { "express": "2.5.8", "sockjs": "*", "websocket-multiplex" : "0.1.x" } } sockjs-node-0.3.24/examples/multiplex/server.js000066400000000000000000000024131415227375600215400ustar00rootroot00000000000000var express = require('express'); var sockjs = require('sockjs'); var websocket_multiplex = require('websocket-multiplex'); // 1. Setup SockJS server var service = sockjs.createServer(); // 2. Setup multiplexing var multiplexer = new websocket_multiplex.MultiplexServer(service); var ann = multiplexer.registerChannel('ann'); ann.on('connection', function(conn) { conn.write('Ann says hi!'); conn.on('data', function(data) { conn.write('Ann nods: ' + data); }); }); var bob = multiplexer.registerChannel('bob'); bob.on('connection', function(conn) { conn.write('Bob doesn\'t agree.'); conn.on('data', function(data) { conn.write('Bob says no to: ' + data); }); }); var carl = multiplexer.registerChannel('carl'); carl.on('connection', function(conn) { conn.write('Carl says goodbye!'); // Explicitly cancel connection conn.end(); }); // 3. Express server var app = express.createServer(); service.installHandlers(app, {prefix:'/multiplex'}); console.log(' [*] Listening on 0.0.0.0:9999' ); app.listen(9999, '0.0.0.0'); app.get('/', function (req, res) { res.sendfile(__dirname + '/index.html'); }); app.get('/multiplex.js', function (req, res) { res.sendfile(__dirname + '/multiplex.js'); }); sockjs-node-0.3.24/index.js000066400000000000000000000000521415227375600154750ustar00rootroot00000000000000module.exports = require('./lib/sockjs'); sockjs-node-0.3.24/lib/000077500000000000000000000000001415227375600146015ustar00rootroot00000000000000sockjs-node-0.3.24/lib/.placeholder000066400000000000000000000000001415227375600170520ustar00rootroot00000000000000sockjs-node-0.3.24/package-lock.json000066400000000000000000000125411415227375600172520ustar00rootroot00000000000000{ "name": "sockjs", "version": "0.3.24", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "sockjs", "version": "0.3.24", "license": "MIT", "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", "websocket-driver": "^0.7.4" }, "devDependencies": { "coffeescript": "^1.12.7" } }, "node_modules/coffeescript": { "version": "1.12.7", "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", "dev": true, "bin": { "cake": "bin/cake", "coffee": "bin/coffee" }, "engines": { "node": ">=0.8.0" } }, "node_modules/faye-websocket": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", "dependencies": { "websocket-driver": ">=0.5.1" }, "engines": { "node": ">=0.8.0" } }, "node_modules/http-parser-js": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==" }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/feross" }, { "type": "patreon", "url": "https://www.patreon.com/feross" }, { "type": "consulting", "url": "https://feross.org/support" } ] }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "bin": { "uuid": "dist/bin/uuid" } }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" }, "engines": { "node": ">=0.8.0" } }, "node_modules/websocket-extensions": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "engines": { "node": ">=0.8.0" } } }, "dependencies": { "coffeescript": { "version": "1.12.7", "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", "dev": true }, "faye-websocket": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", "requires": { "websocket-driver": ">=0.5.1" } }, "http-parser-js": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==" }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "requires": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } }, "websocket-extensions": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" } } } sockjs-node-0.3.24/package.json000066400000000000000000000024161415227375600163240ustar00rootroot00000000000000{ "name": "sockjs", "description": "SockJS-node is a server counterpart of SockJS-client a JavaScript library that provides a WebSocket-like object in the browser. SockJS gives you a coherent, cross-browser, Javascript API which creates a low latency, full duplex, cross-domain communication channel between the browser and the web server.", "version": "0.3.24", "author": "Marek Majkowski", "bugs": { "url": "https://github.com/sockjs/sockjs-node/issues" }, "contributors": [ { "name": "Bryce Kahle", "email": "bkahle@gmail.com" }, { "name": "Marek Majkowski", "email": "deadbeef@popcount.org" } ], "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", "websocket-driver": "^0.7.4" }, "devDependencies": { "coffeescript": "^1.12.7" }, "homepage": "https://github.com/sockjs/sockjs-node", "keywords": [ "websockets", "websocket" ], "license": "MIT", "main": "index", "repository": { "type": "git", "url": "https://github.com/sockjs/sockjs-node.git" }, "scripts": { "version": "make build && git add Changelog", "postversion": "npm publish", "postpublish": "git push origin --all && git push origin --tags" } } sockjs-node-0.3.24/scripts/000077500000000000000000000000001415227375600155225ustar00rootroot00000000000000sockjs-node-0.3.24/scripts/test.sh000077500000000000000000000004731415227375600170440ustar00rootroot00000000000000#!/bin/bash set -e rm -rf sockjs-protocol git clone --depth=1 https://github.com/sockjs/sockjs-protocol.git cd sockjs-protocol make test_deps pycco_deps cd .. node tests/test_server/server.js & SRVPID=$! sleep 1 set +e cd sockjs-protocol ./venv/bin/python sockjs-protocol.py PASSED=$? kill $SRVPID exit $PASSED sockjs-node-0.3.24/src/000077500000000000000000000000001415227375600146225ustar00rootroot00000000000000sockjs-node-0.3.24/src/chunking-test.coffee000066400000000000000000000035101415227375600205550ustar00rootroot00000000000000# ***** BEGIN LICENSE BLOCK ***** # Copyright (c) 2011-2012 VMware, Inc. # # For the license see COPYING. # ***** END LICENSE BLOCK ***** utils = require('./utils') exports.app = # TODO: remove in next major release chunking_test: (req, res, _, next_filter) -> res.setHeader('Content-Type', 'application/javascript; charset=UTF-8') res.writeHead(200) write = (payload) => try res.write(payload + '\n') catch x return utils.timeout_chain([ # IE requires 2KB prelude [0, => write('h')], [1, => write(Array(2049).join(' ') + 'h')], [5, => write('h')], [25, => write('h')], [125, => write('h')], [625, => write('h')], [3125, => write('h'); res.end()], ]) return true info: (req, res, _) -> info = { websocket: @options.websocket, origins: ['*:*'] unless @options.disable_cors, cookie_needed: not not @options.jsessionid, entropy: utils.random32(), } # Users can specify a new base URL which further requests will be made # against. For example, it may contain a randomized domain name to # avoid browser per-domain connection limits. if typeof @options.base_url is 'function' info.base_url = @options.base_url() else if @options.base_url info.base_url = @options.base_url res.setHeader('Content-Type', 'application/json; charset=UTF-8') res.writeHead(200) res.end(JSON.stringify(info)) info_options: (req, res) -> res.statusCode = 204 res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET') res.setHeader('Access-Control-Max-Age', res.cache_for) return '' sockjs-node-0.3.24/src/iframe.coffee000066400000000000000000000022511415227375600172360ustar00rootroot00000000000000# ***** BEGIN LICENSE BLOCK ***** # Copyright (c) 2011-2012 VMware, Inc. # # For the license see COPYING. # ***** END LICENSE BLOCK ***** utils = require('./utils') iframe_template = """

Don't panic!

This is a SockJS hidden iframe. It's used for cross domain magic.

""" exports.app = iframe: (req, res) -> context = '{{ sockjs_url }}': @options.sockjs_url content = iframe_template for k of context content = content.replace(k, context[k]) quoted_md5 = '"' + utils.md5_hex(content) + '"' if 'if-none-match' of req.headers and req.headers['if-none-match'] is quoted_md5 res.statusCode = 304 return '' res.setHeader('Content-Type', 'text/html; charset=UTF-8') res.setHeader('ETag', quoted_md5) return content sockjs-node-0.3.24/src/sockjs.coffee000066400000000000000000000154201415227375600172710ustar00rootroot00000000000000# ***** BEGIN LICENSE BLOCK ***** # Copyright (c) 2011-2012 VMware, Inc. # # For the license see COPYING. # ***** END LICENSE BLOCK ***** events = require('events') fs = require('fs') webjs = require('./webjs') utils = require('./utils') trans_websocket = require('./trans-websocket') trans_jsonp = require('./trans-jsonp') trans_xhr = require('./trans-xhr') iframe = require('./iframe') trans_eventsource = require('./trans-eventsource') trans_htmlfile = require('./trans-htmlfile') chunking_test = require('./chunking-test') sockjsVersion = -> try pkg = fs.readFileSync(__dirname + '/../package.json', 'utf-8') catch x return if pkg then JSON.parse(pkg).version else null class App extends webjs.GenericApp welcome_screen: (req, res) -> res.setHeader('content-type', 'text/plain; charset=UTF-8') res.writeHead(200) res.end("Welcome to SockJS!\n") return true handle_404: (req, res) -> res.setHeader('content-type', 'text/plain; charset=UTF-8') res.writeHead(404) res.end('404 Error: Page not found\n') return true disabled_transport: (req, res, data) -> return @handle_404(req, res, data) h_sid: (req, res, data) -> # Some load balancers do sticky sessions, but only if there is # a JSESSIONID cookie. If this cookie isn't yet set, we shall # set it to a dummy value. It doesn't really matter what, as # session information is usually added by the load balancer. req.cookies = utils.parseCookie(req.headers.cookie) if typeof @options.jsessionid is 'function' # Users can supply a function @options.jsessionid(req, res) else if (@options.jsessionid and res.setHeader) # We need to set it every time, to give the loadbalancer # opportunity to attach its own cookies. jsid = req.cookies['JSESSIONID'] or 'dummy' res.setHeader('Set-Cookie', 'JSESSIONID=' + jsid + '; path=/') return data log: (severity, line) -> @options.log(severity, line) utils.objectExtend(App.prototype, iframe.app) utils.objectExtend(App.prototype, chunking_test.app) utils.objectExtend(App.prototype, trans_websocket.app) utils.objectExtend(App.prototype, trans_jsonp.app) utils.objectExtend(App.prototype, trans_xhr.app) utils.objectExtend(App.prototype, trans_eventsource.app) utils.objectExtend(App.prototype, trans_htmlfile.app) generate_dispatcher = (options) -> p = (s) => new RegExp('^' + options.prefix + s + '[/]?$') t = (s) => [p('/([^/.]+)/([^/.]+)' + s), 'server', 'session'] opts_filters = (options_filter='xhr_options') -> return ['h_sid', 'xhr_cors', 'cache_for', options_filter, 'expose'] prefix_dispatcher = [ ['GET', p(''), ['welcome_screen']], ['GET', p('/iframe[0-9-.a-z_]*.html'), ['iframe', 'cache_for', 'expose']], ['OPTIONS', p('/info'), opts_filters('info_options')], ['GET', p('/info'), ['xhr_cors', 'h_no_cache', 'info', 'expose']], ['OPTIONS', p('/chunking_test'), opts_filters()], ['POST', p('/chunking_test'), ['xhr_cors', 'expect_xhr', 'chunking_test']] ] transport_dispatcher = [ ['GET', t('/jsonp'), ['h_sid', 'h_no_cache', 'jsonp']], ['POST', t('/jsonp_send'), ['h_sid', 'h_no_cache', 'expect_form', 'jsonp_send']], ['POST', t('/xhr'), ['h_sid', 'h_no_cache', 'xhr_cors', 'xhr_poll']], ['OPTIONS', t('/xhr'), opts_filters()], ['POST', t('/xhr_send'), ['h_sid', 'h_no_cache', 'xhr_cors', 'expect_xhr', 'xhr_send']], ['OPTIONS', t('/xhr_send'), opts_filters()], ['POST', t('/xhr_streaming'), ['h_sid', 'h_no_cache', 'xhr_cors', 'xhr_streaming']], ['OPTIONS', t('/xhr_streaming'), opts_filters()], ['GET', t('/eventsource'), ['h_sid', 'h_no_cache', 'eventsource']], ['GET', t('/htmlfile'), ['h_sid', 'h_no_cache', 'htmlfile']], ] # TODO: remove this code on next major release if options.websocket prefix_dispatcher.push( ['GET', p('/websocket'), ['raw_websocket']]) transport_dispatcher.push( ['GET', t('/websocket'), ['sockjs_websocket']]) else # modify urls to return 404 prefix_dispatcher.push( ['GET', p('/websocket'), ['cache_for', 'disabled_transport']]) transport_dispatcher.push( ['GET', t('/websocket'), ['cache_for', 'disabled_transport']]) return prefix_dispatcher.concat(transport_dispatcher) class Listener constructor: (@options, emit) -> @app = new App() @app.options = @options @app.emit = emit @app.log('debug', 'SockJS v' + sockjsVersion() + ' ' + 'bound to ' + JSON.stringify(@options.prefix)) @dispatcher = generate_dispatcher(@options) @webjs_handler = webjs.generateHandler(@app, @dispatcher) @path_regexp = new RegExp('^' + @options.prefix + '([/].+|[/]?)$') handler: (req, res, extra) => # All urls that match the prefix must be handled by us. if not req.url.match(@path_regexp) return false @webjs_handler(req, res, extra) return true getHandler: () -> return (a,b,c) => @handler(a,b,c) class Server extends events.EventEmitter constructor: (user_options) -> @options = prefix: '' response_limit: 128*1024 websocket: true faye_server_options: null jsessionid: false heartbeat_delay: 25000 disconnect_delay: 5000 log: (severity, line) -> console.log(line) sockjs_url: 'https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js' if user_options utils.objectExtend(@options, user_options) listener: (handler_options) -> options = utils.objectExtend({}, @options) if handler_options utils.objectExtend(options, handler_options) return new Listener(options, => @emit.apply(@, arguments)) installHandlers: (http_server, handler_options) -> handler = @listener(handler_options).getHandler() utils.overshadowListeners(http_server, 'request', handler) utils.overshadowListeners(http_server, 'upgrade', handler) return true middleware: (handler_options) -> handler = @listener(handler_options).getHandler() handler.upgrade = handler return handler exports.createServer = (options) -> return new Server(options) exports.listen = (http_server, options) -> srv = exports.createServer(options) if http_server srv.installHandlers(http_server) return srv sockjs-node-0.3.24/src/trans-eventsource.coffee000066400000000000000000000024121415227375600214610ustar00rootroot00000000000000# ***** BEGIN LICENSE BLOCK ***** # Copyright (c) 2011-2012 VMware, Inc. # # For the license see COPYING. # ***** END LICENSE BLOCK ***** utils = require('./utils') transport = require('./transport') class EventSourceReceiver extends transport.ResponseReceiver protocol: "eventsource" doSendFrame: (payload) -> # Beware of leading whitespace data = ['data: ', utils.escape_selected(payload, '\r\n\x00'), '\r\n\r\n'] super(data.join('')) exports.app = eventsource: (req, res) -> if !req.headers['origin'] or req.headers['origin'] is 'null' origin = '*' else origin = req.headers['origin'] res.setHeader('Access-Control-Allow-Credentials', 'true') res.setHeader('Content-Type', 'text/event-stream') res.setHeader('Access-Control-Allow-Origin', origin) res.setHeader('Vary', 'Origin') headers = req.headers['access-control-request-headers'] if headers res.setHeader('Access-Control-Allow-Headers', headers) res.writeHead(200) # Opera needs one more new line at the start. res.write('\r\n') transport.register(req, @, new EventSourceReceiver(req, res, @options)) return true sockjs-node-0.3.24/src/trans-htmlfile.coffee000066400000000000000000000036671415227375600207400ustar00rootroot00000000000000# ***** BEGIN LICENSE BLOCK ***** # Copyright (c) 2011-2012 VMware, Inc. # # For the license see COPYING. # ***** END LICENSE BLOCK ***** utils = require('./utils') transport = require('./transport') # Browsers fail with "Uncaught exception: ReferenceError: Security # error: attempted to read protected variable: _jp". Set # document.domain in order to work around that. iframe_template = """

Don't panic!

""" # Safari needs at least 1024 bytes to parse the website. Relevant: # http://code.google.com/p/browsersec/wiki/Part2#Survey_of_content_sniffing_behaviors iframe_template += Array(1024 - iframe_template.length + 14).join(' ') iframe_template += '\r\n\r\n' class HtmlFileReceiver extends transport.ResponseReceiver protocol: "htmlfile" doSendFrame: (payload) -> super( '\r\n' ) exports.app = htmlfile: (req, res) -> if not('c' of req.query or 'callback' of req.query) throw { status: 500 message: '"callback" parameter required' } callback = if 'c' of req.query then req.query['c'] else req.query['callback'] if /[^a-zA-Z0-9-_.]/.test(callback) throw { status: 500 message: 'invalid "callback" parameter' } res.setHeader('Content-Type', 'text/html; charset=UTF-8') res.writeHead(200) res.write(iframe_template.replace(/{{ callback }}/g, callback)); transport.register(req, @, new HtmlFileReceiver(req, res, @options)) return true sockjs-node-0.3.24/src/trans-jsonp.coffee000066400000000000000000000052321415227375600202530ustar00rootroot00000000000000# ***** BEGIN LICENSE BLOCK ***** # Copyright (c) 2011-2012 VMware, Inc. # # For the license see COPYING. # ***** END LICENSE BLOCK ***** transport = require('./transport') class JsonpReceiver extends transport.ResponseReceiver protocol: "jsonp-polling" max_response_size: 1 constructor: (req, res, options, @callback) -> super(req, res, options) doSendFrame: (payload) -> # Yes, JSONed twice, there isn't a a better way, we must pass # a string back, and the script, will be evaled() by the # browser. # prepend comment to avoid SWF exploit #163 super("/**/" + @callback + "(" + JSON.stringify(payload) + ");\r\n") exports.app = jsonp: (req, res, _, next_filter) -> if not('c' of req.query or 'callback' of req.query) throw { status: 500 message: '"callback" parameter required' } callback = if 'c' of req.query then req.query['c'] else req.query['callback'] if /[^a-zA-Z0-9-_.]/.test(callback) or callback.length > 32 throw { status: 500 message: 'invalid "callback" parameter' } # protect against SWF JSONP exploit - #163 res.setHeader('X-Content-Type-Options', 'nosniff') res.setHeader('Content-Type', 'application/javascript; charset=UTF-8') res.writeHead(200) transport.register(req, @, new JsonpReceiver(req, res, @options, callback)) return true jsonp_send: (req, res, query) -> if not query throw { status: 500 message: 'Payload expected.' } if typeof query is 'string' try d = JSON.parse(query) catch x throw { status: 500 message: 'Broken JSON encoding.' } else d = query.d if typeof d is 'string' and d try d = JSON.parse(d) catch x throw { status: 500 message: 'Broken JSON encoding.' } if not d or d.__proto__.constructor isnt Array throw { status: 500 message: 'Payload expected.' } jsonp = transport.Session.bySessionId(req.session) if jsonp is null throw {status: 404} for message in d jsonp.didMessage(message) res.setHeader('Content-Length', '2') res.setHeader('Content-Type', 'text/plain; charset=UTF-8') res.writeHead(200) res.end('ok') return true sockjs-node-0.3.24/src/trans-websocket.coffee000066400000000000000000000110331415227375600211040ustar00rootroot00000000000000# ***** BEGIN LICENSE BLOCK ***** # Copyright (c) 2011-2012 VMware, Inc. # # For the license see COPYING. # ***** END LICENSE BLOCK ***** FayeWebsocket = require('faye-websocket') utils = require('./utils') transport = require('./transport') exports.app = _websocket_check: (req, connection, head) -> if not FayeWebsocket.isWebSocket(req) throw { status: 400 message: 'Not a valid websocket request' } sockjs_websocket: (req, connection, head) -> @_websocket_check(req, connection, head) ws = new FayeWebsocket(req, connection, head, null, @options.faye_server_options) ws.onopen = => # websockets possess no session_id transport.registerNoSession(req, @, new WebSocketReceiver(ws, connection)) return true raw_websocket: (req, connection, head) -> @_websocket_check(req, connection, head) ver = req.headers['sec-websocket-version'] or '' if ['8', '13'].indexOf(ver) is -1 throw { status: 400 message: 'Only supported WebSocket protocol is RFC 6455.' } ws = new FayeWebsocket(req, connection, head, null, @options.faye_server_options) ws.onopen = => new RawWebsocketSessionReceiver(req, connection, @, ws) return true class WebSocketReceiver extends transport.GenericReceiver protocol: "websocket" constructor: (@ws, @connection) -> try @connection.setKeepAlive(true, 5000) @connection.setNoDelay(true) catch x @ws.addEventListener('message', (m) => @didMessage(m.data)) @heartbeat_cb = => @heartbeat_timeout() super @connection setUp: -> super @ws.addEventListener('close', @thingy_end_cb) tearDown: -> @ws.removeEventListener('close', @thingy_end_cb) super didMessage: (payload) -> if @ws and @session and payload.length > 0 try message = JSON.parse(payload) catch x return @didClose(3000, 'Broken framing.') if payload[0] is '[' for msg in message @session.didMessage(msg) else @session.didMessage(message) doSendFrame: (payload) -> if @ws try @ws.send(payload) return true catch x return false didClose: (status=1000, reason="Normal closure") -> super try @ws.close(status, reason, false) catch x @ws = null @connection = null heartbeat: -> supportsHeartbeats = @ws.ping null, -> clearTimeout(hto_ref) if supportsHeartbeats hto_ref = setTimeout(@heartbeat_cb, 10000) else super heartbeat_timeout: -> if @session? @session.close(3000, 'No response from heartbeat') Transport = transport.Transport # Inheritance only for decorateConnection. class RawWebsocketSessionReceiver extends transport.Session constructor: (req, conn, server, @ws) -> @prefix = server.options.prefix @readyState = Transport.OPEN @recv = {connection: conn, protocol: "websocket-raw"} @connection = new transport.SockJSConnection(@) @decorateConnection(req) server.emit('connection', @connection) @_end_cb = => @didClose() @ws.addEventListener('close', @_end_cb) @_message_cb = (m) => @didMessage(m) @ws.addEventListener('message', @_message_cb) didMessage: (m) -> if @readyState is Transport.OPEN @connection.emit('data', m.data) return send: (payload) -> if @readyState isnt Transport.OPEN return false @ws.send(payload) return true close: (status=1000, reason="Normal closure") -> if @readyState isnt Transport.OPEN return false @readyState = Transport.CLOSING @ws.close(status, reason, false) return true didClose: -> if not @ws return @ws.removeEventListener('message', @_message_cb) @ws.removeEventListener('close', @_end_cb) try @ws.close(1000, "Normal closure", false) catch x @ws = null @readyState = Transport.CLOSED @connection.emit('end') @connection.emit('close') @connection = null sockjs-node-0.3.24/src/trans-xhr.coffee000066400000000000000000000054601415227375600177260ustar00rootroot00000000000000# ***** BEGIN LICENSE BLOCK ***** # Copyright (c) 2011-2012 VMware, Inc. # # For the license see COPYING. # ***** END LICENSE BLOCK ***** transport = require('./transport') utils = require('./utils') class XhrStreamingReceiver extends transport.ResponseReceiver protocol: "xhr-streaming" doSendFrame: (payload) -> return super(payload + '\n') class XhrPollingReceiver extends XhrStreamingReceiver protocol: "xhr-polling" max_response_size: 1 exports.app = xhr_options: (req, res) -> res.statusCode = 204 # No content res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, POST') res.setHeader('Access-Control-Max-Age', res.cache_for) return '' xhr_send: (req, res, data) -> if not data throw { status: 500 message: 'Payload expected.' } try d = JSON.parse(data) catch x throw { status: 500 message: 'Broken JSON encoding.' } if not d or d.__proto__.constructor isnt Array throw { status: 500 message: 'Payload expected.' } jsonp = transport.Session.bySessionId(req.session) if not jsonp throw {status: 404} for message in d jsonp.didMessage(message) # FF assumes that the response is XML. res.setHeader('Content-Type', 'text/plain; charset=UTF-8') res.writeHead(204) res.end() return true xhr_cors: (req, res, content) -> if @options.disable_cors return content if !req.headers['origin'] origin = '*' else origin = req.headers['origin'] res.setHeader('Access-Control-Allow-Credentials', 'true') res.setHeader('Access-Control-Allow-Origin', origin) res.setHeader('Vary', 'Origin') headers = req.headers['access-control-request-headers'] if headers res.setHeader('Access-Control-Allow-Headers', headers) return content xhr_poll: (req, res, _, next_filter) -> res.setHeader('Content-Type', 'application/javascript; charset=UTF-8') res.writeHead(200) transport.register(req, @, new XhrPollingReceiver(req, res, @options)) return true xhr_streaming: (req, res, _, next_filter) -> res.setHeader('Content-Type', 'application/javascript; charset=UTF-8') res.writeHead(200) # IE requires 2KB prefix: # http://blogs.msdn.com/b/ieinternals/archive/2010/04/06/comet-streaming-in-internet-explorer-with-xmlhttprequest-and-xdomainrequest.aspx res.write(Array(2049).join('h') + '\n') transport.register(req, @, new XhrStreamingReceiver(req, res, @options) ) return true sockjs-node-0.3.24/src/transport.coffee000066400000000000000000000215271415227375600200360ustar00rootroot00000000000000# ***** BEGIN LICENSE BLOCK ***** # Copyright (c) 2011-2012 VMware, Inc. # # For the license see COPYING. # ***** END LICENSE BLOCK ***** stream = require('stream') { v4: uuidv4 } = require('uuid') utils = require('./utils') class Transport Transport.CONNECTING = 0 Transport.OPEN = 1 Transport.CLOSING = 2 Transport.CLOSED = 3 closeFrame = (status, reason) -> return 'c' + JSON.stringify([status, reason]) class SockJSConnection extends stream.Stream constructor: (@_session) -> @id = uuidv4() @headers = {} @prefix = @_session.prefix toString: -> return '' write: (string) -> return @_session.send('' + string) end: (string) -> if string @write(string) @close() return null close: (code, reason) -> @_session.close(code, reason) destroy: () -> @end() @removeAllListeners() destroySoon: () -> @destroy() SockJSConnection.prototype.__defineGetter__ 'readable', -> @_session.readyState is Transport.OPEN SockJSConnection.prototype.__defineGetter__ 'writable', -> @_session.readyState is Transport.OPEN SockJSConnection.prototype.__defineGetter__ 'readyState', -> @_session.readyState MAP = {} class Session constructor: (@session_id, server) -> @heartbeat_delay = server.options.heartbeat_delay @disconnect_delay = server.options.disconnect_delay @prefix = server.options.prefix @send_buffer = [] @is_closing = false @readyState = Transport.CONNECTING if @session_id MAP[@session_id] = @ @timeout_cb = => @didTimeout() @to_tref = setTimeout(@timeout_cb, @disconnect_delay) @connection = new SockJSConnection(@) @emit_open = => @emit_open = null server.emit('connection', @connection) register: (req, recv) -> if @recv recv.doSendFrame(closeFrame(2010, "Another connection still open")) recv.didClose() return if @to_tref clearTimeout(@to_tref) @to_tref = null if @readyState is Transport.CLOSING @flushToRecv(recv) recv.doSendFrame(@close_frame) recv.didClose() @to_tref = setTimeout(@timeout_cb, @disconnect_delay) return # Registering. From now on 'unregister' is responsible for # setting the timer. @recv = recv @recv.session = @ # Save parameters from request @decorateConnection(req) # first, send the open frame if @readyState is Transport.CONNECTING @recv.doSendFrame('o') @readyState = Transport.OPEN # Emit the open event, but not right now process.nextTick @emit_open # At this point the transport might have gotten away (jsonp). if not @recv return @tryFlush() return decorateConnection: (req) -> # Store the last known address. unless socket = @recv.connection socket = @recv.response.connection try remoteAddress = socket.remoteAddress remotePort = socket.remotePort address = socket.address() catch x if remoteAddress # All-or-nothing @connection.remoteAddress = remoteAddress @connection.remotePort = remotePort @connection.address = address @connection.url = req.url @connection.pathname = req.pathname @connection.protocol = @recv.protocol headers = {} for key in ['referer', 'x-client-ip', 'x-forwarded-for', \ 'x-forwarded-host', 'x-forwarded-port', \ 'x-cluster-client-ip', 'via', 'x-real-ip', \ 'x-forwarded-proto', 'x-ssl', 'dnt', \ 'host', 'user-agent', 'accept-language'] headers[key] = req.headers[key] if req.headers[key] if headers @connection.headers = headers unregister: -> delay = @recv.delay_disconnect @recv.session = null @recv = null if @to_tref clearTimeout(@to_tref) if delay @to_tref = setTimeout(@timeout_cb, @disconnect_delay) else @timeout_cb() flushToRecv: (recv) -> if @send_buffer.length > 0 [sb, @send_buffer] = [@send_buffer, []] recv.doSendBulk(sb) return true return false tryFlush: -> if not @flushToRecv(@recv) or not @to_tref if @to_tref clearTimeout(@to_tref) x = => if @recv @to_tref = setTimeout(x, @heartbeat_delay) @recv.heartbeat() @to_tref = setTimeout(x, @heartbeat_delay) return didTimeout: -> if @to_tref clearTimeout(@to_tref) @to_tref = null if @readyState isnt Transport.CONNECTING and @readyState isnt Transport.OPEN and @readyState isnt Transport.CLOSING throw Error('INVALID_STATE_ERR') if @recv throw Error('RECV_STILL_THERE') @readyState = Transport.CLOSED # Node streaming API is broken. Reader defines 'close' and 'end' # but Writer defines only 'close'. 'End' isn't optional though. # http://nodejs.org/docs/v0.5.8/api/streams.html#event_close_ @connection.emit('end') @connection.emit('close') @connection = null if @session_id delete MAP[@session_id] @session_id = null didMessage: (payload) -> if @readyState is Transport.OPEN @connection.emit('data', payload) return send: (payload) -> if @readyState isnt Transport.OPEN return false @send_buffer.push('' + payload) if @recv @tryFlush() return true close: (status=1000, reason="Normal closure") -> if @readyState isnt Transport.OPEN return false @readyState = Transport.CLOSING @close_frame = closeFrame(status, reason) if @recv # Go away. doSendFrame can trigger didClose which can # trigger unregister. Make sure the @recv is not null. @recv.doSendFrame(@close_frame) if @recv @recv.didClose() if @recv @unregister() return true Session.bySessionId = (session_id) -> if not session_id return null return MAP[session_id] or null register = (req, server, session_id, receiver) -> session = Session.bySessionId(session_id) if not session session = new Session(session_id, server) session.register(req, receiver) return session exports.register = (req, server, receiver) -> register(req, server, req.session, receiver) exports.registerNoSession = (req, server, receiver) -> register(req, server, undefined, receiver) class GenericReceiver constructor: (@thingy) -> @setUp(@thingy) setUp: -> @thingy_end_cb = () => @didAbort() @thingy.addListener('close', @thingy_end_cb) @thingy.addListener('end', @thingy_end_cb) tearDown: -> @thingy.removeListener('close', @thingy_end_cb) @thingy.removeListener('end', @thingy_end_cb) @thingy_end_cb = null didAbort: -> @delay_disconnect = false @didClose() didClose: -> if @thingy @tearDown(@thingy) @thingy = null if @session @session.unregister() doSendBulk: (messages) -> q_msgs = for m in messages utils.quote(m) @doSendFrame('a' + '[' + q_msgs.join(',') + ']') heartbeat: -> @doSendFrame('h') # Write stuff to response, using chunked encoding if possible. class ResponseReceiver extends GenericReceiver max_response_size: undefined delay_disconnect: true constructor: (@request, @response, @options) -> @curr_response_size = 0 try @request.connection.setKeepAlive(true, 5000) catch x super (@request.connection) if @max_response_size is undefined @max_response_size = @options.response_limit doSendFrame: (payload) -> @curr_response_size += payload.length r = false try @response.write(payload) r = true catch x if @max_response_size and @curr_response_size >= @max_response_size @didClose() return r didClose: -> super try @response.end() catch x @response = null exports.GenericReceiver = GenericReceiver exports.Transport = Transport exports.Session = Session exports.ResponseReceiver = ResponseReceiver exports.SockJSConnection = SockJSConnection sockjs-node-0.3.24/src/utils.coffee000066400000000000000000000063761415227375600171470ustar00rootroot00000000000000# ***** BEGIN LICENSE BLOCK ***** # Copyright (c) 2011-2012 VMware, Inc. # # For the license see COPYING. # ***** END LICENSE BLOCK ***** crypto = require('crypto') exports.array_intersection = array_intersection = (arr_a, arr_b) -> r = [] for a in arr_a if arr_b.indexOf(a) isnt -1 r.push(a) return r exports.escape_selected = (str, chars) -> map = {} chars = '%'+chars for c in chars map[c] = escape(c) r = new RegExp('(['+chars+'])') parts = str.split(r) for i in [0...parts.length] v = parts[i] if v.length is 1 and v of map parts[i] = map[v] return parts.join('') # exports.random_string = (letters, max) -> # chars = 'abcdefghijklmnopqrstuvwxyz0123456789_' # max or= chars.length # ret = for i in [0...letters] # chars[Math.floor(Math.random() * max)] # return ret.join('') exports.buffer_concat = (buf_a, buf_b) -> dst = new Buffer(buf_a.length + buf_b.length) buf_a.copy(dst) buf_b.copy(dst, buf_a.length) return dst exports.md5_hex = (data) -> return crypto.createHash('md5') .update(data) .digest('hex') exports.sha1_base64 = (data) -> return crypto.createHash('sha1') .update(data) .digest('base64') exports.timeout_chain = (arr) -> arr = arr.slice(0) if not arr.length then return [timeout, user_fun] = arr.shift() fun = => user_fun() exports.timeout_chain(arr) setTimeout(fun, timeout) exports.objectExtend = (dst, src) -> for k of src if src.hasOwnProperty(k) dst[k] = src[k] return dst exports.overshadowListeners = (ee, event, handler) -> # listeners() returns a reference to the internal array of EventEmitter. # Make a copy, because we're about the replace the actual listeners. old_listeners = ee.listeners(event).slice(0) ee.removeAllListeners(event) new_handler = () -> if handler.apply(this, arguments) isnt true for listener in old_listeners listener.apply(this, arguments) return false return true ee.addListener(event, new_handler) escapable = /[\x00-\x1f\ud800-\udfff\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufff0-\uffff]/g unroll_lookup = (escapable) -> unrolled = {} c = for i in [0...65536] String.fromCharCode(i) escapable.lastIndex = 0 c.join('').replace escapable, (a) -> unrolled[ a ] = '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4) return unrolled lookup = unroll_lookup(escapable) exports.quote = (string) -> quoted = JSON.stringify(string) # In most cases normal json encoding fast and enough escapable.lastIndex = 0 if not escapable.test(quoted) return quoted return quoted.replace escapable, (a) -> return lookup[a] exports.parseCookie = (cookie_header) -> cookies = {} if cookie_header for cookie in cookie_header.split(';') parts = cookie.split('=') cookies[ parts[0].trim() ] = ( parts[1] || '' ).trim() return cookies exports.random32 = () -> foo = crypto.randomBytes(4) v = [foo[0], foo[1], foo[2], foo[3]] return v[0] + (v[1]*256) + (v[2]*256*256) + (v[3]*256*256*256) sockjs-node-0.3.24/src/webjs.coffee000066400000000000000000000160731415227375600171140ustar00rootroot00000000000000# ***** BEGIN LICENSE BLOCK ***** # Copyright (c) 2011-2012 VMware, Inc. # # For the license see COPYING. # ***** END LICENSE BLOCK ***** url = require('url') querystring = require('querystring') fs = require('fs') http = require('http') utils = require('./utils') execute_request = (app, funs, req, res, data) -> try while funs.length > 0 fun = funs.shift() req.last_fun = fun data = app[fun](req, res, data, req.next_filter) catch x if typeof x is 'object' and 'status' of x if x.status is 0 return else if 'handle_' + x.status of app app['handle_' + x.status](req, res, x) else app['handle_error'](req, res, x) else app['handle_error'](req, res, x) app['log_request'](req, res, true) fake_response = (req, res) -> # This is quite simplistic, don't expect much. headers = {'Connection': 'close'} res.writeHead = (status, user_headers = {}) -> r = [] r.push('HTTP/' + req.httpVersion + ' ' + status + ' ' + http.STATUS_CODES[status]) utils.objectExtend(headers, user_headers) for k of headers r.push(k + ': ' + headers[k]) r = r.concat(['', '']) try res.write(r.join('\r\n')) catch x res.setHeader = (k, v) -> headers[k] = v exports.generateHandler = (app, dispatcher) -> return (req, res, head) -> if typeof res.writeHead is "undefined" fake_response(req, res) utils.objectExtend(req, url.parse(req.url, true)) req.start_date = new Date() found = false allowed_methods = [] for row in dispatcher [method, path, funs] = row if path.constructor isnt Array path = [path] # path[0] must be a regexp m = req.pathname.match(path[0]) if not m continue if not req.method.match(new RegExp(method)) allowed_methods.push(method) continue for i in [1...path.length] req[path[i]] = m[i] funs = funs[0..] funs.push('log_request') req.next_filter = (data) -> execute_request(app, funs, req, res, data) req.next_filter(head) found = true break if not found if allowed_methods.length isnt 0 app['handle_405'](req, res, allowed_methods) else app['handle_404'](req, res) app['log_request'](req, res, true) return exports.GenericApp = class GenericApp handle_404: (req, res, x) -> if res.finished return x res.writeHead(404, {}) res.end() return true handle_405:(req, res, methods) -> res.writeHead(405, {'Allow': methods.join(', ')}) res.end() return true handle_error: (req, res, x) -> # console.log('handle_error', x.stack) if res.finished return x if typeof x is 'object' and 'status' of x res.writeHead(x.status, {}) res.end((x.message or "")) else try res.writeHead(500, {}) res.end("500 - Internal Server Error") catch x @log('error', 'Exception on "'+ req.method + ' ' + req.href + '" in filter "' + req.last_fun + '":\n' + (x.stack || x)) return true log_request: (req, res, data) -> td = (new Date()) - req.start_date @log('info', req.method + ' ' + req.url + ' ' + td + 'ms ' + (if res.finished then res.statusCode else '(unfinished)')) return data log: (severity, line) -> console.log(line) expose_html: (req, res, content) -> if res.finished return content if not res.getHeader('Content-Type') res.setHeader('Content-Type', 'text/html; charset=UTF-8') return @expose(req, res, content) expose_json: (req, res, content) -> if res.finished return content if not res.getHeader('Content-Type') res.setHeader('Content-Type', 'application/json') return @expose(req, res, JSON.stringify(content)) expose: (req, res, content) -> if res.finished return content if content and not res.getHeader('Content-Type') res.setHeader('Content-Type', 'text/plain') if content res.setHeader('Content-Length', content.length) res.writeHead(res.statusCode) res.end(content, 'utf8') return true serve_file: (req, res, filename, next_filter) -> a = (error, content) -> if error res.writeHead(500) res.end("can't read file") else res.setHeader('Content-length', content.length) res.writeHead(res.statusCode, res.headers) res.end(content, 'utf8') next_filter(true) fs.readFile(filename, a) throw {status:0} cache_for: (req, res, content) -> res.cache_for = res.cache_for or 365 * 24 * 60 * 60 # one year. # See: http://code.google.com/speed/page-speed/docs/caching.html res.setHeader('Cache-Control', 'public, max-age=' + res.cache_for) exp = new Date() exp.setTime(exp.getTime() + res.cache_for * 1000) res.setHeader('Expires', exp.toGMTString()) return content h_no_cache: (req, res, content) -> res.setHeader('Cache-Control', 'no-store, no-cache, no-transform, must-revalidate, max-age=0') return content expect_form: (req, res, _data, next_filter) -> data = new Buffer(0) req.on 'data', (d) => data = utils.buffer_concat(data, new Buffer(d, 'binary')) req.on 'end', => data = data.toString('utf-8') switch (req.headers['content-type'] or '').split(';')[0] when 'application/x-www-form-urlencoded' q = querystring.parse(data) when 'text/plain', '' q = data else @log('error', "Unsupported content-type " + req.headers['content-type']) q = undefined next_filter(q) throw {status:0} expect_xhr: (req, res, _data, next_filter) -> data = new Buffer(0) req.on 'data', (d) => data = utils.buffer_concat(data, new Buffer(d, 'binary')) req.on 'end', => data = data.toString('utf-8') switch (req.headers['content-type'] or '').split(';')[0] when 'text/plain', 'T', 'application/json', 'application/xml', '', 'text/xml' q = data else @log('error', 'Unsupported content-type ' + req.headers['content-type']) q = undefined next_filter(q) throw {status:0} sockjs-node-0.3.24/tests/000077500000000000000000000000001415227375600151755ustar00rootroot00000000000000sockjs-node-0.3.24/tests/test_server/000077500000000000000000000000001415227375600175425ustar00rootroot00000000000000sockjs-node-0.3.24/tests/test_server/README.md000066400000000000000000000013411415227375600210200ustar00rootroot00000000000000SockJS test server ================== In order to test sockjs server implementation the server needs to provide a standarized sockjs endpoint that will can used by various sockjs-related tests. For example by QUnit or [Sockjs-protocol](https://github.com/sockjs/sockjs-protocol) tests. This small code does exactly that - runs a simple server that supports the following SockJS services: * `/echo` * `/disabled_websocket_echo` * `/cookie_needed_echo` * `/close` * `/ticker` * `/amplify` * `/broadcast` If you just want to quickly run it: npm install node server.js If you want to run do development it's recommended to run `make test_server` from the top `sockjs-node` directory: cd ../.. make test_server sockjs-node-0.3.24/tests/test_server/config.js000066400000000000000000000002501415227375600213420ustar00rootroot00000000000000exports.config = { server_opts: { sockjs_url: 'http://localhost:8080/lib/sockjs.js', websocket: true }, port: 8081, host: '0.0.0.0' }; sockjs-node-0.3.24/tests/test_server/server.js000066400000000000000000000010671415227375600214120ustar00rootroot00000000000000var http = require('http'); var config = require('./config').config; var sockjs_app = require('./sockjs_app'); var server = http.createServer(); server.addListener('request', function(req, res) { res.setHeader('content-type', 'text/plain'); res.writeHead(404); res.end('404 - Nothing here (via sockjs-node test_server)'); }); server.addListener('upgrade', function(req, res){ res.end(); }); sockjs_app.install(config.server_opts, server); console.log(" [*] Listening on", config.host + ':' + config.port); server.listen(config.port, config.host); sockjs-node-0.3.24/tests/test_server/sockjs_app.js000066400000000000000000000060551415227375600222420ustar00rootroot00000000000000var sockjs = require('../../index'); exports.install = function(opts, server) { var sjs_echo = sockjs.createServer(opts); sjs_echo.on('connection', function(conn) { console.log(' [+] echo open ' + conn); conn.on('close', function() { console.log(' [-] echo close ' + conn); }); conn.on('data', function(m) { var d = JSON.stringify(m); console.log(' [ ] echo message ' + conn, d.slice(0,64)+ ((d.length > 64) ? '...' : '')); conn.write(m); }); }); var sjs_close = sockjs.createServer(opts); sjs_close.on('connection', function(conn) { console.log(' [+] clos open ' + conn); conn.close(3000, "Go away!"); conn.on('close', function() { console.log(' [-] clos close ' + conn); }); }); var sjs_ticker = sockjs.createServer(opts); sjs_ticker.on('connection', function(conn) { console.log(' [+] ticker open ' + conn); var tref; var schedule = function() { conn.write('tick!'); tref = setTimeout(schedule, 1000); }; tref = setTimeout(schedule, 1000); conn.on('close', function() { clearTimeout(tref); console.log(' [-] ticker close ' + conn); }); }); var broadcast = {}; var sjs_broadcast = sockjs.createServer(opts); sjs_broadcast.on('connection', function(conn) { console.log(' [+] broadcast open ' + conn); broadcast[conn.id] = conn; conn.on('close', function() { delete broadcast[conn.id]; console.log(' [-] broadcast close' + conn); }); conn.on('data', function(m) { console.log(' [-] broadcast message', m); for(var id in broadcast) { broadcast[id].write(m); } }); }); var sjs_amplify = sockjs.createServer(opts); sjs_amplify.on('connection', function(conn) { console.log(' [+] amp open ' + conn); conn.on('close', function() { console.log(' [-] amp close ' + conn); }); conn.on('data', function(m) { var n = Math.floor(Number(m)); n = (n > 0 && n < 19) ? n : 1; console.log(' [ ] amp message: 2^' + n); conn.write(Array(Math.pow(2, n)+1).join('x')); }); }); sjs_echo.installHandlers(server, {prefix:'/echo', response_limit: 4096}), sjs_echo.installHandlers(server, {prefix:'/disabled_websocket_echo', websocket: false}); sjs_echo.installHandlers(server, {prefix:'/cookie_needed_echo', jsessionid: true}); sjs_close.installHandlers(server, {prefix:'/close'}); sjs_ticker.installHandlers(server, {prefix:'/ticker'}); sjs_amplify.installHandlers(server, {prefix:'/amplify'}); sjs_broadcast.installHandlers(server, {prefix:'/broadcast'}); };