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.
- data/.gitignore +0 -4
- data/.travis.yml +6 -0
- data/CHANGELOG.md +11 -5
- data/Gemfile +2 -0
- data/README.md +47 -68
- data/Rakefile +5 -7
- data/config.ru +19 -0
- data/example/config.yml +4 -0
- data/lib/socky/server.rb +23 -0
- data/lib/socky/server/application.rb +51 -0
- data/lib/socky/server/channel.rb +30 -0
- data/lib/socky/server/channel/base.rb +80 -0
- data/lib/socky/server/channel/presence.rb +49 -0
- data/lib/socky/server/channel/private.rb +44 -0
- data/lib/socky/server/channel/public.rb +43 -0
- data/lib/socky/server/channel/stub.rb +17 -0
- data/lib/socky/server/config.rb +52 -0
- data/lib/socky/server/connection.rb +66 -0
- data/lib/socky/server/http.rb +95 -0
- data/lib/socky/server/logger.rb +24 -0
- data/lib/socky/server/message.rb +35 -0
- data/lib/socky/server/misc.rb +18 -0
- data/lib/socky/server/version.rb +5 -0
- data/lib/socky/server/websocket.rb +43 -0
- data/socky-server.gemspec +5 -7
- data/spec/fixtures/example_config.yml +3 -0
- data/spec/integration/ws_channels_spec.rb +144 -0
- data/spec/integration/ws_connection_spec.rb +48 -0
- data/spec/integration/ws_presence_spec.rb +118 -0
- data/spec/integration/ws_rights_spec.rb +133 -0
- data/spec/spec_helper.rb +24 -2
- data/spec/support/websocket_application.rb +14 -0
- data/spec/unit/socky/server/application_spec.rb +54 -0
- data/spec/unit/socky/server/config_spec.rb +50 -0
- data/spec/unit/socky/server/connection_spec.rb +67 -0
- data/spec/unit/socky/server/message_spec.rb +64 -0
- metadata +93 -126
- data/bin/socky +0 -5
- data/lib/em-websocket_hacks.rb +0 -15
- data/lib/socky.rb +0 -75
- data/lib/socky/connection.rb +0 -137
- data/lib/socky/connection/authentication.rb +0 -99
- data/lib/socky/connection/finders.rb +0 -67
- data/lib/socky/message.rb +0 -85
- data/lib/socky/misc.rb +0 -74
- data/lib/socky/net_request.rb +0 -27
- data/lib/socky/options.rb +0 -39
- data/lib/socky/options/config.rb +0 -79
- data/lib/socky/options/parser.rb +0 -93
- data/lib/socky/runner.rb +0 -95
- data/spec/em-websocket_spec.rb +0 -36
- data/spec/files/default.yml +0 -18
- data/spec/files/invalid.yml +0 -1
- data/spec/socky/connection/authentication_spec.rb +0 -183
- data/spec/socky/connection/finders_spec.rb +0 -188
- data/spec/socky/connection_spec.rb +0 -151
- data/spec/socky/message_spec.rb +0 -102
- data/spec/socky/misc_spec.rb +0 -74
- data/spec/socky/net_request_spec.rb +0 -42
- data/spec/socky/options/config_spec.rb +0 -72
- data/spec/socky/options/parser_spec.rb +0 -76
- data/spec/socky/options_spec.rb +0 -60
- data/spec/socky/runner_spec.rb +0 -88
- data/spec/socky_spec.rb +0 -89
- data/spec/support/stallion.rb +0 -96
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## 0.
|
3
|
+
## 0.5.0.beta1 / 2011-08-01
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
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
|
-
|
3
|
+
## Installation
|
5
4
|
|
6
|
-
|
5
|
+
$ gem install socky-server --pre
|
7
6
|
|
8
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
29
|
-
rake build
|
21
|
+
## Configuration
|
30
22
|
|
31
|
-
|
23
|
+
Both middlewares accept options as hash. Currently available options are:
|
32
24
|
|
33
|
-
|
34
|
-
- EM-HTTP-Client: Sending authorize requests
|
25
|
+
### :applications [Hash]
|
35
26
|
|
36
|
-
|
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
|
-
|
29
|
+
http://example.org/websocket/my_app
|
39
30
|
|
40
|
-
|
31
|
+
### :debug [Boolean]
|
41
32
|
|
42
|
-
|
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
|
-
|
35
|
+
### :config [String]
|
45
36
|
|
46
|
-
|
37
|
+
Path to YAML config file. Config file should contain hash with exactly the same syntax like normal options.
|
47
38
|
|
48
|
-
|
49
|
-
|
50
|
-
## Configuration
|
39
|
+
## Example configuration
|
51
40
|
|
52
|
-
|
41
|
+
Create file 'config.ru':
|
53
42
|
|
54
|
-
|
43
|
+
require 'socky/server'
|
55
44
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
53
|
+
map '/websocket' do
|
54
|
+
run Socky::Server::WebSocket.new options
|
55
|
+
end
|
75
56
|
|
76
|
-
|
77
|
-
|
57
|
+
map '/http' do
|
58
|
+
use Rack::CommonLogger
|
59
|
+
run Socky::Server::HTTP.new options
|
60
|
+
end
|
78
61
|
|
79
|
-
|
80
|
-
:unsubscribe_url: http://localhost:3000/socky/unsubscribe
|
62
|
+
Run file using Thin:
|
81
63
|
|
82
|
-
|
64
|
+
$ thin -R config.ru -p3001 start
|
83
65
|
|
84
|
-
|
66
|
+
## Setting other options
|
85
67
|
|
86
|
-
|
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
|
-
|
89
|
-
# :pid_path: /var/run/socky.pid
|
70
|
+
## Which Rack servers are currently supported?
|
90
71
|
|
91
|
-
|
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)
|
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
|
-
|
11
|
-
|
12
|
-
|
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
|
data/config.ru
ADDED
@@ -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
|
data/example/config.yml
ADDED
data/lib/socky/server.rb
ADDED
@@ -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
|