websocket-eventmachine-server 1.0.0

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.
@@ -0,0 +1,3 @@
1
+ Gemfile.lock
2
+ autobahn
3
+ pkg/*.gem
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ - initial release
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
@@ -0,0 +1,89 @@
1
+ # WebSocket Server for Ruby
2
+
3
+ WebSocket-EventMachine-Server is Ruby WebSocket server based on EventMachine.
4
+
5
+ ## Why another WebSocket server?
6
+
7
+ 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 below). This library was created to fix most of them.
8
+
9
+ [Autobahn tests](http://imanel.github.com/websocket-ruby/autobahn/server)
10
+
11
+ ## Installation
12
+
13
+ ``` bash
14
+ gem install websocket-eventmachine-server
15
+ ```
16
+
17
+ or in Gemfile
18
+
19
+ ``` ruby
20
+ gem 'websocket-eventmachine-server'
21
+ ```
22
+
23
+ ## Simple server example
24
+
25
+ ```ruby
26
+ EventMachine.run {
27
+
28
+ WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => 8080) do |ws|
29
+ ws.onopen {
30
+ puts "WebSocket connection open"
31
+
32
+ # publish message to the client
33
+ ws.send "Hello Client"
34
+ }
35
+
36
+ ws.onclose { puts "Connection closed" }
37
+ ws.onmessage { |msg|
38
+ puts "Recieved message: #{msg}"
39
+ ws.send "Pong: #{msg}"
40
+ }
41
+ end
42
+ }
43
+ ```
44
+
45
+ ## Secure server
46
+
47
+ It is possible to accept secure wss:// connections by passing :secure => true when opening the connection. Safari 5 does not currently support prompting on untrusted SSL certificates therefore using signed certificates is highly recommended. Pass a :tls_options hash containing keys as described in http://eventmachine.rubyforge.org/EventMachine/Connection.html#M000296
48
+
49
+ For example,
50
+
51
+ ```ruby
52
+ WebSocket::EventMachine::Server.start({
53
+ :host => "0.0.0.0",
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
+ ...
62
+ end
63
+ ```
64
+
65
+ ## Running behind an SSL Proxy/Terminator, like Stunnel
66
+
67
+ The :secure_proxy => true option makes it possible to run correctly when behind a secure SSL proxy/terminator like [Stunnel](http://www.stunnel.org/). When setting :secure_proxy => true, any reponse from the em-websocket which contains the websocket url will use the wss:// url scheme. None of the traffic is encrypted.
68
+
69
+ This option is necessary when using websockets with an SSL proxy/terminator on Safari 5.1.x or earlier, and also on Safari in iOS 5.x and earlier. Most versions of Chrome, Safari 5.2, and Safari in iOS 6 do not appear to have this problem.
70
+
71
+ For example,
72
+
73
+ ```ruby
74
+ WebSocket::EventMachine::Server.start({
75
+ :host => "0.0.0.0",
76
+ :port => 8080,
77
+ :secure_proxy => true
78
+ }) do |ws|
79
+ ...
80
+ end
81
+ ```
82
+
83
+ ## Migrating from EM-WebSocket
84
+
85
+ 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.
86
+
87
+ ## License
88
+
89
+ The MIT License - Copyright (c) 2012 Bernard Potocki
@@ -0,0 +1,16 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ # require 'rspec/core/rake_task'
5
+
6
+ # RSpec::Core::RakeTask.new do |t|
7
+ # t.rspec_opts = ["-c", "-f progress"]
8
+ # t.pattern = 'spec/**/*_spec.rb'
9
+ # end
10
+
11
+ # task :default => :spec
12
+
13
+ desc "Run autobahn tests for server"
14
+ task :autobahn do
15
+ system('wstest --mode=fuzzingclient --spec=autobahn.json')
16
+ end
@@ -0,0 +1,13 @@
1
+ {
2
+ "options": {"failByDrop": false},
3
+ "outdir": "./autobahn",
4
+
5
+ "servers": [
6
+ {"agent": "WebSocket-EventMachine-Server<br>(1.0.0)", "url": "ws://localhost:9001", "options": {"version": 18}},
7
+ {"agent": "EM-WebSocket<br>(0.3.8)", "url": "ws://localhost:8080", "options": {"version": 18}}
8
+ ],
9
+
10
+ "cases": ["*"],
11
+ "exclude-cases": [],
12
+ "exclude-agent-cases": {}
13
+ }
@@ -0,0 +1,24 @@
1
+ require File.expand_path('../../lib/websocket-eventmachine-server', __FILE__)
2
+
3
+ EM.epoll
4
+ EM.run do
5
+
6
+ trap("TERM") { stop }
7
+ trap("INT") { stop }
8
+
9
+ WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => 9001) do |ws|
10
+
11
+ ws.onmessage do |msg, type|
12
+ ws.send msg, :type => type
13
+ end
14
+
15
+ end
16
+
17
+ puts "Server started at port 9001"
18
+
19
+ def stop
20
+ puts "Terminating WebSocket Server"
21
+ EventMachine.stop
22
+ end
23
+
24
+ end
@@ -0,0 +1,45 @@
1
+ require File.expand_path('../../lib/websocket-eventmachine-server', __FILE__)
2
+
3
+ EM.epoll
4
+ EM.run do
5
+
6
+ trap("TERM") { stop }
7
+ trap("INT") { stop }
8
+
9
+ WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => 9001) do |ws|
10
+
11
+ ws.onopen do
12
+ puts "Client connected"
13
+ end
14
+
15
+ ws.onmessage do |msg, type|
16
+ puts "Received message: #{msg}"
17
+ ws.send msg, :type => type
18
+ end
19
+
20
+ ws.onclose do
21
+ puts "Client disconnected"
22
+ end
23
+
24
+ ws.onerror do |e|
25
+ puts "Error: #{e}"
26
+ end
27
+
28
+ ws.onping do |msg|
29
+ puts "Receied ping: #{msg}"
30
+ end
31
+
32
+ ws.onpong do |msg|
33
+ puts "Received pong: #{msg}"
34
+ end
35
+
36
+ end
37
+
38
+ puts "Server started at port 9001"
39
+
40
+ def stop
41
+ puts "Terminating WebSocket Server"
42
+ EventMachine.stop
43
+ end
44
+
45
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path('../websocket/eventmachine/server', __FILE__)
@@ -0,0 +1,229 @@
1
+ require 'websocket'
2
+ require 'eventmachine'
3
+
4
+ module WebSocket
5
+ module EventMachine
6
+
7
+ # WebSocket Server (using EventMachine)
8
+ # @example
9
+ # WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => 8080) do |ws|
10
+ # ws.onopen { ws.send "Hello Client!"}
11
+ # ws.onmessage { |msg| ws.send "Pong: #{msg}" }
12
+ # ws.onclose { puts "WebSocket closed" }
13
+ # ws.onerror { |e| puts "Error: #{e}" }
14
+ # end
15
+ class Server < ::EventMachine::Connection
16
+
17
+ ###########
18
+ ### API ###
19
+ ###########
20
+
21
+ # Start server
22
+ # @param options [Hash] The request arguments
23
+ # @option args [String] :host The host IP/DNS name
24
+ # @option args [Integer] :port The port to connect too(default = 80)
25
+ def self.start(options, &block)
26
+ ::EventMachine::start_server(options[:host], options[:port], self, options) do |c|
27
+ block.call(c)
28
+ end
29
+ end
30
+
31
+ # Initialize connection
32
+ # @param args [Hash] Arguments for server
33
+ # @option args [Boolean] :debug Should server log debug data?
34
+ # @option args [Boolean] :secure If true then server will run over SSL
35
+ # @option args [Boolean] :secure_proxy If true then server will use wss protocol but will not encrypt connection. Usefull for sll proxies.
36
+ # @option args [Hash] :tls_options Options for SSL if secure = true
37
+ def initialize(args)
38
+ @debug = !!args[:debug]
39
+ @secure = !!args[:secure]
40
+ @secure_proxy = args[:secure_proxy] || @secure
41
+ @tls_options = args[:tls_options] || {}
42
+ end
43
+
44
+ # Called when connection is opened.
45
+ # No parameters are passed to block
46
+ def onopen(&blk); @onopen = blk; end
47
+
48
+ # Called when connection is closed.
49
+ # No parameters are passed to block
50
+ def onclose(&blk); @onclose = blk; end
51
+
52
+ # Called when error occurs.
53
+ # One parameter passed to block:
54
+ # error - string with error message
55
+ def onerror(&blk); @onerror = blk; end
56
+
57
+ # Called when message is received from server.
58
+ # Two parameters passed to block:
59
+ # message - string with message sent to server
60
+ # type - type of message. Valid values are :text and :binary
61
+ def onmessage(&blk); @onmessage = blk; end
62
+
63
+ # Called when ping message is received from server.
64
+ # One parameter passed to block:
65
+ # message - string with ping message
66
+ def onping(&blk); @onping = blk; end
67
+
68
+ # Called when pond message is received from server.
69
+ # One parameter passed to block:
70
+ # message - string with pong message
71
+ def onpong(&blk); @onpong = blk; end
72
+
73
+ # Send data to client
74
+ # @param data [String] Data to send
75
+ # @param args [Hash] Arguments for send
76
+ # @option args [String] :type Type of frame to send - available types are "text", "binary", "ping", "pong" and "close"
77
+ # @option args [Integer] :code Code for close frame
78
+ # @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
95
+
96
+ # Close connection
97
+ # @return [Boolean] true if connection is closed immediately, false if waiting for server to close connection
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
109
+
110
+ # Send ping message to client
111
+ # @return [Boolean] false if protocol version is not supporting ping requests
112
+ def ping(data = '')
113
+ send(data, :type => :ping)
114
+ end
115
+
116
+ # Send pong message to client
117
+ # @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
226
+
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,7 @@
1
+ module WebSocket
2
+ module EventMachine
3
+ class Server
4
+ VERSION = '1.0.0'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "websocket/eventmachine/server/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "websocket-eventmachine-server"
7
+ s.version = WebSocket::EventMachine::Server::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Bernard Potocki"]
10
+ s.email = ["bernard.potocki@imanel.org"]
11
+ s.homepage = "http://github.com/imanel/websocket-eventmachine-server"
12
+ s.summary = %q{WebSocket server for Ruby}
13
+ s.description = %q{WebSocket server for Ruby}
14
+
15
+ s.add_dependency 'websocket', '~> 1.0'
16
+ s.add_dependency 'websocket-native', '~> 1.0'
17
+ s.add_dependency 'eventmachine', '~> 1.0'
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: websocket-eventmachine-server
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Bernard Potocki
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-19 00:00:00.000000000 Z
13
+ dependencies:
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
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ description: WebSocket server for Ruby
63
+ email:
64
+ - bernard.potocki@imanel.org
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - CHANGELOG.md
71
+ - Gemfile
72
+ - README.md
73
+ - Rakefile
74
+ - autobahn.json
75
+ - examples/autobahn_server.rb
76
+ - examples/echo_server.rb
77
+ - lib/websocket-eventmachine-server.rb
78
+ - lib/websocket/eventmachine/server.rb
79
+ - lib/websocket/eventmachine/server/version.rb
80
+ - websocket-eventmachine-server.gemspec
81
+ homepage: http://github.com/imanel/websocket-eventmachine-server
82
+ licenses: []
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 1.8.24
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: WebSocket server for Ruby
105
+ test_files: []