garufa 1.1.0.rc.1 → 1.1.0.rc.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -13,207 +13,15 @@ Garufa is a Ruby websocket server which implements the Pusher protocol. It is
13
13
  built on top of [Goliath][goliath], a high performance non-blocking web server,
14
14
  and inspired by [Slanger][slanger], another server compatible with Pusher.
15
15
 
16
- Install
17
- -------
18
-
19
- Be sure you have a ruby version >= 1.9.2
20
-
21
- ``` console
22
- $ gem install garufa
23
-
24
- $ garufa --help
25
- ```
26
-
27
- Usage
28
- -------
29
-
30
- Start garufa server:
31
-
32
- ``` console
33
- $ garufa -sv --app_id app-id --app_key app-key --secret app-secret
34
- ```
35
-
36
- This will start Garufa, logging to stdout in verbose mode. If you want Garufa
37
- to run in background (daemonized) add `-d` flag.
38
-
39
- Now say you want to send events to your browser. Create an .html file which
40
- requires the *pusher.js* library and binds to some events, then point your
41
- browser to that file (for testing purpose, you can simply open it with your
42
- browser).
43
-
44
- Maybe you would like to open your JavaScript console to see JavaScript debug
45
- messages.
46
-
47
- ``` html
48
- <html>
49
- <script src="http://js.pusher.com/2.1/pusher.min.js"></script>
50
- <script>
51
- Pusher.log = function(message) { console.log(message) };
52
- Pusher.host = 'localhost';
53
- Pusher.ws_port = 8080;
54
-
55
- var appKey = 'app-key';
56
- var pusher = new Pusher(appKey);
57
- var channel = pusher.subscribe('my-channel');
58
-
59
- channel.bind('my-event', function(data) { alert(data.message) });
60
- </script>
61
- </html>
62
- ```
63
-
64
- Now trigger *my-event* from Ruby code. Be sure you have already installed
65
- the *pusher* gem (*gem install pusher*). Open a Ruby console and paste this:
66
-
67
-
68
- ``` ruby
69
- require 'pusher'
70
-
71
- Pusher.host = 'localhost'
72
- Pusher.port = 8080
73
-
74
- Pusher.app_id = 'app-id'
75
- Pusher.key = 'app-key'
76
- Pusher.secret = 'app-secret'
77
-
78
- Pusher.trigger('my-channel', 'my-event', { message: 'hello world' })
79
- ```
80
-
81
- Check your browser to see the event have just arrived.
82
-
83
- SSL support
84
- -----------
85
-
86
- ``` console
87
- $ garufa -sv --app_key app-key --secret app-secret --ssl --ssl-cert /path/to/cert.pem --ssl-key /path/to/cert.key
88
- ```
89
-
90
- **NOTE**: At the moment, Garufa uses the same port for API messages and websocket
91
- connections. This means that if you start the server with SSL enabled, you will
92
- have to enable SSL in the client library as well as in the api client.
93
-
94
- An alternative is to setup Garufa behind a reverse proxy such as Nginx and let the
95
- proxy take care of SSL. See below for more info.
96
-
97
-
98
- Using Nginx as reverse proxy
99
- ----------------------
100
-
101
- You can set Garufa behind Nginx if you want: http://nginx.org/en/docs/http/websocket.html
102
-
103
- Take into account that you will need to set *proxy_read_timeout* to a value a little
104
- higher than Pusher *ACTIVITY_TIMEOUT*, otherwise Nginx will close the connection.
105
-
106
- In addition, you can let Ngnix take care of SSL and start Garufa without SSL enabled.
107
- You could use something like this in your Nginx configuration.
108
-
109
- ```
110
- upstream garufa {
111
- server 127.0.0.1:8000;
112
- }
113
-
114
- map $http_upgrade $connection_upgrade {
115
- default upgrade;
116
- '' close;
117
- }
118
-
119
- server {
120
- listen 8080;
121
- server_name garufa.example.com;
122
-
123
- ; Set this a little higher than Pusher ACTIVITY_TIMEOUT
124
- proxy_read_timeout 150;
125
-
126
- ssl on;
127
- ssl_certificate /path/to/cert.pem;
128
- ssl_certificate_key /path/to/cert.key;
129
-
130
- location / {
131
- proxy_pass http://garufa;
132
- proxy_http_version 1.1;
133
- proxy_set_header Upgrade $http_upgrade;
134
- proxy_set_header Connection $connection_upgrade;
135
- }
136
- }
137
- ```
138
-
139
- Setting up Garufa on Heroku
140
- ---------------------------
141
-
142
- Create a repo for your app:
143
-
144
- ```
145
- $ mkdir garufa-heroku
146
- $ cd garufa-heroku
147
- $ git init .
148
- ```
149
-
150
- Create Gemfile and Procfile files:
151
-
152
- ``` ruby
153
- # Gemfile
154
- source 'http://rubygems.org'
155
- ruby '2.1.2'
156
- gem 'garufa'
157
-
158
- # Procfile
159
- web: garufa -v --app_key $GARUFA_APP_KEY --secret $GARUFA_SECRET -p $PORT
160
- ```
161
-
162
- Generate Gemfile.lock and commit your changes:
163
- ```
164
- $ bundle install
165
- $ git add .
166
- $ git commit -m 'Initial commit'
167
- ```
168
-
169
- Create your app, set environment variables and deploy:
170
-
171
- ```
172
- $ heroku create
173
- $ heroku config:set GARUFA_APP_KEY=app-key
174
- $ heroku config:set GARUFA_SECRET=app-secret
175
- $ git push heroku master
176
- ```
177
-
178
- At this point you should have Garufa up and listening on port 80.
179
-
180
- Set `Pusher.port` and `Pusher.ws_port` in the example above to port 80, and
181
- `Pusher.host` to the name of your app provided by Heroku (something like
182
- `random-name-2323.herokuapp.com`).
183
-
184
- Checking number of current connections
185
- --------------------------------------
186
-
187
- A simple way to check the number of current connections is using `lsof`. Be sure to
188
- set the right path to garufa.pid file.
189
-
190
-
191
- ``` console
192
- while :; do echo "$(date '+%H:%M:%S') $(sudo lsof -p `cat /path/to/garufa.pid` | grep ESTABLISHED | wc -l)"; sleep 1; done;
193
- ```
194
-
195
- Testing and Contributing
196
- ------------------------
197
-
198
- It's up to you how you install Garufa dependencies for development and testing,
199
- just be sure you have installed dependencies listed in the .gemspec. If you want
200
- to let *bundler* handle this, you can generate a Gemfile from the .gemspec and
201
- then run *bundle install*.
202
-
203
-
204
- ``` console
205
- $ bundle init --gemspec=garufa.gemspec
206
- $ bundle install
207
- ```
208
-
209
- Once you have dependencies installed, run *rake test* (or just *rake*).
210
-
211
- ``` console
212
- $ rake
213
- ```
214
-
215
- Pull requests are welcome!
216
-
16
+ Documentation
17
+ -------------
18
+
19
+ * [Installation and usage] (/doc/install.md)
20
+ * [SSL Support] (/doc/ssl.md)
21
+ * [Deploy to Heroku] (/doc/heroku.md)
22
+ * [Using Nginx as reverse proxy] (/doc/nginx.md)
23
+ * [Cheking number of current connections] (/doc/connections.md)
24
+ * [Testing and contributing] (/doc/testing.md)
217
25
 
218
26
  Pusher
219
27
  ------
data/bin/garufa.pid CHANGED
@@ -1 +1 @@
1
- 4987
1
+ 4311
data/garufa.gemspec CHANGED
@@ -29,6 +29,8 @@ Gem::Specification.new do |s|
29
29
  s.add_dependency "faye-websocket", "0.7.4"
30
30
  s.add_dependency "cuba", "3.1.1"
31
31
  s.add_dependency "signature", "0.1.7"
32
+ s.add_dependency "tilt", "2.0.1"
33
+ s.add_dependency "yajl-ruby", "1.2.1"
32
34
  s.add_development_dependency "rake", "10.3.2"
33
35
  s.add_development_dependency "minitest", "5.4.0"
34
36
  end
@@ -0,0 +1,13 @@
1
+ module Garufa
2
+ module API
3
+ module ChannelFilter
4
+ def channel_filter(params)
5
+ {
6
+ info: params['info'].to_s.split(/\s*,\s*/),
7
+ prefix: params['filter_by_prefix']
8
+ }
9
+ end
10
+ end
11
+ end
12
+ end
13
+
@@ -1,19 +1,43 @@
1
+ require 'cuba'
2
+ require 'cuba/render'
3
+
4
+ require 'yajl'
5
+ require 'yajl/json_gem'
6
+ require 'tilt/yajl'
7
+
8
+ require 'garufa/api/channel_filter'
9
+ require 'garufa/api/settings_setter'
10
+
1
11
  module Garufa
2
12
  module API
3
- class Channels < Cuba; end
13
+ class Channels < Cuba
14
+ plugin Cuba::Render
15
+ plugin ChannelFilter
16
+ plugin SettingsSetter
17
+
18
+ set :render, template_engine: 'yajl', views: File.expand_path("views", File.dirname(__FILE__))
19
+ end
4
20
 
5
- # TODO: Implement channels requests
6
21
  Channels.define do
7
22
 
8
- # Channels
9
- on get, "channels" do
23
+ on get, "channels/:channel/users" do |channel|
24
+ stats = Subscriptions.channel_stats(channel)
25
+ res.write partial('users', stats: stats)
10
26
  end
11
27
 
12
- on get, "channels/:channel" do
28
+ on get, "channels/:channel" do |channel|
29
+ filter = channel_filter(req.params)
30
+ stats = Subscriptions.channel_stats(channel)
31
+ res.write partial('channel', stats: stats, filter: filter)
13
32
  end
14
33
 
15
- # Users
16
- on get, "channels/:channel/users" do
34
+ on get, "channels" do
35
+ filter = channel_filter(req.params)
36
+ stats = Subscriptions.all.each_with_object({}) do |(channel, sub), obj|
37
+ obj[channel] = Subscriptions.channel_stats(channel)
38
+ end
39
+
40
+ res.write partial('channels', stats: stats, filter: filter)
17
41
  end
18
42
  end
19
43
  end
@@ -11,7 +11,7 @@ module Garufa
11
11
 
12
12
  # Some old api clients send channel and event in the url, while only data is
13
13
  # in the body. New clients send everything in the body. We have to check where
14
- # data is coming to build the final params.
14
+ # data is coming in to build the final params.
15
15
  params.merge!(body_params['data'] ? body_params : { data: body_params })
16
16
 
17
17
  message = Garufa::Message.new(params)
File without changes
@@ -0,0 +1,15 @@
1
+ module Garufa
2
+ module API
3
+ module SettingsSetter
4
+ module ClassMethods
5
+ def set(key, value)
6
+ if value.is_a? Hash
7
+ settings[key].merge! value
8
+ else
9
+ settings[key] = value
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -54,7 +54,7 @@ module Garufa
54
54
  end
55
55
 
56
56
  def cleanup
57
- @subscriptions.values.each(&:unsubscribe)
57
+ @subscriptions.each { |_, subscription| unsubscribe(subscription) }
58
58
  @subscriptions.clear
59
59
  end
60
60
 
@@ -84,11 +84,16 @@ module Garufa
84
84
 
85
85
  def pusher_subscribe(data)
86
86
  subscription = Subscription.new(data, self)
87
+ subscribe(subscription)
88
+ end
89
+
90
+ def subscribe(subscription)
87
91
  subscription.subscribe
88
92
 
89
93
  if subscription.success?
90
94
  @subscriptions[subscription.channel] = subscription
91
95
  send_subscription_succeeded(subscription)
96
+ notify_member(:member_added, subscription) if subscription.presence_channel?
92
97
  else
93
98
  error(subscription.error.code, subscription.error.message)
94
99
  end
@@ -96,7 +101,12 @@ module Garufa
96
101
 
97
102
  def pusher_unsubscribe(data)
98
103
  subscription = @subscriptions.delete data["channel"]
99
- subscription.unsubscribe if subscription
104
+ unsubscribe(subscription) if subscription
105
+ end
106
+
107
+ def unsubscribe(subscription)
108
+ notify_member(:member_removed, subscription) if subscription.presence_channel?
109
+ subscription.unsubscribe
100
110
  end
101
111
 
102
112
  def valid_app_key?
@@ -108,7 +118,23 @@ module Garufa
108
118
  end
109
119
 
110
120
  def send_subscription_succeeded(subscription)
111
- send_message Message.subscription_succeeded(subscription.channel)
121
+ channel = subscription.channel
122
+ data = {}
123
+
124
+ if subscription.presence_channel?
125
+ data[:presence] = Subscriptions.channels_data(channel)
126
+ data[:presence][:count] = Subscriptions.channel_size(channel)
127
+ end
128
+
129
+ send_message Message.subscription_succeeded(channel, data)
130
+ end
131
+
132
+ def notify_member(event, subscription)
133
+ options = {
134
+ data: subscription.channel_data,
135
+ socket_id: subscription.socket_id
136
+ }
137
+ Subscriptions.notify [subscription.channel], "pusher_internal:#{event}", options
112
138
  end
113
139
  end
114
140
  end
@@ -1,9 +1,10 @@
1
1
  require 'goliath/api'
2
2
  require 'goliath/connection'
3
3
  require 'faye/websocket'
4
+
4
5
  require 'garufa/config'
5
6
  require 'garufa/websocket'
6
- require 'garufa/api'
7
+ require 'garufa/api/server'
7
8
  require 'garufa/version'
8
9
 
9
10
  module Garufa
@@ -20,6 +21,8 @@ module Garufa
20
21
 
21
22
  options[:port] = DEFAULT_PORT
22
23
 
24
+ opts.on('-V', '--version', 'Display version and exit') { puts "Garufa version #{Garufa::VERSION}"; exit }
25
+
23
26
  opts.separator ""
24
27
  opts.separator "Pusher options:"
25
28
 
@@ -32,8 +35,6 @@ module Garufa
32
35
  opts.on(v.first, v.last) { |value| Garufa::Config[k] = value }
33
36
  end
34
37
 
35
- opts.separator ""
36
- opts.on('-V', '--version', 'Display version and exit') { puts "Garufa version #{Garufa::VERSION}"; exit }
37
38
  end
38
39
 
39
40
  def response(env)
@@ -31,8 +31,8 @@ module Garufa
31
31
  new(event: 'pusher:connection_established', data: data)
32
32
  end
33
33
 
34
- def self.subscription_succeeded(channel)
35
- new(event: 'pusher_internal:subscription_succeeded', channel: channel, data: {})
34
+ def self.subscription_succeeded(channel, data = {})
35
+ new(event: 'pusher_internal:subscription_succeeded', channel: channel, data: data)
36
36
  end
37
37
 
38
38
  def self.pong
@@ -53,6 +53,10 @@ module Garufa
53
53
  @data['channel']
54
54
  end
55
55
 
56
+ def channel_data
57
+ @data['channel_data']
58
+ end
59
+
56
60
  def channel_prefix
57
61
  channel[/^private-|presence-/].to_s[0...-1]
58
62
  end
@@ -76,7 +80,7 @@ module Garufa
76
80
  end
77
81
 
78
82
  def valid_signature?
79
- string_to_sign = [@connection.socket_id, channel].compact.join(':')
83
+ string_to_sign = [@connection.socket_id, channel, channel_data].compact.join(':')
80
84
  token(string_to_sign) == signature
81
85
  end
82
86
 
@@ -10,31 +10,67 @@ module Garufa
10
10
  end
11
11
 
12
12
  def add(subscription)
13
- subscriptions[subscription.channel].add subscription
13
+ subs = subscriptions[subscription.channel] ||= Set.new
14
+ subs.add subscription
14
15
  end
15
16
 
16
17
  def remove(subscription)
17
- subscriptions[subscription.channel].delete subscription
18
+ channel = subscription.channel
19
+ subscriptions[channel].delete subscription
20
+ subscriptions.delete(channel) if channel_size(channel) == 0
18
21
  end
19
22
 
20
23
  def notify(channels, event, options = {})
21
24
  channels.each do |channel|
22
25
  subscriptions[channel].each do |sub|
26
+
23
27
  # Skip notifying if the same socket_id is provided
24
28
  next if sub.socket_id == options[:socket_id]
29
+
30
+ # Skip notifying the same member (probably from different tabs)
31
+ next if sub.presence_channel? and sub.channel_data == options[:data]
32
+
25
33
  sub.notify Message.channel_event(channel, event, options[:data])
26
34
  end
27
35
  end
28
36
  end
29
37
 
30
38
  def include?(subscription)
31
- subscriptions[subscription.channel].include? subscription
39
+ subs = subscriptions[subscription.channel]
40
+ subs && subs.include?(subscription)
41
+ end
42
+
43
+ def channel_size(channel)
44
+ subs = subscriptions[channel]
45
+ subs ? subs.size : 0
46
+ end
47
+
48
+ def channels_data(channel)
49
+ response = { ids: [], hash: {} }
50
+
51
+ (subscriptions[channel] || []).each do |sub|
52
+ channel_data = JSON.parse(sub.channel_data)
53
+ id, info = channel_data.values_at('user_id', 'user_info')
54
+
55
+ next if response[:ids].include? id
56
+
57
+ response[:ids] << id
58
+ response[:hash][id] = info
59
+ end
60
+ response
61
+ end
62
+
63
+ def channel_stats(channel)
64
+ {
65
+ size: channel_size(channel),
66
+ presence: channels_data(channel)
67
+ }
32
68
  end
33
69
 
34
70
  private
35
71
 
36
72
  def subscriptions
37
- @subscriptions ||= Hash.new { |h, k| h[k] = Set.new }
73
+ @subscriptions ||= {}
38
74
  end
39
75
  end
40
76
  end
@@ -1,3 +1,3 @@
1
1
  module Garufa
2
- VERSION = '1.1.0.rc.1'
2
+ VERSION = '1.1.0.rc.2'
3
3
  end
data/test/connection.rb CHANGED
@@ -29,10 +29,10 @@ module Garufa
29
29
  let(:data) { { event: 'pusher:subscribe', data: { channel: channel } } }
30
30
 
31
31
  it 'should add a new Subscription to Subscriptions' do
32
- count = Subscriptions.all[channel].count
32
+ count = Subscriptions.channel_size(channel)
33
33
  @socket.expect :send, true, [String]
34
34
  @connection.handle_incomming_data data.to_json
35
- Subscriptions.all[channel].count.must_equal count + 1
35
+ Subscriptions.channel_size(channel).must_equal count + 1
36
36
  end
37
37
 
38
38
  describe 'public channels' do
@@ -96,9 +96,9 @@ module Garufa
96
96
  end
97
97
 
98
98
  it 'should remove Subscription from Subscriptions' do
99
- count = Subscriptions.all[channel].count
99
+ count = Subscriptions.channel_size(channel)
100
100
  @connection.handle_incomming_data data_unsubscribe.to_json
101
- Subscriptions.all[channel].count.must_equal count - 1
101
+ Subscriptions.channel_size(channel).must_equal count - 1
102
102
  end
103
103
 
104
104
  end
@@ -14,9 +14,9 @@ module Garufa
14
14
  end
15
15
 
16
16
  it 'should remove subscription to channel' do
17
- count = Subscriptions.all[channel].count
17
+ count = Subscriptions.channel_size(channel)
18
18
  Subscriptions.remove @subscription
19
- Subscriptions.all[channel].count.must_equal count - 1
19
+ Subscriptions.channel_size(channel).must_equal count - 1
20
20
  end
21
21
  end
22
22
 
@@ -29,9 +29,9 @@ module Garufa
29
29
  end
30
30
 
31
31
  it 'should add a new subscription to channel' do
32
- count = Subscriptions.all[channel].count
32
+ count = Subscriptions.channel_size(channel)
33
33
  Subscriptions.add Subscription.new(data, @connection)
34
- Subscriptions.all[channel].count.must_equal count + 1
34
+ Subscriptions.channel_size(channel).must_equal count + 1
35
35
  end
36
36
  end
37
37
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: garufa
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0.rc.1
4
+ version: 1.1.0.rc.2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-01-15 00:00:00.000000000 Z
12
+ date: 2015-02-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: goliath
@@ -75,6 +75,38 @@ dependencies:
75
75
  - - '='
76
76
  - !ruby/object:Gem::Version
77
77
  version: 0.1.7
78
+ - !ruby/object:Gem::Dependency
79
+ name: tilt
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - '='
84
+ - !ruby/object:Gem::Version
85
+ version: 2.0.1
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - '='
92
+ - !ruby/object:Gem::Version
93
+ version: 2.0.1
94
+ - !ruby/object:Gem::Dependency
95
+ name: yajl-ruby
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - '='
100
+ - !ruby/object:Gem::Version
101
+ version: 1.2.1
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - '='
108
+ - !ruby/object:Gem::Version
109
+ version: 1.2.1
78
110
  - !ruby/object:Gem::Dependency
79
111
  name: rake
80
112
  requirement: !ruby/object:Gem::Requirement
@@ -119,11 +151,13 @@ files:
119
151
  - README.md
120
152
  - Rakefile
121
153
  - lib/garufa/version.rb
122
- - lib/garufa/api.rb
123
154
  - lib/garufa/api/channels.rb
124
155
  - lib/garufa/api/event_handler.rb
156
+ - lib/garufa/api/server.rb
125
157
  - lib/garufa/api/authentication.rb
158
+ - lib/garufa/api/channel_filter.rb
126
159
  - lib/garufa/api/events.rb
160
+ - lib/garufa/api/settings_setter.rb
127
161
  - lib/garufa/subscription.rb
128
162
  - lib/garufa/message.rb
129
163
  - lib/garufa/config.rb