Skip to content

Commit

Permalink
#16 API refactoring continues. Use Streams-like API.
Browse files Browse the repository at this point in the history
  • Loading branch information
majek committed Oct 20, 2011
1 parent a394b16 commit 7863ee0
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 64 deletions.
73 changes: 27 additions & 46 deletions README.md
Expand Up @@ -155,7 +155,7 @@ receiving connection wasn't seen for 5 seconds.


### Server instance ### Server instance


Once you have instantiated `Server` class you can hook it to the Once you have create `Server` instance you can hook it to the
[http server instance](http://nodejs.org/docs/v0.4.10/api/http.html#http.createServer). [http server instance](http://nodejs.org/docs/v0.4.10/api/http.html#http.createServer).


```javascript ```javascript
Expand All @@ -172,7 +172,7 @@ constructor.
and emits following event: and emits following event:


<dl> <dl>
<dt>connection(connection)</dt> <dt>Event: connection (connection)</dt>
<dd>A new connection has been successfully opened.</dd> <dd>A new connection has been successfully opened.</dd>
</dl> </dl>


Expand All @@ -182,32 +182,40 @@ handlers.


### Connection instance ### Connection instance


A `Connection` instance has following methods and properties: A `Connection` instance supports
[Node Stream API](http://nodejs.org/docs/v0.5.8/api/streams.html) and
has following methods and properties:


<dl> <dl>
<dt>readyState</dt> <dt>Property: readable (boolean)</dt>
<dd>A property describing a state of the connecion.</dd> <dd>Is the stream readable?</dd>


<dt>send(message)</dt> <dt>Property: writable (boolean)</dt>
<dd>Is the stream writable?</dd>

<dt>write(message)</dt>
<dd>Sends a message over opened connection. It's illegal to send a <dd>Sends a message over opened connection. It's illegal to send a
message after the connection was closed (either by 'close' method message after the connection was closed (either after 'close' or
or 'close' event).</dd> 'end' method or 'close' event).</dd>


<dt>close([status], [reason])</dt> <dt>close([code], [reason])</dt>
<dd>Asks the remote client to disconnect. 'status' and 'reason' <dd>Asks the remote client to disconnect. 'code' and 'reason'
parameters are optional and can be used to share the reason of parameters are optional and can be used to share the reason of
disconnection.</dd> disconnection.</dd>

<dt>end()</dt>
<dd>Asks the remote client to disconnect with default 'code' and
'reason' values.</dd>

</dl> </dl>


A `Connection` instance is also an A `Connection` instance emits the following events:
[EventEmitter](http://nodejs.org/docs/v0.4.10/api/events.html#events.EventEmitter),
and emits following events:


<dl> <dl>
<dt>message(message)</dt> <dt>Event: data (message)</dt>
<dd>A message arrived on the connection.</dd> <dd>A message arrived on the connection.</dd>


<dt>close()</dt> <dt>Event: close ()</dt>
<dd>Connection was closed. This event is triggered exactly once for <dd>Connection was closed. This event is triggered exactly once for
every connection.</dd> every connection.</dd>
</dl> </dl>
Expand All @@ -220,7 +228,7 @@ sjs.on('connection', function(conn) {
conn.on('close', function() { conn.on('close', function() {
console.log('close ' + conn); console.log('close ' + conn);
}); });
conn.on('message', function(message) { conn.on('data', function(message) {
console.log('message ' + conn, console.log('message ' + conn,
message); message);
}); });
Expand All @@ -230,36 +238,9 @@ sjs.on('connection', function(conn) {
### Footnote ### Footnote


A fully working echo server does need a bit more boilerplate (to A fully working echo server does need a bit more boilerplate (to
handle unanswered requests), here it is: handle unanswered requests), see the

[`echo` example](https://github.com/sockjs/sockjs-node/tree/master/examples/echo)
```javascript for a full code.
var http = require('http');
var sockjs = require('sockjs');

var sockjs_opts = {sockjs_url:
"http://sockjs.github.com/sockjs-client/sockjs-latest.min.js"};

var sjs = sockjs.createServer(sockjs_opts);
sjs.on('connection', function(conn) {
conn.on('message', function(message) {
conn.send(message);
});
conn.on('close', function() {});
});

var server = http.createServer();
server.addListener('request', function(req, res) {
res.writeHead(404);
res.end('404 not found');
});
server.addListener('upgrade', function(req, con) {
con.end();
});

sjs.installHandlers(server, {prefix:'[/]echo'});

server.listen(9999, '0.0.0.0');
```


### Examples ### Examples


Expand Down
8 changes: 3 additions & 5 deletions examples/echo/server.js
Expand Up @@ -5,10 +5,10 @@ var node_static = require('node-static');
// 1. Echo sockjs server // 1. Echo sockjs server
var sockjs_opts = {sockjs_url: "http://sockjs.github.com/sockjs-client/sockjs-latest.min.js"}; var sockjs_opts = {sockjs_url: "http://sockjs.github.com/sockjs-client/sockjs-latest.min.js"};


var sjs_echo = new sockjs.Server(sockjs_opts); var sjs_echo = sockjs.createServer(sockjs_opts);
sjs_echo.on('connection', function(conn) { sjs_echo.on('connection', function(conn) {
conn.on('message', function(message) { conn.on('data', function(message) {
conn.send(message); conn.write(message);
}); });
}); });


Expand All @@ -28,5 +28,3 @@ sjs_echo.installHandlers(server, {prefix:'[/]echo'});


console.log(' [*] Listening on 0.0.0.0:9999' ); console.log(' [*] Listening on 0.0.0.0:9999' );
server.listen(9999, '0.0.0.0'); server.listen(9999, '0.0.0.0');


8 changes: 4 additions & 4 deletions src/sockjs.coffee
Expand Up @@ -153,17 +153,17 @@ exports.listen = (http_server, options) ->
class DeprecatedConnectionWrapper extends events.EventEmitter class DeprecatedConnectionWrapper extends events.EventEmitter
constructor: (@conn) -> constructor: (@conn) ->
@id = @conn.id @id = @conn.id
@conn.on 'message', (message) => @conn.on 'data', (message) =>
@emit('message', {data:message}) @emit('message', {data:message})
@conn.on 'close', (e) => @conn.on 'close', () =>
e = e =
status: 1001 status: 1001
reason: 'Session timed out' reason: 'Session timed out'
wasClean: false wasClean: false
@emit('close', e) @emit('close', e)


send: (m) -> send: (m) ->
@conn.send(m) @conn.write(m)


close: (a, b) -> close: (a, b) ->
@conn.close(a, b) @conn.close(a, b)
Expand All @@ -172,7 +172,7 @@ class DeprecatedConnectionWrapper extends events.EventEmitter
@conn.toString() @conn.toString()


DeprecatedConnectionWrapper.prototype.__defineGetter__ 'readyState', -> DeprecatedConnectionWrapper.prototype.__defineGetter__ 'readyState', ->
@conn.readyState if @conn.readable then 1 else 3




class DeprecatedServerWrapper extends events.EventEmitter class DeprecatedServerWrapper extends events.EventEmitter
Expand Down
54 changes: 45 additions & 9 deletions src/transport.coffee
@@ -1,4 +1,4 @@
events = require('events') stream = require('stream')
uuid = require('node-uuid') uuid = require('node-uuid')


class Transport class Transport
Expand All @@ -12,24 +12,55 @@ closeFrame = (status, reason) ->
return 'c' + JSON.stringify([status, reason]) return 'c' + JSON.stringify([status, reason])




class SockJSConnection extends stream.Stream
constructor: (@_session) ->
@id = uuid()

toString: ->
return '<SockJSConnection ' + @id + '>'

write: (string) ->
return @_session.send('' + string)

end: (string) ->
if string
@write(string)
@close()
return null

close: (code, reason) ->
@_session.close(code, reason)

destroy: () ->
@removeAllListeners()
@end()

destroySoon: () ->
@destroy()

SockJSConnection.prototype.__defineGetter__ 'readable', ->
@_session.readyState is Transport.OPEN
SockJSConnection.prototype.__defineGetter__ 'writable', ->
@_session.readyState is Transport.OPEN



MAP = {} MAP = {}


class Session extends events.EventEmitter class Session
constructor: (@session_id, server) -> constructor: (@session_id, server) ->
@heartbeat_delay = server.options.heartbeat_delay @heartbeat_delay = server.options.heartbeat_delay
@disconnect_delay = server.options.disconnect_delay @disconnect_delay = server.options.disconnect_delay
@id = uuid()
@send_buffer = [] @send_buffer = []
@is_closing = false @is_closing = false
@readyState = Transport.CONNECTING @readyState = Transport.CONNECTING
if @session_id if @session_id
MAP[@session_id] = @ MAP[@session_id] = @
@timeout_cb = => @didTimeout() @timeout_cb = => @didTimeout()
@to_tref = setTimeout(@timeout_cb, @disconnect_delay) @to_tref = setTimeout(@timeout_cb, @disconnect_delay)
@connection = new SockJSConnection(@)
@emit_open = => @emit_open = =>
@emit_open = null @emit_open = null
server.emit('connection', @) server.emit('connection', @connection)


register: (recv) -> register: (recv) ->
if @recv if @recv
Expand Down Expand Up @@ -89,22 +120,28 @@ class Session extends events.EventEmitter
if @recv if @recv
throw Error('RECV_STILL_THERE') throw Error('RECV_STILL_THERE')
@readyState = Transport.CLOSED @readyState = Transport.CLOSED
@emit('close') # 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 if @session_id
delete MAP[@session_id] delete MAP[@session_id]
@session_id = null @session_id = null


didMessage: (payload) -> didMessage: (payload) ->
if @readyState is Transport.OPEN if @readyState is Transport.OPEN
@emit('message', payload) @connection.emit('data', payload)
return return


send: (payload) -> send: (payload) ->
if @readyState isnt Transport.OPEN if @readyState isnt Transport.OPEN
throw Error('INVALID_STATE_ERR') return false
@send_buffer.push('' + payload) @send_buffer.push('' + payload)
if @recv if @recv
@tryFlush() @tryFlush()
return true


close: (status=1000, reason="Normal closure") -> close: (status=1000, reason="Normal closure") ->
if @readyState isnt Transport.OPEN if @readyState isnt Transport.OPEN
Expand All @@ -116,9 +153,8 @@ class Session extends events.EventEmitter
@recv.doSendFrame(@close_frame) @recv.doSendFrame(@close_frame)
if @recv if @recv
@unregister @unregister
return true


toString: ->
return '<Session ' + @id + '>'




Session.bySessionId = (session_id) -> Session.bySessionId = (session_id) ->
Expand Down

0 comments on commit 7863ee0

Please sign in to comment.