socky-server 0.4.1 → 0.5.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +0 -4
  2. data/.travis.yml +6 -0
  3. data/CHANGELOG.md +11 -5
  4. data/Gemfile +2 -0
  5. data/README.md +47 -68
  6. data/Rakefile +5 -7
  7. data/config.ru +19 -0
  8. data/example/config.yml +4 -0
  9. data/lib/socky/server.rb +23 -0
  10. data/lib/socky/server/application.rb +51 -0
  11. data/lib/socky/server/channel.rb +30 -0
  12. data/lib/socky/server/channel/base.rb +80 -0
  13. data/lib/socky/server/channel/presence.rb +49 -0
  14. data/lib/socky/server/channel/private.rb +44 -0
  15. data/lib/socky/server/channel/public.rb +43 -0
  16. data/lib/socky/server/channel/stub.rb +17 -0
  17. data/lib/socky/server/config.rb +52 -0
  18. data/lib/socky/server/connection.rb +66 -0
  19. data/lib/socky/server/http.rb +95 -0
  20. data/lib/socky/server/logger.rb +24 -0
  21. data/lib/socky/server/message.rb +35 -0
  22. data/lib/socky/server/misc.rb +18 -0
  23. data/lib/socky/server/version.rb +5 -0
  24. data/lib/socky/server/websocket.rb +43 -0
  25. data/socky-server.gemspec +5 -7
  26. data/spec/fixtures/example_config.yml +3 -0
  27. data/spec/integration/ws_channels_spec.rb +144 -0
  28. data/spec/integration/ws_connection_spec.rb +48 -0
  29. data/spec/integration/ws_presence_spec.rb +118 -0
  30. data/spec/integration/ws_rights_spec.rb +133 -0
  31. data/spec/spec_helper.rb +24 -2
  32. data/spec/support/websocket_application.rb +14 -0
  33. data/spec/unit/socky/server/application_spec.rb +54 -0
  34. data/spec/unit/socky/server/config_spec.rb +50 -0
  35. data/spec/unit/socky/server/connection_spec.rb +67 -0
  36. data/spec/unit/socky/server/message_spec.rb +64 -0
  37. metadata +93 -126
  38. data/bin/socky +0 -5
  39. data/lib/em-websocket_hacks.rb +0 -15
  40. data/lib/socky.rb +0 -75
  41. data/lib/socky/connection.rb +0 -137
  42. data/lib/socky/connection/authentication.rb +0 -99
  43. data/lib/socky/connection/finders.rb +0 -67
  44. data/lib/socky/message.rb +0 -85
  45. data/lib/socky/misc.rb +0 -74
  46. data/lib/socky/net_request.rb +0 -27
  47. data/lib/socky/options.rb +0 -39
  48. data/lib/socky/options/config.rb +0 -79
  49. data/lib/socky/options/parser.rb +0 -93
  50. data/lib/socky/runner.rb +0 -95
  51. data/spec/em-websocket_spec.rb +0 -36
  52. data/spec/files/default.yml +0 -18
  53. data/spec/files/invalid.yml +0 -1
  54. data/spec/socky/connection/authentication_spec.rb +0 -183
  55. data/spec/socky/connection/finders_spec.rb +0 -188
  56. data/spec/socky/connection_spec.rb +0 -151
  57. data/spec/socky/message_spec.rb +0 -102
  58. data/spec/socky/misc_spec.rb +0 -74
  59. data/spec/socky/net_request_spec.rb +0 -42
  60. data/spec/socky/options/config_spec.rb +0 -72
  61. data/spec/socky/options/parser_spec.rb +0 -76
  62. data/spec/socky/options_spec.rb +0 -60
  63. data/spec/socky/runner_spec.rb +0 -88
  64. data/spec/socky_spec.rb +0 -89
  65. data/spec/support/stallion.rb +0 -96
data/.gitignore CHANGED
@@ -1,6 +1,2 @@
1
1
  pkg/
2
- tmp/
3
- *.gem
4
-
5
- spec/files/socky.log
6
2
  Gemfile.lock
@@ -0,0 +1,6 @@
1
+ script: "bundle exec rake spec"
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - ree
6
+ - rbx
@@ -1,11 +1,17 @@
1
1
  # Changelog
2
2
 
3
- ## 0.4.1 / 2011-05-12
3
+ ## 0.5.0.beta1 / 2011-08-01
4
4
 
5
- - new features:
6
- - none
7
- - bugfixes:
8
- - restore compatibility with em-websocket 0.3.x
5
+ Socky was rewritten from scratch. From this version it's Rack application and is based on
6
+ open protocol, and have a lot of new features. Some of them:
7
+
8
+ - Rack app - you can run both Socky and your web-application in the same process!
9
+ - New, standarized communication protocol(it will be easy to implement Socky in other languages)
10
+ - New user authentication process - much faster and more secure
11
+ - Allow users to dynamicly subscribe/unsubscribe from channels
12
+ - New events system - easier to learn and much more powerfull
13
+
14
+ And many more - please check [Socky website](http://socky.org) for more or check specific Socky elements at [Github](http://github.com/socky).
9
15
 
10
16
  ## 0.4.0 / 2010-10-27
11
17
 
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gemspec
4
+
5
+ gem 'rake'
data/README.md CHANGED
@@ -1,102 +1,81 @@
1
- Socky - server in Ruby
2
- ===========
1
+ # Socky - server in Ruby [![](http://travis-ci.org/socky/socky-server-ruby.png)](http://travis-ci.org/socky/socky-server-ruby)
3
2
 
4
- Socky is push server for Ruby based on WebSockets. It allows you to break border between your application and client browser by letting the server initialize a connection and push data to the client.
3
+ ## Installation
5
4
 
6
- ## Example
5
+ $ gem install socky-server --pre
7
6
 
8
- You can try [live example](http://sockydemo.imanel.org) or view its [source code](http://github.com/socky/socky-example)
9
-
10
- Also important information can be found on our [google group](http://groups.google.com/group/socky-users).
11
-
12
- ## Install
13
-
14
- The best way to install Socky server is via RubyGems:
15
-
16
- gem install socky-server
17
-
18
- Socky ruby server requires the gems em-websocket and em-http-request. These are automatically installed by the gem install command.
19
-
20
- Alternative method is to clone repo and run it manually:
7
+ ## Usage
21
8
 
22
- git clone git://github.com/socky/socky-server-ruby.git
23
- cd socky-server-ruby
24
- ./bin/socky
9
+ Socky server provides two Rack middlewares - WebSocket and HTTP. Each one of them can be used separately, but they can be also used in one process. Example Rackup file could look like that:
25
10
 
26
- You can also build it after clonning(this will require Jeweler gem)
11
+ require 'socky/server'
12
+
13
+ map '/websocket' do
14
+ run Socky::Server::WebSocket.new
15
+ end
16
+
17
+ map '/http' do
18
+ run Socky::Server::HTTP.new
19
+ end
27
20
 
28
- rake gemspec
29
- rake build
21
+ ## Configuration
30
22
 
31
- ## Runtime Dependencies
23
+ Both middlewares accept options as hash. Currently available options are:
32
24
 
33
- - EM-WebSocket: Backend for WebSocket server
34
- - EM-HTTP-Client: Sending authorize requests
25
+ ### :applications [Hash]
35
26
 
36
- ## Usage
27
+ Hash of supported applications. Each key is application name, each value is application secret. You can use as much applications as you want - each of them will have separate application address created by mixing hostname, middleware address and applicatio name. So i.e. for app "my_app" WebSocket application uri will be:
37
28
 
38
- After installing gem you will have binary available:
29
+ http://example.org/websocket/my_app
39
30
 
40
- socky [OPTIONS]
31
+ ### :debug [Boolean]
41
32
 
42
- First of all you will need to generate config file:
33
+ Should application log output? Default Rack logger will be used, so demonized server will log to file. Please note that for HTTP middlewere Rack::CommonLogger will be more reliable that debug mode.
43
34
 
44
- socky -g config.yml
35
+ ### :config [String]
45
36
 
46
- After that you can tweak this file, or just run server with default values:
37
+ Path to YAML config file. Config file should contain hash with exactly the same syntax like normal options.
47
38
 
48
- socky -c config.yml
49
-
50
- ## Configuration
39
+ ## Example configuration
51
40
 
52
- The following is a list of the currently supported configuration options. These can all be specified by creating a config file. There are also flags for the `socky` executable which are described below next to their respective configuration options.
41
+ Create file 'config.ru':
53
42
 
54
- ### Configuration Settings
43
+ require 'socky/server'
55
44
 
56
- | *Setting* | *Config* | *Flag* | *Description* |
57
- | --------------- | ------------------------ | ----------------------- | ------------- |
58
- | Generate config | | `-g, --generate [path]` | Generate default config file
59
- | Read config | | `-c, --config [path]` | Path to configuration file
60
- | Port | `port: [integer]` | `-p, --port PORT` | Set the port for server to listen
61
- | Secure | `secure: [boolean]` | `-s, --secure` | Set wss/SSL model on
62
- | TLS Options | `tls_options: [hash]` | | Paths to private key file and certificate chain file for secure connection
63
- | Daemon | `daemon: [boolean]` | `-d, --daemon` | Run in daemon(background) mode
64
- | Pid path | `pid_path: [path]` | `-P, --pid` | Path to store pid path
65
- | Kill | | `-k, --kill` | Kill daemonized server described by file in pid path
66
- | Use log | `log_path: [path]` | `-l, --log [path]` | Path to print debugging information
67
- | Debug | `debug: [boolean]` | `--debug` | Run in debug mode
68
- | Deep debug | `deep_debug: [boolean]` | `--deep-debug` | Run in debug mode that is even more verbose
69
- | Subscribe URL | `subscribe_url: [url]` | | Url to with send authentication request
70
- | Unsubscribe URL | `unsubscribe_url: [url]` | | Url to with send logout request
71
- | Timeout | `timeout: [integer]` | | Time after with authentication request will timeout
72
- | Secret | `secret: [string]` | | Key that will be used by to authorize as admin user
45
+ options = {
46
+ :debug => true,
47
+ :applications => {
48
+ :my_app => 'my_secret',
49
+ :other_app => 'other_secret'
50
+ }
51
+ }
73
52
 
74
- ### Default Configuration
53
+ map '/websocket' do
54
+ run Socky::Server::WebSocket.new options
55
+ end
75
56
 
76
- :port: 8080
77
- :debug: false
57
+ map '/http' do
58
+ use Rack::CommonLogger
59
+ run Socky::Server::HTTP.new options
60
+ end
78
61
 
79
- :subscribe_url: http://localhost:3000/socky/subscribe
80
- :unsubscribe_url: http://localhost:3000/socky/unsubscribe
62
+ Run file using Thin:
81
63
 
82
- :secret: my_secret_key
64
+ $ thin -R config.ru -p3001 start
83
65
 
84
- :secure: false
66
+ ## Setting other options
85
67
 
86
- # :timeout: 3
68
+ Options like demonizing, logging to file, SSL support and others should be supported by Rack server like Thin. Socky Server is utilizing all of them so we will not describe them here.
87
69
 
88
- # :log_path: /var/log/socky.log
89
- # :pid_path: /var/run/socky.pid
70
+ ## Which Rack servers are currently supported?
90
71
 
91
- # :tls_options:
92
- # :private_key_file: /private/key
93
- # :cert_chain_file: /ssl/certificate
72
+ All that are supported by [websocket-rack](http://github.com/imanel/websocket-rack). At the time of writing only Thin was supported, but it should change in near future.
94
73
 
95
74
  ## License
96
75
 
97
76
  (The MIT License)
98
77
 
99
- Copyright (c) 2010 Bernard Potocki
78
+ Copyright (c) 2011 Bernard Potocki
100
79
 
101
80
  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:
102
81
 
data/Rakefile CHANGED
@@ -1,13 +1,11 @@
1
1
  require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
3
 
4
- require 'rake'
5
- require 'rake/clean'
6
- CLEAN.include %w(**/*.{log,rbc})
7
-
8
4
  require 'rspec/core/rake_task'
9
5
 
10
- task :default => :spec
11
-
12
- RSpec::Core::RakeTask.new(:spec) do |t|
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.rspec_opts = ["-c", "-f progress"]
8
+ t.pattern = 'spec/**/*_spec.rb'
13
9
  end
10
+
11
+ task :default => :spec
@@ -0,0 +1,19 @@
1
+ # Start with:
2
+ # $ thin -R socky.ru start
3
+ current_dir = File.expand_path(File.dirname(__FILE__))
4
+ require current_dir + '/lib/socky/server'
5
+
6
+ options = {
7
+ :config_file => current_dir + '/example/config.yml',
8
+ :debug => true
9
+ }
10
+
11
+ map '/websocket' do
12
+ run Socky::Server::WebSocket.new options
13
+ end
14
+
15
+ map '/http' do
16
+ use Rack::CommonLogger
17
+
18
+ run Socky::Server::HTTP.new options
19
+ end
@@ -0,0 +1,4 @@
1
+ debug: false
2
+
3
+ applications:
4
+ my_app: my_secret
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+ require 'rack/websocket'
4
+ require 'socky/authenticator'
5
+
6
+ # Socky is a WebSocket server and client for Ruby
7
+ # @author Bernard "Imanel" Potocki
8
+ # @see http://github.com/socky/socky-server-ruby main repository
9
+ module Socky
10
+ module Server
11
+ ROOT = File.expand_path(File.dirname(__FILE__))
12
+
13
+ autoload :Application, "#{ROOT}/server/application"
14
+ autoload :Channel, "#{ROOT}/server/channel"
15
+ autoload :Config, "#{ROOT}/server/config"
16
+ autoload :Connection, "#{ROOT}/server/connection"
17
+ autoload :HTTP, "#{ROOT}/server/http"
18
+ autoload :Logger, "#{ROOT}/server/logger"
19
+ autoload :Message, "#{ROOT}/server/message"
20
+ autoload :Misc, "#{ROOT}/server/misc"
21
+ autoload :WebSocket, "#{ROOT}/server/websocket"
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ module Socky
2
+ module Server
3
+ class Application
4
+
5
+ attr_accessor :name, :secret
6
+
7
+ class << self
8
+ # list of all known applications
9
+ # @return [Hash] list of applications
10
+ def list
11
+ @list ||= {}
12
+ end
13
+
14
+ # find application by name
15
+ # @param [String] name name of application
16
+ # @return [Application] found application or nil
17
+ def find(name)
18
+ list[name]
19
+ end
20
+ end
21
+
22
+ # initialize new application
23
+ # @param [String] name application name
24
+ # @param [String] secret application secret key
25
+ def initialize(name, secret)
26
+ @name = name
27
+ @secret = secret
28
+ self.class.list[name] ||= self
29
+ end
30
+
31
+ # list of all connections for this application
32
+ # @return [Hash] hash of all connections
33
+ def connections
34
+ @connections ||= {}
35
+ end
36
+
37
+ # add new connection to application
38
+ # @param [Connection] connection connetion to add
39
+ def add_connection(connection)
40
+ self.connections[connection.id] = connection
41
+ end
42
+
43
+ # remove connection from application
44
+ # @param [Connection] connection connection to remove
45
+ def remove_connection(connection)
46
+ self.connections.delete(connection.id)
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,30 @@
1
+ module Socky
2
+ module Server
3
+ class Channel
4
+ ROOT = File.expand_path(File.dirname(__FILE__))
5
+
6
+ autoload :Base, "#{ROOT}/channel/base"
7
+ autoload :Presence, "#{ROOT}/channel/presence"
8
+ autoload :Private, "#{ROOT}/channel/private"
9
+ autoload :Public, "#{ROOT}/channel/public"
10
+ autoload :Stub, "#{ROOT}/channel/stub"
11
+
12
+ # Find or create by application and channel by name
13
+ # @param [String] application_name name of application
14
+ # @param [String] channel_name name of channel
15
+ # @return [Channel::Base] channel instance
16
+ def self.find_or_create(application_name, channel_name)
17
+ return Stub.new(application_name, channel_name) unless (1..30).include?(channel_name.size)
18
+
19
+ channel_type = case channel_name.match(/\A\w+/).to_s
20
+ when 'private' then Private
21
+ when 'presence' then Presence
22
+ else Public
23
+ end
24
+
25
+ channel_type.find_or_create(application_name, channel_name)
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,80 @@
1
+ module Socky
2
+ module Server
3
+ class Channel
4
+ class Base
5
+
6
+ attr_accessor :application, :name
7
+
8
+ class << self
9
+ # List of all already registered channels of current type
10
+ # namespaces by application name
11
+ def list
12
+ @list ||= Hash.new{ |hash, key| hash[key] = Hash.new }
13
+ end
14
+
15
+ # Find channel or create new
16
+ # @param [String] application_name name of application
17
+ # @param [String] channel_name name for channel
18
+ # @return [Base] channel instance
19
+ def find_or_create(application_name, channel_name)
20
+ self.list[application_name][channel_name] ||= self.new(application_name, channel_name)
21
+ end
22
+ end
23
+
24
+ # Initialize new channel
25
+ # @param [String] application_name name of application
26
+ # @param [String] channel_name name for channel
27
+ def initialize(application_name, channel_name)
28
+ @application = Application.find(application_name)
29
+ @name = channel_name
30
+ end
31
+
32
+ def subscribers
33
+ @subscribers ||= {}
34
+ end
35
+
36
+ def send_data(data, except = nil)
37
+ self.subscribers.each do |subscriber_id, subscriber|
38
+ subscriber['connection'].send_data(data) unless subscriber_id == except || !subscriber['read']
39
+ end
40
+ end
41
+
42
+ def add_subscriber(connection, message, subscriber_data = nil)
43
+ self.subscribers[connection.id] = { 'connection' => connection, 'data' => subscriber_data }.merge( rights(message) )
44
+ connection.channels[self.name] = self
45
+ end
46
+
47
+ def remove_subscriber(connection)
48
+ self.subscribers.delete(connection.id)
49
+ connection.channels.delete(self.name)
50
+ end
51
+
52
+ def deliver(connection, message)
53
+ return unless connection.nil? || (subscribers[connection.id] && subscribers[connection.id]['write'])
54
+ send_data({ 'event' => message.event, 'channel' => self.name, 'data' => message.user_data })
55
+ end
56
+
57
+ protected
58
+
59
+ def subscribe_successful(connection, message)
60
+ self.add_subscriber(connection, message)
61
+ connection.send_data('event' => 'socky:subscribe:success', 'channel' => self.name)
62
+ end
63
+
64
+ def subscribe_failed(connection)
65
+ connection.send_data('event' => 'socky:subscribe:failure', 'channel' => self.name)
66
+ end
67
+
68
+ def unsubscribe_successful(connection)
69
+ self.remove_subscriber(connection)
70
+ connection.send_data('event' => 'socky:unsubscribe:success', 'channel' => self.name)
71
+ end
72
+
73
+ def unsubscribe_failed(connection)
74
+ connection.send_data('event' => 'socky:unsubscribe:failure', 'channel' => self.name)
75
+ end
76
+
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,49 @@
1
+ module Socky
2
+ module Server
3
+ class Channel
4
+ class Presence < Private
5
+
6
+ def add_subscriber(connection, message, subscriber_data = nil)
7
+ self.send_data({ 'event' => 'socky:member:added', 'connection_id' => connection.id, 'channel' => self.name, 'data' => subscriber_data }, connection.id) unless rights(message)['hide']
8
+ super
9
+ end
10
+
11
+ def remove_subscriber(connection)
12
+ self.send_data({ 'event' => 'socky:member:removed', 'connection_id' => connection.id, 'channel' => self.name }, connection.id)
13
+ super
14
+ end
15
+
16
+ protected
17
+
18
+ def subscribe_successful(connection, message)
19
+ user_data = JSON.parse(message.user_data) rescue nil
20
+ user_data = {} unless user_data.is_a?(Hash)
21
+
22
+ self.add_subscriber(connection, message, user_data)
23
+ connection.send_data('event' => 'socky:subscribe:success', 'channel' => self.name, 'members' => member_list(connection))
24
+ end
25
+
26
+ def member_list(connection)
27
+ list = []
28
+ self.subscribers.each do |connection_id, member|
29
+ list << { 'connection_id' => connection_id, 'data' => member['data'] } unless connection_id == connection.id
30
+ end
31
+ list
32
+ end
33
+
34
+ def hash_from_message(connection, message)
35
+ hash = super
36
+ hash.merge!('data' => message.user_data)
37
+ hash
38
+ end
39
+
40
+ def rights(message)
41
+ r = super
42
+ r.merge!( 'hide' => !!message.hide ) unless message.hide.nil?
43
+ r
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end