padrino-websockets 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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +89 -0
- data/Rakefile +1 -0
- data/examples/padrino-app/.components +9 -0
- data/examples/padrino-app/.gitignore +9 -0
- data/examples/padrino-app/Gemfile +34 -0
- data/examples/padrino-app/Rakefile +4 -0
- data/examples/padrino-app/app/app.rb +19 -0
- data/examples/padrino-app/app/views/index.slim +19 -0
- data/examples/padrino-app/config/apps.rb +36 -0
- data/examples/padrino-app/config/boot.rb +46 -0
- data/examples/padrino-app/config.ru +9 -0
- data/examples/padrino-app/public/favicon.ico +0 -0
- data/lib/padrino-websockets/base-event-manager.rb +187 -0
- data/lib/padrino-websockets/spider-gazelle/event-manager.rb +50 -0
- data/lib/padrino-websockets/spider-gazelle/helpers.rb +14 -0
- data/lib/padrino-websockets/spider-gazelle/routing.rb +36 -0
- data/lib/padrino-websockets/spider-gazelle.rb +9 -0
- data/lib/padrino-websockets/version.rb +5 -0
- data/lib/padrino-websockets.rb +31 -0
- data/padrino-websockets.gemspec +24 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 29fa2c66e13018259a69e4c31ae06bd65c6e636b
|
4
|
+
data.tar.gz: 65df06207d203791552bf15743e380a7236dd110
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ad15ee0928289ec7ddb10c7d2f079ef42de8d43ac2935d8855d9e52c0e539d5ce31b4538037892020d1861a4c9c625e6778249211ba7199017c208fdcb5a978c
|
7
|
+
data.tar.gz: 7846d58a63850da913458687f0d2535d529d83b57ca39a986df9a5ed4325c4cd2549fcdf9d1cdef9080c4d878bd3c3c1ef6d5460741671dabae3ad663e032fa4
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Darío Javier Cravero (dario@uxtemple.com)
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# Padrino Websockets
|
2
|
+
|
3
|
+
A WebSockets abstraction for the Padrino Ruby Web Framework to manage
|
4
|
+
channels, users and events regardless of the underlying WebSockets implementation.
|
5
|
+
|
6
|
+
## Current support
|
7
|
+
|
8
|
+
The current version supports [SpiderGazelle](https://github.com/cotag/spider-gazelle) as its
|
9
|
+
backend working with LibUV.
|
10
|
+
|
11
|
+
Feel free to implement your own backend using, say, EventMachine and submit it! :)
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's `Gemfile`:
|
16
|
+
|
17
|
+
```
|
18
|
+
# It only works with SpiderGazelle for now, so this dependency is a must at the moment.
|
19
|
+
gem 'spider-gazelle', github: 'cotag/spider-gazelle'
|
20
|
+
gem 'uv-rays', github: 'cotag/uv-rays'
|
21
|
+
|
22
|
+
gem 'padrino-websockets'
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
```
|
28
|
+
$ bundle
|
29
|
+
```
|
30
|
+
|
31
|
+
Or install it yourself as:
|
32
|
+
|
33
|
+
```
|
34
|
+
$ gem install padrino-websockets
|
35
|
+
```
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
Add this line to your main application's file (`app/app.rb`):
|
40
|
+
|
41
|
+
```
|
42
|
+
register Padrino::WebSockets
|
43
|
+
```
|
44
|
+
|
45
|
+
Then in any controller or in the app itself, define a WebSocket channel:
|
46
|
+
|
47
|
+
```
|
48
|
+
websocket :channel do
|
49
|
+
event :ping do |context, message|
|
50
|
+
send_message({pong: true, data: message})
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
How to use it on a browser?
|
56
|
+
|
57
|
+
```
|
58
|
+
var connection = new WebSocket('ws://localhost:3000/channel');
|
59
|
+
|
60
|
+
connection.onopen = function(message) {
|
61
|
+
console.log('connected to channel');
|
62
|
+
connection.send(JSON.stringify({event: 'ping', some: 'data'}));
|
63
|
+
}
|
64
|
+
|
65
|
+
connection.onmessage = function(message) {
|
66
|
+
console.log('message', JSON.parse(message.data));
|
67
|
+
}
|
68
|
+
|
69
|
+
// TODO Implement on the backend
|
70
|
+
connection.onerror = function(message) {
|
71
|
+
console.error('channel', JSON.parse(message.data));
|
72
|
+
}
|
73
|
+
|
74
|
+
```
|
75
|
+
|
76
|
+
## Contributing
|
77
|
+
|
78
|
+
1. Fork it ( http://github.com/<my-github-username>/padrino-websockets/fork )
|
79
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
80
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
81
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
82
|
+
5. Create new Pull Request
|
83
|
+
|
84
|
+
|
85
|
+
## Contributors
|
86
|
+
|
87
|
+
Made with <3 by @dariocravero at [UXtemple](http://uxtemple.com).
|
88
|
+
|
89
|
+
Heavily inspired by @stakach's [example](https://github.com/cotag/spider-gazelle/issues/4).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Distribute your app as a gem
|
4
|
+
# gemspec
|
5
|
+
|
6
|
+
# Server requirements
|
7
|
+
# gem 'thin' # or mongrel
|
8
|
+
# gem 'trinidad', :platform => 'jruby'
|
9
|
+
gem 'spider-gazelle', github: 'cotag/spider-gazelle'
|
10
|
+
|
11
|
+
# Optional JSON codec (faster performance)
|
12
|
+
gem 'oj'
|
13
|
+
|
14
|
+
# Project requirements
|
15
|
+
gem 'rake'
|
16
|
+
gem 'uv-rays', github: 'cotag/uv-rays'
|
17
|
+
gem 'padrino-websockets', path: '../..'
|
18
|
+
# gem 'padrino-websockets', github: 'dariocravero/padrino-websockets'
|
19
|
+
|
20
|
+
# Component requirements
|
21
|
+
gem 'slim'
|
22
|
+
|
23
|
+
# Test requirements
|
24
|
+
|
25
|
+
# Padrino Stable Gem
|
26
|
+
# gem 'padrino', '0.12.0.rc3'
|
27
|
+
|
28
|
+
# Or Padrino Edge
|
29
|
+
gem 'padrino', :github => 'padrino/padrino-framework'
|
30
|
+
|
31
|
+
# Or Individual Gems
|
32
|
+
# %w(core gen helpers cache mailer admin).each do |g|
|
33
|
+
# gem 'padrino-' + g, '0.12.0.rc3'
|
34
|
+
# end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module PadrinoWebsocketsDemo
|
2
|
+
class App < Padrino::Application
|
3
|
+
register Padrino::Rendering
|
4
|
+
register Padrino::WebSockets
|
5
|
+
|
6
|
+
enable :sessions
|
7
|
+
|
8
|
+
get :index do
|
9
|
+
render :index
|
10
|
+
end
|
11
|
+
|
12
|
+
websocket :channel do
|
13
|
+
event :test do |context, message|
|
14
|
+
"test on channel"
|
15
|
+
send_message message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
h1 Padrino WebSockets demo
|
2
|
+
|
3
|
+
p Watch out your developer's console. There's a <strong>connection</strong> variable ready to use.
|
4
|
+
|
5
|
+
javascript:
|
6
|
+
!function(window) {
|
7
|
+
'use strict';
|
8
|
+
|
9
|
+
window.connection = new WebSocket('ws://localhost:3000/channel');
|
10
|
+
|
11
|
+
connection.onopen = function(message) {
|
12
|
+
console.log('connected to channel');
|
13
|
+
connection.send(JSON.stringify({event: 'test', some: 'data'}));
|
14
|
+
}
|
15
|
+
|
16
|
+
connection.onmessage = function(message) {
|
17
|
+
console.log('message', JSON.parse(message.data));
|
18
|
+
}
|
19
|
+
}(window);
|
@@ -0,0 +1,36 @@
|
|
1
|
+
##
|
2
|
+
# This file mounts each app in the Padrino project to a specified sub-uri.
|
3
|
+
# You can mount additional applications using any of these commands below:
|
4
|
+
#
|
5
|
+
# Padrino.mount('blog').to('/blog')
|
6
|
+
# Padrino.mount('blog', :app_class => 'BlogApp').to('/blog')
|
7
|
+
# Padrino.mount('blog', :app_file => 'path/to/blog/app.rb').to('/blog')
|
8
|
+
#
|
9
|
+
# You can also map apps to a specified host:
|
10
|
+
#
|
11
|
+
# Padrino.mount('Admin').host('admin.example.org')
|
12
|
+
# Padrino.mount('WebSite').host(/.*\.?example.org/)
|
13
|
+
# Padrino.mount('Foo').to('/foo').host('bar.example.org')
|
14
|
+
#
|
15
|
+
# Note 1: Mounted apps (by default) should be placed into the project root at '/app_name'.
|
16
|
+
# Note 2: If you use the host matching remember to respect the order of the rules.
|
17
|
+
#
|
18
|
+
# By default, this file mounts the primary app which was generated with this project.
|
19
|
+
# However, the mounted app can be modified as needed:
|
20
|
+
#
|
21
|
+
# Padrino.mount('AppName', :app_file => 'path/to/file', :app_class => 'BlogApp').to('/')
|
22
|
+
#
|
23
|
+
|
24
|
+
##
|
25
|
+
# Setup global project settings for your apps. These settings are inherited by every subapp. You can
|
26
|
+
# override these settings in the subapps as needed.
|
27
|
+
#
|
28
|
+
Padrino.configure_apps do
|
29
|
+
# enable :sessions
|
30
|
+
set :session_secret, '439d7a65623d358416f31a49fadefb9f449b6c5f472c887166c04365f2fe6534'
|
31
|
+
set :protection, :except => :path_traversal
|
32
|
+
set :protect_from_csrf, true
|
33
|
+
end
|
34
|
+
|
35
|
+
# Mounts the core application for this project
|
36
|
+
Padrino.mount('PadrinoWebsocketsDemo::App', :app_file => Padrino.root('app/app.rb')).to('/')
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Defines our constants
|
2
|
+
RACK_ENV = ENV['RACK_ENV'] ||= 'development' unless defined?(RACK_ENV)
|
3
|
+
PADRINO_ROOT = File.expand_path('../..', __FILE__) unless defined?(PADRINO_ROOT)
|
4
|
+
|
5
|
+
# Load our dependencies
|
6
|
+
require 'rubygems' unless defined?(Gem)
|
7
|
+
require 'bundler/setup'
|
8
|
+
Bundler.require(:default, RACK_ENV)
|
9
|
+
|
10
|
+
##
|
11
|
+
# ## Enable devel logging
|
12
|
+
#
|
13
|
+
# Padrino::Logger::Config[:development][:log_level] = :devel
|
14
|
+
# Padrino::Logger::Config[:development][:log_static] = true
|
15
|
+
#
|
16
|
+
# ## Configure your I18n
|
17
|
+
#
|
18
|
+
# I18n.default_locale = :en
|
19
|
+
# I18n.enforce_available_locales = false
|
20
|
+
#
|
21
|
+
# ## Configure your HTML5 data helpers
|
22
|
+
#
|
23
|
+
# Padrino::Helpers::TagHelpers::DATA_ATTRIBUTES.push(:dialog)
|
24
|
+
# text_field :foo, :dialog => true
|
25
|
+
# Generates: <input type="text" data-dialog="true" name="foo" />
|
26
|
+
#
|
27
|
+
# ## Add helpers to mailer
|
28
|
+
#
|
29
|
+
# Mail::Message.class_eval do
|
30
|
+
# include Padrino::Helpers::NumberHelpers
|
31
|
+
# include Padrino::Helpers::TranslationHelpers
|
32
|
+
# end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Add your before (RE)load hooks here
|
36
|
+
#
|
37
|
+
Padrino.before_load do
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Add your after (RE)load hooks here
|
42
|
+
#
|
43
|
+
Padrino.after_load do
|
44
|
+
end
|
45
|
+
|
46
|
+
Padrino.load!
|
Binary file
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module Padrino
|
2
|
+
module WebSockets
|
3
|
+
class BaseEventManager
|
4
|
+
require 'oj'
|
5
|
+
|
6
|
+
ERRORS = {
|
7
|
+
parse_message: "Can't parse the WebSocket's message",
|
8
|
+
message_format: "Wrong message format. Expected: {event: 'ev', p1: 'p1', ...}",
|
9
|
+
unsupported_event: "The event you requested isn't supported",
|
10
|
+
runtime: "Error while running the event handler"
|
11
|
+
}
|
12
|
+
|
13
|
+
##
|
14
|
+
# Creates a new WebSocket manager for a specific connection with a user on a channel.
|
15
|
+
#
|
16
|
+
# It will listen for specific events and run their blocks whenever a WebSocket call on
|
17
|
+
# that channel comes through.
|
18
|
+
#
|
19
|
+
# == Params
|
20
|
+
# * `channel`: The name of the channel
|
21
|
+
# * `user`: Unique string to ID the user. See the `set_websocket_user` helper.
|
22
|
+
# * `ws`: The WebSocket promise/reactor/etc - whatever keeps it alive for this user.
|
23
|
+
# * `event_context`: The Padrino application controller's context so that it can access the
|
24
|
+
# helpers, mailers, settings, etc. Making it a first-class citizen as a regular HTTP
|
25
|
+
# action is. TODO Review it though, it's probably wrong.
|
26
|
+
# * `&block`: A block with supported events to manage.
|
27
|
+
#
|
28
|
+
def initialize(channel, user, ws, event_context, &block)
|
29
|
+
@channel = channel
|
30
|
+
@user = user
|
31
|
+
@ws = ws
|
32
|
+
@@connections ||= {}
|
33
|
+
@@connections[@channel] ||= {}
|
34
|
+
@@connections[@channel][@user] = @ws
|
35
|
+
|
36
|
+
@events = {}
|
37
|
+
|
38
|
+
@event_context = event_context
|
39
|
+
|
40
|
+
instance_eval &block if block_given?
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# DSL for adding events from the events block
|
45
|
+
#
|
46
|
+
def event(name, &block)
|
47
|
+
@events[name.to_sym] = block if block_given?
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Message receiver
|
52
|
+
#
|
53
|
+
def on_message(data, ws)
|
54
|
+
# TODO Detect external ws hijack and drop it
|
55
|
+
begin
|
56
|
+
# Parse the message
|
57
|
+
message = ::Oj.load data
|
58
|
+
|
59
|
+
# Check if we have well formed message, i.e., it includes at least an event name.
|
60
|
+
event = message.delete 'event'
|
61
|
+
|
62
|
+
if event.nil?
|
63
|
+
logger.error ERRORS[:message_format]
|
64
|
+
logger.error e.message
|
65
|
+
logger.error e.backtrace.join("\n")
|
66
|
+
|
67
|
+
return send_message({error: {
|
68
|
+
name: :message_format,
|
69
|
+
message: ERRORS[:message_format]
|
70
|
+
}})
|
71
|
+
end
|
72
|
+
|
73
|
+
event = event.to_sym
|
74
|
+
|
75
|
+
# Check if it's a valid event
|
76
|
+
unless @events.include?(event)
|
77
|
+
logger.error ERRORS[:unsupported_event]
|
78
|
+
logger.error e.message
|
79
|
+
logger.error e.backtrace.join("\n")
|
80
|
+
|
81
|
+
return send_message({error: {
|
82
|
+
name: :unsupported_event,
|
83
|
+
message: ERRORS[:unsupported_event],
|
84
|
+
event: event
|
85
|
+
}})
|
86
|
+
end
|
87
|
+
|
88
|
+
# Call it
|
89
|
+
# TODO Run the event in the context of the app
|
90
|
+
# @events[event].bind(@event_context).call
|
91
|
+
# TODO Make the params (message) available through the params variable as we do
|
92
|
+
# in normal actions.
|
93
|
+
logger.debug "Calling event: #{event} as user: #{@user} on channel #{@channel}."
|
94
|
+
logger.debug message.inspect
|
95
|
+
@events[event].call(@event_context, message)
|
96
|
+
rescue Oj::ParseError => e
|
97
|
+
logger.error ERRORS[:parse_message]
|
98
|
+
logger.error e.message
|
99
|
+
logger.error e.backtrace.join("\n")
|
100
|
+
|
101
|
+
send_message({error: {
|
102
|
+
name: :parse_message,
|
103
|
+
message: ERRORS[:parse_message]
|
104
|
+
}})
|
105
|
+
rescue => e
|
106
|
+
logger.error ERRORS[:runtime]
|
107
|
+
logger.error e.message
|
108
|
+
logger.error e.backtrace.join("\n")
|
109
|
+
|
110
|
+
send_message({error: {
|
111
|
+
name: :runtime,
|
112
|
+
message: ERRORS[:runtime],
|
113
|
+
event: event,
|
114
|
+
exception: {
|
115
|
+
message: e.message,
|
116
|
+
backtrace: e.backtrace
|
117
|
+
}
|
118
|
+
}})
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Manage the WebSocket's connection being closed.
|
124
|
+
#
|
125
|
+
def on_shutdown
|
126
|
+
logger.debug "Disconnecting user: #{@user} from channel: #{@channel}."
|
127
|
+
@@connections[@channel].delete(@ws)
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Broadcast a message to the whole channel
|
132
|
+
#
|
133
|
+
def broadcast(message, except=[])
|
134
|
+
self.class.broadcast @channel, message, except
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Send a message to a user on the channel
|
139
|
+
#
|
140
|
+
def send_message(message, user=@user)
|
141
|
+
self.class.send_message @channel, user, message
|
142
|
+
end
|
143
|
+
|
144
|
+
class << self
|
145
|
+
##
|
146
|
+
# Broadcast a message to the whole channel.
|
147
|
+
# Can be used to access it outside the router's scope, for instance, in a background process.
|
148
|
+
#
|
149
|
+
def broadcast(channel, message, except=[])
|
150
|
+
logger.debug "Broadcasting message on channel: #{channel}. Message:"
|
151
|
+
logger.debug message
|
152
|
+
@@connections[channel].each do |user, ws|
|
153
|
+
next if except.include?(user)
|
154
|
+
write message, ws
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# Send a message to a user on the channel
|
160
|
+
# Can be used to access it outside the router's scope, for instance, in a background process.
|
161
|
+
#
|
162
|
+
def send_message(channel, user, message)
|
163
|
+
logger.debug "Sending message: #{message} to user: #{user} on channel: #{channel}. Message"
|
164
|
+
write message, @@connections[channel][user]
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Write a message to the WebSocket.
|
169
|
+
#
|
170
|
+
# It's a wrapper around the different WS implementations.
|
171
|
+
# This has to be implemented for each backend.
|
172
|
+
def write(message, ws)
|
173
|
+
logger.error "Override the write method on the WebSocket-specific backend."
|
174
|
+
raise NotImplementedError
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
protected
|
179
|
+
##
|
180
|
+
# Maintain the connection if ping frames are supported
|
181
|
+
#
|
182
|
+
def on_open(event)
|
183
|
+
logger.debug "Connection openned as user: #{@user} on channel: #{@channel}."
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Padrino
|
2
|
+
module WebSockets
|
3
|
+
module SpiderGazelle
|
4
|
+
class EventManager < BaseEventManager
|
5
|
+
def initialize(channel, user, ws, event_context, &block)
|
6
|
+
ws.progress method(:on_message)
|
7
|
+
ws.finally method(:on_shutdown)
|
8
|
+
ws.on_open method(:on_open)
|
9
|
+
|
10
|
+
super channel, user, ws, event_context, &block
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Manage the WebSocket's connection being closed.
|
15
|
+
#
|
16
|
+
def on_shutdown
|
17
|
+
@pinger.cancel if @pinger
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Write a message to the WebSocket.
|
23
|
+
#
|
24
|
+
def self.write(message, ws)
|
25
|
+
ws.text ::Oj.dump(message)
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
##
|
30
|
+
# Maintain the connection if ping frames are supported
|
31
|
+
#
|
32
|
+
def on_open(event)
|
33
|
+
super event
|
34
|
+
|
35
|
+
if @ws.ping('pong')
|
36
|
+
variation = 1 + rand(20000)
|
37
|
+
@pinger = @ws.loop.scheduler.every 40000 + variation, method(:do_ping)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Ping the WebSocket connection
|
43
|
+
#
|
44
|
+
def do_ping(time1, time2)
|
45
|
+
@ws.ping 'pong'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Padrino
|
2
|
+
module WebSockets
|
3
|
+
module SpiderGazelle
|
4
|
+
module Helpers
|
5
|
+
def send_message(channel, user, message)
|
6
|
+
Padrino::WebSockets::SpiderGazelle::EventManager.send_message channel, user, message
|
7
|
+
end
|
8
|
+
def broadcast(channel, message)
|
9
|
+
Padrino::WebSockets::SpiderGazelle::EventManager.broadcast channel, message
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Padrino
|
2
|
+
module WebSockets
|
3
|
+
module SpiderGazelle
|
4
|
+
module Routing
|
5
|
+
require 'spider-gazelle/upgrades/websocket'
|
6
|
+
|
7
|
+
##
|
8
|
+
# Creates a WebSocket endpoint using SpiderGazelle + libuv.
|
9
|
+
#
|
10
|
+
# It handles upgrading the HTTP connection for you.
|
11
|
+
# You can nest this inside controllers as you would do with regular actions in Padrino.
|
12
|
+
#
|
13
|
+
def websocket(channel, *args, &block)
|
14
|
+
get channel, *args do
|
15
|
+
# Let some other action try to handle the request if it's not a WebSocket.
|
16
|
+
throw :pass unless request.env['rack.hijack']
|
17
|
+
|
18
|
+
event_context = self
|
19
|
+
|
20
|
+
# It's a WebSocket. Get the libuv promise and manage its events
|
21
|
+
request.env['rack.hijack'].call.then do |hijacked|
|
22
|
+
ws = ::SpiderGazelle::Websocket.new hijacked.socket, hijacked.env
|
23
|
+
|
24
|
+
set_websocket_user
|
25
|
+
|
26
|
+
Padrino::WebSockets::SpiderGazelle::EventManager.new(
|
27
|
+
channel, session['websocket_user'], ws, event_context, &block)
|
28
|
+
ws.start
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
alias :ws :websocket
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Padrino
|
2
|
+
module WebSockets
|
3
|
+
module Helpers
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
def set_websocket_user
|
7
|
+
session['websocket_user'] ||= SecureRandom.uuid
|
8
|
+
end
|
9
|
+
end
|
10
|
+
class << self
|
11
|
+
##
|
12
|
+
# Main class that register this extension.
|
13
|
+
#
|
14
|
+
def registered(app)
|
15
|
+
require 'padrino-websockets/base-event-manager'
|
16
|
+
|
17
|
+
if defined?(::SpiderGazelle)
|
18
|
+
require 'padrino-websockets/spider-gazelle'
|
19
|
+
app.helpers Padrino::WebSockets::SpiderGazelle::Helpers
|
20
|
+
app.extend Padrino::WebSockets::SpiderGazelle::Routing
|
21
|
+
else
|
22
|
+
logger.error "Can't find a WebSockets backend. At the moment we only support SpiderGazelle."
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
app.helpers Padrino::WebSockets::Helpers
|
27
|
+
end
|
28
|
+
alias :included :registered
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'padrino-websockets/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "padrino-websockets"
|
8
|
+
spec.version = Padrino::Websockets::VERSION
|
9
|
+
spec.authors = ["Darío Javier Cravero"]
|
10
|
+
spec.email = ["dario@uxtemple.com"]
|
11
|
+
spec.summary = %q{A WebSockets abstraction for Padrino}
|
12
|
+
spec.description = %q{A WebSockets abstraction for the Padrino Ruby Web Framework to manage
|
13
|
+
channels, users and events regardless of the underlying WebSockets implementation.}
|
14
|
+
spec.homepage = "https://github.com/dariocravero/padrino-websockets"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: padrino-websockets
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Darío Javier Cravero
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: |-
|
42
|
+
A WebSockets abstraction for the Padrino Ruby Web Framework to manage
|
43
|
+
channels, users and events regardless of the underlying WebSockets implementation.
|
44
|
+
email:
|
45
|
+
- dario@uxtemple.com
|
46
|
+
executables: []
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- ".gitignore"
|
51
|
+
- Gemfile
|
52
|
+
- LICENSE.txt
|
53
|
+
- README.md
|
54
|
+
- Rakefile
|
55
|
+
- examples/padrino-app/.components
|
56
|
+
- examples/padrino-app/.gitignore
|
57
|
+
- examples/padrino-app/Gemfile
|
58
|
+
- examples/padrino-app/Rakefile
|
59
|
+
- examples/padrino-app/app/app.rb
|
60
|
+
- examples/padrino-app/app/views/index.slim
|
61
|
+
- examples/padrino-app/config.ru
|
62
|
+
- examples/padrino-app/config/apps.rb
|
63
|
+
- examples/padrino-app/config/boot.rb
|
64
|
+
- examples/padrino-app/public/favicon.ico
|
65
|
+
- lib/padrino-websockets.rb
|
66
|
+
- lib/padrino-websockets/base-event-manager.rb
|
67
|
+
- lib/padrino-websockets/spider-gazelle.rb
|
68
|
+
- lib/padrino-websockets/spider-gazelle/event-manager.rb
|
69
|
+
- lib/padrino-websockets/spider-gazelle/helpers.rb
|
70
|
+
- lib/padrino-websockets/spider-gazelle/routing.rb
|
71
|
+
- lib/padrino-websockets/version.rb
|
72
|
+
- padrino-websockets.gemspec
|
73
|
+
homepage: https://github.com/dariocravero/padrino-websockets
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 2.2.0
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: A WebSockets abstraction for Padrino
|
97
|
+
test_files: []
|