websocket-eventmachine-server 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +4 -0
- data/README.md +168 -39
- data/lib/websocket/eventmachine/server.rb +52 -155
- data/lib/websocket/eventmachine/server/version.rb +1 -1
- data/websocket-eventmachine-server.gemspec +1 -3
- metadata +3 -35
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
WebSocket-EventMachine-Server is Ruby WebSocket server based on EventMachine.
|
4
4
|
|
5
|
-
|
5
|
+
- [Autobahn tests](http://imanel.github.com/websocket-ruby/autobahn/server)
|
6
|
+
- [Docs](http://rdoc.info/github/imanel/websocket-eventmachine-server/master/frames)
|
6
7
|
|
7
|
-
|
8
|
+
## Why another WebSocket server?
|
8
9
|
|
9
|
-
|
10
|
+
There are multiple Ruby WebSocket servers, each with different quirks and errors. Most commonly used em-websocket is unfortunately slow and have multiple bugs(see Autobahn tests above). This library was created to fix most of them.
|
10
11
|
|
11
12
|
## Installation
|
12
13
|
|
@@ -23,63 +24,191 @@ gem 'websocket-eventmachine-server'
|
|
23
24
|
## Simple server example
|
24
25
|
|
25
26
|
```ruby
|
26
|
-
|
27
|
+
EM.run do
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => 8080) do |ws|
|
30
|
+
ws.onopen do
|
31
|
+
puts "Client connected"
|
32
|
+
end
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
ws.onmessage do |msg, type|
|
35
|
+
puts "Received message: #{msg}"
|
36
|
+
ws.send msg, :type => type
|
37
|
+
end
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
puts "Recieved message: #{msg}"
|
39
|
-
ws.send "Pong: #{msg}"
|
40
|
-
}
|
39
|
+
ws.onclose do
|
40
|
+
puts "Client disconnected"
|
41
41
|
end
|
42
|
-
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
## Options
|
48
|
+
|
49
|
+
Following options can be passed to WebSocket::EventMachine::Server initializer:
|
50
|
+
|
51
|
+
- `[String] :host` - IP on which server should accept connections. '0.0.0.0' means all.
|
52
|
+
- `[Integer] :port` - Port on which server should accept connections.
|
53
|
+
- `[Boolean] :secure` - Enable secure WSS protocol. This will enable both SSL encryption and using WSS url and require `tls_options` key.
|
54
|
+
- `[Boolean] :secure_proxy` - Enable secure WSS protocol over proxy. This will enable only using WSS url and assume that SSL encryption is handled by some kind proxy(like [Stunnel](http://www.stunnel.org/))
|
55
|
+
- `[Hash] :tls_options` - Options for SSL(according to [EventMachine start_tls method](http://eventmachine.rubyforge.org/EventMachine/Connection.html#start_tls-instance_method))
|
56
|
+
- `[String] :private_key_file` - URL to private key file
|
57
|
+
- `[String] :cert_chain_file` - URL to cert chain file
|
58
|
+
|
59
|
+
## Methods
|
60
|
+
|
61
|
+
Following methods are available for WebSocket::EventMachine::Server object:
|
62
|
+
|
63
|
+
### onopen
|
64
|
+
|
65
|
+
Called after client is connected.
|
66
|
+
|
67
|
+
Example:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
ws.onopen do
|
71
|
+
puts "Client connected"
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
### onclose
|
76
|
+
|
77
|
+
Called after client closed connection.
|
78
|
+
|
79
|
+
Example:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
ws.onclose do
|
83
|
+
puts "Client disconnected"
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
### onmessage
|
88
|
+
|
89
|
+
Called when server receive message.
|
90
|
+
|
91
|
+
Parameters:
|
92
|
+
|
93
|
+
- `[String] message` - content of message
|
94
|
+
- `[Symbol] type` - type is type of message(:text or :binary)
|
95
|
+
|
96
|
+
Example:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
ws.onmessage do |msg, type|
|
100
|
+
puts "Received message: #{msg} or type: #{type}"
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
### onerror
|
105
|
+
|
106
|
+
Called when server discovers error.
|
107
|
+
|
108
|
+
Parameters:
|
109
|
+
|
110
|
+
- `[String] error` - error reason.
|
111
|
+
|
112
|
+
Example:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
ws.onerror do |error|
|
116
|
+
puts "Error occured: #{error}"
|
117
|
+
end
|
43
118
|
```
|
44
119
|
|
45
|
-
|
120
|
+
### onping
|
121
|
+
|
122
|
+
Called when server receive ping request. Pong request is sent automatically.
|
46
123
|
|
47
|
-
|
124
|
+
Parameters:
|
48
125
|
|
49
|
-
|
126
|
+
- `[String] message` - message for ping request.
|
127
|
+
|
128
|
+
Example:
|
50
129
|
|
51
130
|
```ruby
|
52
|
-
|
53
|
-
|
54
|
-
:port => 443,
|
55
|
-
:secure => true,
|
56
|
-
:tls_options => {
|
57
|
-
:private_key_file => "/private/key",
|
58
|
-
:cert_chain_file => "/ssl/certificate"
|
59
|
-
}
|
60
|
-
}) do |ws|
|
61
|
-
...
|
131
|
+
ws.onping do |message|
|
132
|
+
puts "Ping received: #{message}"
|
62
133
|
end
|
63
134
|
```
|
64
135
|
|
65
|
-
|
136
|
+
### onpong
|
137
|
+
|
138
|
+
Called when server receive pong response.
|
66
139
|
|
67
|
-
|
140
|
+
Parameters:
|
68
141
|
|
69
|
-
|
142
|
+
- `[String] message` - message for pong response.
|
70
143
|
|
71
|
-
|
144
|
+
Example:
|
72
145
|
|
73
146
|
```ruby
|
74
|
-
|
75
|
-
|
76
|
-
:port => 8080,
|
77
|
-
:secure_proxy => true
|
78
|
-
}) do |ws|
|
79
|
-
...
|
147
|
+
ws.onpong do |message|
|
148
|
+
puts "Pong received: #{message}"
|
80
149
|
end
|
81
150
|
```
|
82
151
|
|
152
|
+
### send
|
153
|
+
|
154
|
+
Sends message to client.
|
155
|
+
|
156
|
+
Parameters:
|
157
|
+
|
158
|
+
- `[String] message` - message that should be sent to client
|
159
|
+
- `[Hash] params` - params for message(optional)
|
160
|
+
- `[Symbol] :type` - type of message. Valid values are :text, :binary(default is :text)
|
161
|
+
|
162
|
+
Example:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
ws.send "Hello Client!"
|
166
|
+
ws.send "binary data", :type => :binary
|
167
|
+
```
|
168
|
+
|
169
|
+
### close
|
170
|
+
|
171
|
+
Closes connection and optionally send close frame to client.
|
172
|
+
|
173
|
+
Parameters:
|
174
|
+
|
175
|
+
- `[Integer] code` - code of closing, according to WebSocket specification(optional)
|
176
|
+
- `[String] data` - data to send in closing frame(optional)
|
177
|
+
|
178
|
+
Example:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
ws.close
|
182
|
+
```
|
183
|
+
|
184
|
+
### ping
|
185
|
+
|
186
|
+
Sends ping request.
|
187
|
+
|
188
|
+
Parameters:
|
189
|
+
|
190
|
+
- `[String] data` - data to send in ping request(optional)
|
191
|
+
|
192
|
+
Example:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
ws.ping 'Hi'
|
196
|
+
```
|
197
|
+
|
198
|
+
### pong
|
199
|
+
|
200
|
+
Sends pong request. Usually there should be no need to send this request, as pong responses are sent automatically by server.
|
201
|
+
|
202
|
+
Parameters:
|
203
|
+
|
204
|
+
- `[String] data` - data to send in pong request(optional)
|
205
|
+
|
206
|
+
Example:
|
207
|
+
|
208
|
+
``` ruby
|
209
|
+
ws.pong 'Hello'
|
210
|
+
```
|
211
|
+
|
83
212
|
## Migrating from EM-WebSocket
|
84
213
|
|
85
214
|
This library is compatible with EM-WebSocket, so only thing you need to change is running server - you need to change from EM-WebSocket to WebSocket::EventMachine::Server in your application and everything will be working.
|
@@ -1,5 +1,4 @@
|
|
1
|
-
require 'websocket'
|
2
|
-
require 'eventmachine'
|
1
|
+
require 'websocket-eventmachine-base'
|
3
2
|
|
4
3
|
module WebSocket
|
5
4
|
module EventMachine
|
@@ -12,7 +11,7 @@ module WebSocket
|
|
12
11
|
# ws.onclose { puts "WebSocket closed" }
|
13
12
|
# ws.onerror { |e| puts "Error: #{e}" }
|
14
13
|
# end
|
15
|
-
class Server <
|
14
|
+
class Server < Base
|
16
15
|
|
17
16
|
###########
|
18
17
|
### API ###
|
@@ -41,188 +40,86 @@ module WebSocket
|
|
41
40
|
@tls_options = args[:tls_options] || {}
|
42
41
|
end
|
43
42
|
|
43
|
+
############################
|
44
|
+
### EventMachine methods ###
|
45
|
+
############################
|
46
|
+
|
47
|
+
# Eventmachine internal
|
48
|
+
# @private
|
49
|
+
def post_init
|
50
|
+
@state = :connecting
|
51
|
+
@handshake = WebSocket::Handshake::Server.new(:secure => @secure_proxy)
|
52
|
+
start_tls(@tls_options) if @secure
|
53
|
+
end
|
54
|
+
|
55
|
+
#######################
|
56
|
+
### Private methods ###
|
57
|
+
#######################
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def incoming_frame
|
62
|
+
WebSocket::Frame::Incoming::Server
|
63
|
+
end
|
64
|
+
|
65
|
+
def outgoing_frame
|
66
|
+
WebSocket::Frame::Outgoing::Server
|
67
|
+
end
|
68
|
+
|
69
|
+
public
|
70
|
+
|
71
|
+
#########################
|
72
|
+
### Inherited methods ###
|
73
|
+
#########################
|
74
|
+
|
44
75
|
# Called when connection is opened.
|
45
76
|
# No parameters are passed to block
|
46
|
-
def onopen(&blk);
|
77
|
+
def onopen(&blk); super; end
|
47
78
|
|
48
79
|
# Called when connection is closed.
|
49
80
|
# No parameters are passed to block
|
50
|
-
def onclose(&blk);
|
81
|
+
def onclose(&blk); super; end
|
51
82
|
|
52
83
|
# Called when error occurs.
|
53
84
|
# One parameter passed to block:
|
54
85
|
# error - string with error message
|
55
|
-
def onerror(&blk);
|
86
|
+
def onerror(&blk); super; end
|
56
87
|
|
57
|
-
# Called when message is received
|
88
|
+
# Called when message is received.
|
58
89
|
# Two parameters passed to block:
|
59
|
-
# message - string with message
|
90
|
+
# message - string with received message
|
60
91
|
# type - type of message. Valid values are :text and :binary
|
61
|
-
def onmessage(&blk);
|
92
|
+
def onmessage(&blk); super; end
|
62
93
|
|
63
|
-
# Called when ping message is received
|
94
|
+
# Called when ping message is received
|
64
95
|
# One parameter passed to block:
|
65
96
|
# message - string with ping message
|
66
|
-
def onping(&blk);
|
97
|
+
def onping(&blk); super; end
|
67
98
|
|
68
|
-
# Called when pond message is received
|
99
|
+
# Called when pond message is received
|
69
100
|
# One parameter passed to block:
|
70
101
|
# message - string with pong message
|
71
|
-
def onpong(&blk);
|
102
|
+
def onpong(&blk); super; end
|
72
103
|
|
73
|
-
# Send data
|
104
|
+
# Send data
|
74
105
|
# @param data [String] Data to send
|
75
106
|
# @param args [Hash] Arguments for send
|
76
107
|
# @option args [String] :type Type of frame to send - available types are "text", "binary", "ping", "pong" and "close"
|
77
108
|
# @option args [Integer] :code Code for close frame
|
78
109
|
# @return [Boolean] true if data was send, otherwise call on_error if needed
|
79
|
-
def send(data, args = {})
|
80
|
-
type = args[:type] || :text
|
81
|
-
unless type == :plain
|
82
|
-
frame = WebSocket::Frame::Outgoing::Server.new args.merge(:version => @handshake.version, :data => data)
|
83
|
-
if !frame.supported?
|
84
|
-
trigger_onerror("Frame type '#{type}' is not supported in protocol version #{@handshake.version}")
|
85
|
-
return false
|
86
|
-
elsif !frame.require_sending?
|
87
|
-
return false
|
88
|
-
end
|
89
|
-
data = frame.to_s
|
90
|
-
end
|
91
|
-
debug "Sending raw: ", data
|
92
|
-
send_data(data)
|
93
|
-
true
|
94
|
-
end
|
110
|
+
def send(data, args = {}); super; end
|
95
111
|
|
96
112
|
# Close connection
|
97
|
-
# @return [Boolean] true if connection is closed immediately, false if waiting for
|
98
|
-
def close(code = 1000, data = nil)
|
99
|
-
if @state == :open
|
100
|
-
@state = :closing
|
101
|
-
return false if send(data, :type => :close, :code => code)
|
102
|
-
else
|
103
|
-
send(data, :type => :close) if @state == :closing
|
104
|
-
@state = :closed
|
105
|
-
end
|
106
|
-
close_connection_after_writing
|
107
|
-
true
|
108
|
-
end
|
113
|
+
# @return [Boolean] true if connection is closed immediately, false if waiting for other side to close connection
|
114
|
+
def close(code = 1000, data = nil); super; end
|
109
115
|
|
110
|
-
# Send ping message
|
116
|
+
# Send ping message
|
111
117
|
# @return [Boolean] false if protocol version is not supporting ping requests
|
112
|
-
def ping(data = '')
|
113
|
-
send(data, :type => :ping)
|
114
|
-
end
|
118
|
+
def ping(data = ''); super; end
|
115
119
|
|
116
|
-
# Send pong message
|
120
|
+
# Send pong message
|
117
121
|
# @return [Boolean] false if protocol version is not supporting pong requests
|
118
|
-
def pong(data = '')
|
119
|
-
send(data, :type => :pong)
|
120
|
-
end
|
121
|
-
|
122
|
-
############################
|
123
|
-
### EventMachine methods ###
|
124
|
-
############################
|
125
|
-
|
126
|
-
# Eventmachine internal
|
127
|
-
# @private
|
128
|
-
def post_init
|
129
|
-
@state = :connecting
|
130
|
-
@handshake = WebSocket::Handshake::Server.new(:secure => @secure_proxy)
|
131
|
-
start_tls(@tls_options) if @secure
|
132
|
-
end
|
133
|
-
|
134
|
-
# Eventmachine internal
|
135
|
-
# @private
|
136
|
-
def receive_data(data)
|
137
|
-
debug "Received raw: ", data
|
138
|
-
case @state
|
139
|
-
when :connecting then handle_connecting(data)
|
140
|
-
when :open then handle_open(data)
|
141
|
-
when :closing then handle_closing(data)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
# Eventmachine internal
|
146
|
-
# @private
|
147
|
-
def unbind
|
148
|
-
unless @state == :closed
|
149
|
-
@state = :closed
|
150
|
-
close
|
151
|
-
trigger_onclose('')
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
#######################
|
156
|
-
### Private methods ###
|
157
|
-
#######################
|
158
|
-
|
159
|
-
private
|
160
|
-
|
161
|
-
['onopen'].each do |m|
|
162
|
-
define_method "trigger_#{m}" do
|
163
|
-
callback = instance_variable_get("@#{m}")
|
164
|
-
callback.call if callback
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
['onerror', 'onping', 'onpong', 'onclose'].each do |m|
|
169
|
-
define_method "trigger_#{m}" do |data|
|
170
|
-
callback = instance_variable_get("@#{m}")
|
171
|
-
callback.call(data) if callback
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
def trigger_onmessage(data, type)
|
176
|
-
@onmessage.call(data, type) if @onmessage
|
177
|
-
end
|
178
|
-
|
179
|
-
def handle_connecting(data)
|
180
|
-
@handshake << data
|
181
|
-
return unless @handshake.finished?
|
182
|
-
if @handshake.valid?
|
183
|
-
send(@handshake.to_s, :type => :plain) if @handshake.should_respond?
|
184
|
-
@frame = WebSocket::Frame::Incoming::Server.new(:version => @handshake.version)
|
185
|
-
@state = :open
|
186
|
-
trigger_onopen
|
187
|
-
handle_open(@handshake.leftovers) if @handshake.leftovers
|
188
|
-
else
|
189
|
-
trigger_onerror(@handshake.error)
|
190
|
-
close
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def handle_open(data)
|
195
|
-
@frame << data
|
196
|
-
while frame = @frame.next
|
197
|
-
case frame.type
|
198
|
-
when :close
|
199
|
-
@state = :closing
|
200
|
-
close
|
201
|
-
trigger_onclose(frame.to_s)
|
202
|
-
when :ping
|
203
|
-
pong(frame.to_s)
|
204
|
-
trigger_onping(frame.to_s)
|
205
|
-
when :pong
|
206
|
-
trigger_onpong(frame.to_s)
|
207
|
-
when :text
|
208
|
-
trigger_onmessage(frame.to_s, :text)
|
209
|
-
when :binary
|
210
|
-
trigger_onmessage(frame.to_s, :binary)
|
211
|
-
end
|
212
|
-
end
|
213
|
-
unbind if @frame.error?
|
214
|
-
end
|
215
|
-
|
216
|
-
def handle_closing(data)
|
217
|
-
@state = :closed
|
218
|
-
close
|
219
|
-
trigger_onclose
|
220
|
-
end
|
221
|
-
|
222
|
-
def debug(description, data)
|
223
|
-
return unless @debug
|
224
|
-
puts(description + data.bytes.to_a.collect{|b| '\x' + b.to_s(16).rjust(2, '0')}.join) unless @state == :connecting
|
225
|
-
end
|
122
|
+
def pong(data = ''); super; end
|
226
123
|
|
227
124
|
end
|
228
125
|
end
|
@@ -12,9 +12,7 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.summary = %q{WebSocket server for Ruby}
|
13
13
|
s.description = %q{WebSocket server for Ruby}
|
14
14
|
|
15
|
-
s.add_dependency 'websocket', '~> 1.0'
|
16
|
-
s.add_dependency 'websocket-native', '~> 1.0'
|
17
|
-
s.add_dependency 'eventmachine', '~> 1.0'
|
15
|
+
s.add_dependency 'websocket-eventmachine-base', '~> 1.0'
|
18
16
|
|
19
17
|
s.files = `git ls-files`.split("\n")
|
20
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: websocket-eventmachine-server
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,42 +9,10 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name: websocket
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
|
-
requirements:
|
19
|
-
- - ~>
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
version: '1.0'
|
22
|
-
type: :runtime
|
23
|
-
prerelease: false
|
24
|
-
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
|
-
requirements:
|
27
|
-
- - ~>
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '1.0'
|
30
|
-
- !ruby/object:Gem::Dependency
|
31
|
-
name: websocket-native
|
32
|
-
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
|
-
requirements:
|
35
|
-
- - ~>
|
36
|
-
- !ruby/object:Gem::Version
|
37
|
-
version: '1.0'
|
38
|
-
type: :runtime
|
39
|
-
prerelease: false
|
40
|
-
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
|
-
requirements:
|
43
|
-
- - ~>
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
version: '1.0'
|
46
|
-
- !ruby/object:Gem::Dependency
|
47
|
-
name: eventmachine
|
15
|
+
name: websocket-eventmachine-base
|
48
16
|
requirement: !ruby/object:Gem::Requirement
|
49
17
|
none: false
|
50
18
|
requirements:
|