rocket-server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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