websocket-eventmachine-server 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []