Skip to content

Commit

Permalink
Adding a websocket service base class
Browse files Browse the repository at this point in the history
  • Loading branch information
ged committed Sep 10, 2013
1 parent 74e8436 commit 4f884b4
Show file tree
Hide file tree
Showing 28 changed files with 1,072 additions and 260 deletions.
16 changes: 16 additions & 0 deletions IDEAS.rdoc
Expand Up @@ -60,3 +60,19 @@ gem sandboxing.
Make a MacOS X tool like Pow! that makes it easy to run Strelka apps with a
minimum of setup.


== WebSocketServer

Planned features:

* DSL plugin for handling various kinds of frames, ala the App routing plugin
* DSL plugin derived from the frame-based routing plugin that adds routing logic based on a
JSON data structure's contents
* Automatic de-fragmenting of frames, with a plugin that allows customization of fragment-handling.
* Heartbeat plugin that automatically pings connected clients, and disconnects them if they haven't
been seen in a while.
* Plugin class to facilitate extensions? Not sure how this would work, but it could use
the 'deflate' extension as the test case.



2 changes: 1 addition & 1 deletion MILESTONES.rdoc
Expand Up @@ -4,7 +4,7 @@

=== Documentation

[ ] Ensure the README is up to date
[] Ensure the README is up to date
[ ] Update the IDEAS doc
[ ] Extract the rest of the tutorial part of the manual out into RDoc for the app classes
[ ] Lay out the framework for the 'cookbook' section of the manual
Expand Down
14 changes: 8 additions & 6 deletions bin/strelka
Expand Up @@ -2,6 +2,8 @@
# vim: set nosta noet ts=4 sw=4:

require 'strelka'
require 'strelka/discovery'

require 'trollop'
require 'highline'
require 'loggability'
Expand Down Expand Up @@ -211,7 +213,7 @@ class Strelka::CLICommand
# Set the datadir override if it's given
if self.options.datadir
self.log.debug "Using data dir option: %s" % [ self.options.datadir ]
Strelka::App.local_data_dirs = Pathname( self.options.datadir )
Strelka::Discovery.local_data_dirs = Pathname( self.options.datadir )
end

# Include a 'lib' directory if there is one
Expand Down Expand Up @@ -266,7 +268,7 @@ class Strelka::CLICommand

self.load_additional_requires

paths = Strelka::App.discover_paths
paths = Strelka::Discovery.discover_paths
if paths.empty?
message "None found."
else
Expand Down Expand Up @@ -297,14 +299,14 @@ class Strelka::CLICommand
def start_command( *args )
appname = args.pop
gemname = args.pop
path, gemname = Strelka::App.find( appname, gemname )
path, gemname = Strelka::Discovery.find( appname, gemname )

header "Starting the %s app%s" % [
appname,
gemname == '' ? '' : " from the #{gemname} gem"
]

apps = Strelka::App.load( path )
apps = Strelka::Discovery.load( path )
Strelka.load_config( self.options.config ) if self.options.config
self.log.debug " loaded: %p" % [ apps ]

Expand All @@ -328,13 +330,13 @@ class Strelka::CLICommand
discovery_name = gemname || ''

header "Dumping config for %s" % [ gemname || 'local apps' ]
discovered_apps = Strelka::App.discover_paths
discovered_apps = Strelka::Discovery.discover_paths

raise ArgumentError, "No apps discovered" unless discovered_apps.key?( discovery_name )

discovered_apps[ discovery_name ].each do |apppath|
message " loading %s (%s)" % [ apppath, apppath.basename('.rb') ]
Strelka::App.load( apppath )
Strelka::Discovery.load( apppath )
end

self.load_additional_requires
Expand Down
2 changes: 1 addition & 1 deletion examples/Procfile
Expand Up @@ -5,5 +5,5 @@ auth: ../bin/strelka -D . -l warn -c config.yml start auth-demo
auth2: ../bin/strelka -D . -l warn -c config.yml start auth-demo2
sessions: ../bin/strelka -D . -l debug -c config.yml start sessions-demo
upload: ../bin/strelka -D . -l debug -c config.yml start upload-demo
# ws: ../bin/strelka -l info -c config.yml start ws-echo
ws: ../bin/strelka -l info -c config.yml start ws-echo

1 change: 1 addition & 0 deletions examples/apps/hello-world
Expand Up @@ -26,4 +26,5 @@ end # class HelloWorldApp


# Run the app
Encoding.default_internal = Encoding::UTF_8
HelloWorldApp.run if __FILE__ == $0
69 changes: 69 additions & 0 deletions examples/apps/ws-chat
@@ -0,0 +1,69 @@
#!/usr/bin/env ruby
# encoding: utf-8

require 'strelka/websocketserver'


# An example of a Strelka WebSocketServer that echoes back whatever (non-control) frames you send
# it.
class WebSocketChatServer < Strelka::WebSocketServer
include Mongrel2::WebSocket::Constants


### Set up the user registry.
def initialize( * )
super
@users = {}
end


#
# Heartbeat plugin
#
plugin :heartbeat

heartbeat_rate 5.0
idle_timeout 15.0


# Make a new user slot for sockets when they start up.
on_handshake do |frame|
super
@users[ frame.socket_id ] = nil
end


# When a text frame comes in (or is assembled from :continuation frames), parse it as JSON
# and decide what to do based on its contents.
on_text do |frame|

end



# Handle close frames
on_close do |frame|

username = self.users.delete( frame.socket_id )
self.broadcast_notice( "#{username} disconnected." ) if username

# There will still be a connection slot if this close originated with
# the client. In that case, reply with the ACK CLOSE frame
self.conn.reply( frame.response(:close) ) if
self.connections.delete( [frame.sender_id, frame.conn_id] )

self.conn.reply_close( frame )
return nil
end


end # class RequestDumper

Loggability.level = $DEBUG||$VERBOSE ? :debug : :info
Loggability.format_as( :color ) if $stdin.tty?

# Point to the config database, which will cause the handler to use
# its ID to look up its own socket info.
Mongrel2::Config.configure( :configdb => 'examples.sqlite' )
WebSocketEchoServer.run( 'ws-echo' )

61 changes: 61 additions & 0 deletions examples/apps/ws-echo
@@ -0,0 +1,61 @@
#!/usr/bin/env ruby
# encoding: utf-8

require 'strelka/websocketserver'


# An example of a Strelka WebSocketServer that echoes back whatever (non-control) frames you send
# it.
class WebSocketEchoServer < Strelka::WebSocketServer

# Application ID
ID = 'ws-echo'

#
# Heartbeat plugin
#
# plugin :heartbeat
#
# heartbeat_rate 5.0
# idle_timeout 15.0


#
# Routing
#

plugin :routing

# Handle TEXT, BINARY, and CONTINUATION frames by replying with an echo of the
# same data. Fragmented frames get echoed back as-is without any reassembly.
on_text do |frame|
self.log.info "Echoing data frame: %p" % [ frame ]

# Make the response frame
response = frame.response
response.fin = frame.fin?
IO.copy_stream( frame.payload, response.payload )

return response
end
alias_method :handle_binary_frame, :on_text_frame
alias_method :handle_continuation_frame, :on_text_frame


# Handle close frames
on_close do |frame|

# There will still be a connection slot if this close originated with
# the client. In that case, reply with the ACK CLOSE frame
self.conn.reply( frame.response(:close) ) if
self.connections.delete( [frame.sender_id, frame.conn_id] )

self.conn.reply_close( frame )
return nil
end


end # class RequestDumper

Encoding.default_internal = Encoding::UTF_8
WebSocketEchoServer.run if __FILE__ == $0
5 changes: 3 additions & 2 deletions lib/strelka.rb
Expand Up @@ -36,6 +36,7 @@ module Strelka
require 'strelka/app'
require 'strelka/httprequest'
require 'strelka/httpresponse'
require 'strelka/discovery'


### Get the library version. If +include_buildnum+ is true, the version string will
Expand Down Expand Up @@ -67,10 +68,10 @@ def self::load_config( configfile, defaults=nil )
### named +gemname+. Returns the first matching class, or raises an exception if no
### app class was found.
def self::App( appname, gemname=nil )
path, _ = Strelka::App.find( appname, gemname )
path, _ = Strelka::Discovery.find( appname, gemname )
raise LoadError, "Can't find the %s app." % [ appname ] unless path

apps = Strelka::App.load( path ) or
apps = Strelka::Discovery.load( path ) or
raise ScriptError "Loading %s didn't define a Strelka::App class." % [ path ]

return apps.first
Expand Down

0 comments on commit 4f884b4

Please sign in to comment.