rocket-server 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ *.rdb
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Araneo Ltd.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
File without changes
@@ -0,0 +1,60 @@
1
+ # -*- ruby -*-
2
+
3
+ $:.unshift(File.expand_path('../lib', __FILE__))
4
+ $:.unshift(File.expand_path('../../rocket/lib', __FILE__))
5
+
6
+ require 'rspec/core/rake_task'
7
+ require 'rake/rdoctask'
8
+ require 'rocket/server/version'
9
+
10
+ SUMMARY = %Q{Fast and extensible web socket server built upon em-websockets.}
11
+ DESCRIPTION = %Q{
12
+ This is a fast web socket server built on awesome EventMachine with em-websockets help.
13
+ It provides easy to use, event-oriented middleware for your web-socket powered applications.
14
+ }
15
+
16
+ begin
17
+ require 'jeweler'
18
+ Jeweler::Tasks.new do |gem|
19
+ gem.version = Rocket::Server.version
20
+ gem.name = "rocket-server"
21
+ gem.email = "chris@nu7hat.ch"
22
+ gem.homepage = "http://github.com/araneo/rocket"
23
+ gem.authors = ["Araneo", "Chris Kowalik"]
24
+ gem.summary = SUMMARY
25
+ gem.description = DESCRIPTION
26
+ #gem.add_dependency "rocket", "0.0.1"
27
+ gem.add_dependency "optitron", "~> 0.2"
28
+ gem.add_dependency "json", "~> 1.4"
29
+ gem.add_dependency "eventmachine", ">= 0.12"
30
+ gem.add_dependency "em-websocket", ">= 0.1.4"
31
+ gem.add_dependency "logging", "~> 1.4"
32
+ gem.add_dependency "daemons", "~> 1.1"
33
+ gem.add_dependency "konfigurator", ">= 0.1.1"
34
+ gem.add_development_dependency "rspec", "~> 2.0"
35
+ gem.add_development_dependency "mocha", "~> 0.9"
36
+ end
37
+ Jeweler::GemcutterTasks.new
38
+ rescue
39
+ puts 'Jeweler is not available here'
40
+ end
41
+
42
+ RSpec::Core::RakeTask.new(:spec) do |t|
43
+ t.pattern = 'spec/**/*_spec.rb'
44
+ t.rspec_opts = %q[-I../rocket/lib -Ilib -c -b]
45
+ end
46
+
47
+ RSpec::Core::RakeTask.new(:rcov) do |t|
48
+ t.rcov = true
49
+ t.rspec_opts = %q[-I../rocket/lib -Ilib -c -b]
50
+ t.rcov_opts = %q[-I../rocket/lib -Ilib -T -x "spec"]
51
+ end
52
+
53
+ Rake::RDocTask.new do |rdoc|
54
+ rdoc.title = "Rocket server #{Rocket::Server.version}"
55
+ rdoc.rdoc_dir = 'rdoc'
56
+ rdoc.rdoc_files.include('README*')
57
+ rdoc.rdoc_files.include('lib/**/*.rb')
58
+ end
59
+
60
+ task :default => :spec
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ trap("TERM") { exit(0) }
4
+ trap("INT") { exit(0) }
5
+
6
+ begin
7
+ require "rocket-server"
8
+ rescue LoadError
9
+ require "rubygems"
10
+ require "rocket-server"
11
+ end
12
+
13
+ Rocket::Server::CLI.dispatch(ARGV)
@@ -0,0 +1,38 @@
1
+ # On which host and port server should listen for connections.
2
+ host: localhost
3
+ port: 9772
4
+
5
+ # Run applicaiton in secured mode (wss).
6
+ #secure: true
7
+ secure: false
8
+
9
+ # Specify prefered verbosity level.
10
+ #verbose: true # Increase verbosity
11
+ #quiet: true # Decrease verbosity
12
+ verbose: true
13
+
14
+ # Run web sockets server in debug mode.
15
+ #debug: true
16
+ debug: true
17
+
18
+ # Run daemonized instance of Rocket server.
19
+ #daemon: true
20
+ daemon: false
21
+
22
+ # Specify path to server process' PID file. This option works only
23
+ # when server is daemonized.
24
+ #pid: /var/run/rocket.pid
25
+
26
+ # Specify path to server's output log.
27
+ #log: /var/log/rocket.log
28
+
29
+ # SSL certificates configuration.
30
+ #tls_options:
31
+ # private_key_file: /home/ssl/private.key
32
+ # cert_chain_file: /home/ssl/certificate
33
+
34
+ # Here you can specify all static apps allowed to run on this server.
35
+ # Apps are kind of namespaces with separated authentication.
36
+ apps:
37
+ test_app:
38
+ secret: secretkey
@@ -0,0 +1,2 @@
1
+ require 'rocket'
2
+ require 'rocket/server'
@@ -0,0 +1,87 @@
1
+ require "konfigurator"
2
+ require 'eventmachine'
3
+ require 'em-websocket'
4
+ require 'logging'
5
+ require 'fileutils'
6
+ require 'json'
7
+ require 'yaml'
8
+
9
+ module Rocket
10
+ module Server
11
+
12
+ autoload :Helpers, "rocket/server/helpers"
13
+ autoload :CLI, "rocket/server/cli"
14
+ autoload :Runner, "rocket/server/runner"
15
+ autoload :Connection, "rocket/server/connection"
16
+ autoload :Channel, "rocket/server/channel"
17
+ autoload :Session, "rocket/server/session"
18
+ autoload :App, "rocket/server/app"
19
+ autoload :Misc, "rocket/server/misc"
20
+
21
+ include Konfigurator::Simple
22
+
23
+ # Default configuration
24
+ set :host, "localhost"
25
+ set :port, 9772
26
+ disable :debug
27
+ disable :secure
28
+ disable :verbose
29
+ disable :quiet
30
+ disable :daemon
31
+ set :pid, nil
32
+ set :log, nil
33
+ set :tls_options, {}
34
+ set :apps, []
35
+ set :plugins, []
36
+
37
+ class << self
38
+
39
+ def apps
40
+ @apps ||= settings[:apps] || {}
41
+ end
42
+
43
+ def logger
44
+ @logger ||= default_logger
45
+ end
46
+
47
+ def logger=(logger)
48
+ @logger = logger
49
+ end
50
+
51
+ def load_settings_with_setup(file, local_settings={})
52
+ load_settings_without_setup(file, false)
53
+ settings.merge!(local_settings)
54
+ configure_logger
55
+ require_plugins
56
+ true
57
+ rescue => ex
58
+ puts ex.to_s
59
+ exit 1
60
+ end
61
+ alias_method :load_settings_without_setup, :load_settings
62
+ alias_method :load_settings, :load_settings_with_setup
63
+
64
+ def require_plugins
65
+ plugins.to_a.each {|plugin| require plugin }
66
+ end
67
+
68
+ def configure_logger
69
+ logger.add_appenders(Logging.appenders.file(settings[:log])) if log
70
+ logger.level = :debug if verbose
71
+ logger.level = :error if !verbose and quiet
72
+ true
73
+ end
74
+
75
+ def default_logger
76
+ logger = Logging.logger["Rocket"]
77
+ logger.add_appenders(Logging.appenders.stdout)
78
+ logger.level = :info
79
+ logger
80
+ end
81
+
82
+ end # << self
83
+ end # Server
84
+ end # Rocket
85
+
86
+ require 'rocket/server/version'
87
+
@@ -0,0 +1,44 @@
1
+ module Rocket
2
+ module Server
3
+ class App
4
+
5
+ # Raised when specified app doesn't exist.
6
+ class NotFoundError < RuntimeError; end
7
+
8
+ class << self
9
+ # Returns list of all registered apps.
10
+ def all
11
+ Rocket::Server.apps.to_a.map {|id,app| new(app.merge('id' => id)) }
12
+ end
13
+
14
+ # Returns given app if such is registered.
15
+ #
16
+ # Rocket::Server::App.find("my-test-app-id") # => #<Rocket::Server::App>
17
+ # Rocket::Server::App.find("not-registered-app") # raises Rocket::Server::App::NotFoundError
18
+ #
19
+ def find(app_id)
20
+ if app = Rocket::Server.apps[app_id]
21
+ new(app.merge('id' => app_id))
22
+ else
23
+ raise NotFoundError, "Application '#{app_id}' does not exist!"
24
+ end
25
+ end
26
+ end # << self
27
+
28
+ attr_accessor :attributes
29
+
30
+ def initialize(attrs={})
31
+ @attributes = attrs
32
+ end
33
+
34
+ def id
35
+ attributes['id']
36
+ end
37
+
38
+ def method_missing(meth, *args, &block) # :nodoc:
39
+ (value = attributes[meth.to_s]) ? value : super(meth, *args, &block)
40
+ end
41
+
42
+ end # App
43
+ end # Server
44
+ end # Rocket
@@ -0,0 +1,22 @@
1
+ module Rocket
2
+ module Server
3
+ class Channel < EM::Channel
4
+
5
+ # Get specfied channel for given app or create it if doesn't exist.
6
+ #
7
+ # Channel["my-app" => "testing-channel"] # => #<Rocket::Channel>
8
+ # Channel["my-another-app" => "next-one"]
9
+ #
10
+ def self.[](id)
11
+ app, name = id.to_a.flatten
12
+ channels[[app, name]] ||= new
13
+ end
14
+
15
+ # Returns list of registered channels.
16
+ def self.channels
17
+ @channels ||= {}
18
+ end
19
+
20
+ end # Channels
21
+ end # Server
22
+ end # Rocket
@@ -0,0 +1,52 @@
1
+ require "optitron"
2
+
3
+ module Rocket
4
+ module Server
5
+ class CLI < Optitron::CLI
6
+
7
+ include Helpers
8
+
9
+ desc "Show used version of Rocket server"
10
+ def version
11
+ puts "Rocket v#{Rocket::Server.version}"
12
+ end
13
+
14
+ desc "Start Rocket server on given host and port"
15
+ opt "config", :short_name => "c", :default => "/etc/rocket/default.yml", :desc => "Path to configuration file"
16
+ opt "host", :short_name => "h", :default => "localhost", :desc => "Specify server host"
17
+ opt "port", :short_name => "p", :default => 9772, :desc => "Specify server port"
18
+ opt "plugins", :short_name => "r", :default => [], :desc => "Require ruby extensions at runtime"
19
+ opt "secure", :short_name => "s", :default => false, :desc => "Switch between wss/ws modes"
20
+ opt "verbose", :short_name => "v", :default => false, :desc => "Increase verbosity"
21
+ opt "quiet", :short_name => "q", :default => false, :desc => "Decrease verbosity"
22
+ opt "debug", :short_name => "D", :default => false, :desc => "Run server in debug mode"
23
+ opt "daemon", :short_name => "d", :default => false, :desc => "Run server as a daemon"
24
+ opt "pid", :short_name => "P", :default => "/var/run/rocket/server.pid", :desc => "Path to PID file (only when daemonized)"
25
+ opt "log", :short_name => "l", :default => nil, :desc => "Path to log file"
26
+ def start
27
+ Rocket::Server.load_settings(params.delete('config'), symbolize_keys(params))
28
+ Rocket::Server::Runner.new(Rocket::Server.settings).start!
29
+ end
30
+
31
+ desc "Stop daemonized instance of Rocket server"
32
+ opt "config", :short_name => "c", :default => "/etc/rocket/default.yml", :desc => "Path to configuration file"
33
+ opt "pid", :short_name => "P", :default => "/var/run/rocket/server.pid", :desc => "Path to PID file (only when daemonized)"
34
+ def stop
35
+ Rocket::Server.load_settings(params.delete('config'), symbolize_keys(params))
36
+
37
+ if pid = Rocket::Server::Runner.new(Rocket::Server.settings).kill!
38
+ puts "Rocket server killed (PID: #{pid})"
39
+ else
40
+ puts "No processes were killed!"
41
+ end
42
+ end
43
+
44
+ desc "Generate configuration file"
45
+ def configure(file="rocket.yml")
46
+ Rocket::Server::Misc.generate_config_file(file)
47
+ puts "Created Rocket's server configuration: #{file}"
48
+ end
49
+
50
+ end # CLI
51
+ end # Server
52
+ end # Rocket
@@ -0,0 +1,137 @@
1
+ module Rocket
2
+ module Server
3
+ class Connection < EM::WebSocket::Connection
4
+
5
+ include Helpers
6
+
7
+ LOG_MESSAGES = {
8
+ :app_not_found_error => "#%d : %s",
9
+ :opening_connection => "%s #%d : Opening new connection...",
10
+ :closing_connection => "%s #%d : Closing connection...",
11
+ :auth_success => "%s #%d : Session authenticated successfully!",
12
+ :auth_error => "%s #%d : Authentication error! Invalid secret key.",
13
+ :web_socket_error => "%s #%d : Web socket error: %s",
14
+ :invalid_json_error => "%s #%d : Invalid event's data! This is not valid JSON: %s",
15
+ :subscribing_channel => "%s #%d : Subscribing the '%s' channel.",
16
+ :unsubscribing_channel => "%s #%d : Unsubscribing the '%s' channel.",
17
+ :trigger_error => "%s #%d : Invalid event's data! No channel or event name specified in: %s",
18
+ :access_denied_error => "%s #%d : Action can't be performed, access denied!",
19
+ :trigger_success => "%s #%d : Triggering '%s' on '%s' channel: %s",
20
+ :subscribe_error => "%s #%d : Can't start subscription! Invalid data: %s",
21
+ :unsubscribe_error => "%s #%d : Can't stop subscription! Invalid data: %s"
22
+ }
23
+
24
+ # Only connections to path matching this pattern will be accepted.
25
+ APP_PATH_PATTERN = /^\/app\/([\d\w\-\_]+)/
26
+
27
+ attr_reader :session
28
+
29
+ def initialize(options={})
30
+ super(options)
31
+ @onopen = method(:onopen)
32
+ @onclose = method(:onclose)
33
+ @onmessage = method(:onmessage)
34
+ @onerror = method(:onerror)
35
+ @debug = Rocket::Server.debug
36
+ end
37
+
38
+ # Starts new session for application specified in request path.
39
+ def onopen
40
+ ok = false
41
+ request["Path"] =~ APP_PATH_PATTERN
42
+
43
+ if @session = Session.new($1)
44
+ log_debug(:opening_connection, app_id, signature)
45
+
46
+ if secret = request["Query"]["secret"]
47
+ if @session.authenticate!(secret)
48
+ ok = true
49
+ log_debug(:auth_success, app_id, signature)
50
+ else
51
+ log_debug(:auth_error, app_id, signature)
52
+ end
53
+ else
54
+ ok = true
55
+ end
56
+ end
57
+
58
+ send({:event => 'rocket:connected', :data => { :session_id => signature }}.to_json) if ok
59
+ return ok
60
+ rescue App::NotFoundError => ex
61
+ log_error(:app_not_found_error, signature, ex.to_s)
62
+ close_connection
63
+ end
64
+
65
+ # Closes current session.
66
+ def onclose
67
+ log_debug(:closing_connection, app_id, signature)
68
+ @session.close and true if session?
69
+ end
70
+
71
+ # Handler websocket's of runtime errors.
72
+ def onerror(reason)
73
+ log_error(:web_socket_error, app_id, signature, reason)
74
+ end
75
+
76
+ # Dispatches the received message.
77
+ def onmessage(message)
78
+ if session? and data = JSON.parse(message)
79
+ case data["event"]
80
+ when "rocket:subscribe" then subscribe!(data['data'] || {})
81
+ when "rocket:unsubscribe" then unsubscribe!(data['data'] || {})
82
+ else
83
+ trigger!(data)
84
+ end
85
+ end
86
+ rescue JSON::ParserError
87
+ log_error(:invalid_json_error, app_id, signature, message.inspect)
88
+ end
89
+
90
+ # Returns true if session is open.
91
+ def session?
92
+ !!@session
93
+ end
94
+
95
+ # Handles subscription event.
96
+ def subscribe!(data)
97
+ if channel = data["channel"]
98
+ log_debug(:subscribing_channel, app_id, signature, channel)
99
+ @session.subscribe(channel, self)
100
+ else
101
+ log_error(:subscribe_error, app_id, signature, data.to_json)
102
+ false
103
+ end
104
+ end
105
+
106
+ # Handles unsubscribe event.
107
+ def unsubscribe!(data)
108
+ if channel = data["channel"]
109
+ log_debug(:unsubscribing_channel, app_id, signature, channel)
110
+ @session.unsubscribe(channel, self)
111
+ else
112
+ log_error(:unsubscribe_error, app_id, signature, data.to_json)
113
+ false
114
+ end
115
+ end
116
+
117
+ # Publishes given message.
118
+ def trigger!(data)
119
+ if session? and session.authenticated?
120
+ channel, event = data.values_at("channel", "event")
121
+ if channel and event
122
+ log_debug(:trigger_success, app_id, signature, event, channel, data.inspect)
123
+ return Channel[session.app_id => channel].push(data.to_json)
124
+ end
125
+ log_error(:trigger_error, app_id, signature, data.inspect)
126
+ else
127
+ log_error(:access_denied_error, app_id, signature)
128
+ end
129
+ end
130
+
131
+ def app_id
132
+ session? ? session.app_id : "unknown"
133
+ end
134
+
135
+ end # Connection
136
+ end # Server
137
+ end # Rocket