websocket-eventmachine-server 1.0.0 → 1.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.
- 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:
|