├── .gitignore
├── CHANGELOG.md
├── Gemfile
├── README.md
├── Rakefile
├── autobahn.json
├── examples
├── autobahn_server.rb
└── echo_server.rb
├── lib
├── websocket-eventmachine-server.rb
└── websocket
│ └── eventmachine
│ ├── server.rb
│ └── server
│ └── version.rb
└── websocket-eventmachine-server.gemspec
/.gitignore:
--------------------------------------------------------------------------------
1 | Gemfile.lock
2 | autobahn
3 | pkg/*.gem
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## Edge
4 |
5 | - fix potential conflict with other WebSocket based libraries
6 |
7 | ## 1.0.1
8 |
9 | - extract most functionality to websocket-eventmachine-base gem to reuse with client
10 |
11 | ## 1.0.0
12 |
13 | - initial release
14 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | gemspec
4 |
5 | gem 'rake'
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebSocket Server for Ruby
2 |
3 | WebSocket-EventMachine-Server is Ruby WebSocket server based on EventMachine.
4 |
5 | - [Autobahn tests](http://imanel.github.com/websocket-ruby/autobahn/server)
6 | - [Docs](http://rdoc.info/github/imanel/websocket-eventmachine-server/master/frames)
7 |
8 | ## Why another WebSocket server?
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.
11 |
12 | ## Installation
13 |
14 | ``` bash
15 | gem install websocket-eventmachine-server
16 | ```
17 |
18 | or in Gemfile
19 |
20 | ``` ruby
21 | gem 'websocket-eventmachine-server'
22 | ```
23 |
24 | ## Simple server example
25 |
26 | ```ruby
27 | EM.run do
28 |
29 | WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => 8080) do |ws|
30 | ws.onopen do
31 | puts "Client connected"
32 | end
33 |
34 | ws.onmessage do |msg, type|
35 | puts "Received message: #{msg}"
36 | ws.send msg, :type => type
37 | end
38 |
39 | ws.onclose do
40 | puts "Client disconnected"
41 | end
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 | Parameters:
68 |
69 | - `[Handshake] handshake` - full handshake. See [specification](http://www.rubydoc.info/github/imanel/websocket-ruby/WebSocket/Handshake/Base) for available methods.
70 |
71 | Example:
72 |
73 | ```ruby
74 | ws.onopen do |handshake|
75 | puts "Client connected with params #{handshake.query}"
76 | end
77 | ```
78 |
79 | ### onclose
80 |
81 | Called after client closed connection.
82 |
83 | Example:
84 |
85 | ```ruby
86 | ws.onclose do
87 | puts "Client disconnected"
88 | end
89 | ```
90 |
91 | ### onmessage
92 |
93 | Called when server receive message.
94 |
95 | Parameters:
96 |
97 | - `[String] message` - content of message
98 | - `[Symbol] type` - type is type of message(:text or :binary)
99 |
100 | Example:
101 |
102 | ```ruby
103 | ws.onmessage do |msg, type|
104 | puts "Received message: #{msg} or type: #{type}"
105 | end
106 | ```
107 |
108 | ### onerror
109 |
110 | Called when server discovers error.
111 |
112 | Parameters:
113 |
114 | - `[String] error` - error reason.
115 |
116 | Example:
117 |
118 | ```ruby
119 | ws.onerror do |error|
120 | puts "Error occured: #{error}"
121 | end
122 | ```
123 |
124 | ### onping
125 |
126 | Called when server receive ping request. Pong request is sent automatically.
127 |
128 | Parameters:
129 |
130 | - `[String] message` - message for ping request.
131 |
132 | Example:
133 |
134 | ```ruby
135 | ws.onping do |message|
136 | puts "Ping received: #{message}"
137 | end
138 | ```
139 |
140 | ### onpong
141 |
142 | Called when server receive pong response.
143 |
144 | Parameters:
145 |
146 | - `[String] message` - message for pong response.
147 |
148 | Example:
149 |
150 | ```ruby
151 | ws.onpong do |message|
152 | puts "Pong received: #{message}"
153 | end
154 | ```
155 |
156 | ### send
157 |
158 | Sends message to client.
159 |
160 | Parameters:
161 |
162 | - `[String] message` - message that should be sent to client
163 | - `[Hash] params` - params for message(optional)
164 | - `[Symbol] :type` - type of message. Valid values are :text, :binary(default is :text)
165 |
166 | Example:
167 |
168 | ```ruby
169 | ws.send "Hello Client!"
170 | ws.send "binary data", :type => :binary
171 | ```
172 |
173 | ### close
174 |
175 | Closes connection and optionally send close frame to client.
176 |
177 | Parameters:
178 |
179 | - `[Integer] code` - code of closing, according to WebSocket specification(optional)
180 | - `[String] data` - data to send in closing frame(optional)
181 |
182 | Example:
183 |
184 | ```ruby
185 | ws.close
186 | ```
187 |
188 | ### ping
189 |
190 | Sends ping request.
191 |
192 | Parameters:
193 |
194 | - `[String] data` - data to send in ping request(optional)
195 |
196 | Example:
197 |
198 | ```ruby
199 | ws.ping 'Hi'
200 | ```
201 |
202 | ### pong
203 |
204 | Sends pong request. Usually there should be no need to send this request, as pong responses are sent automatically by server.
205 |
206 | Parameters:
207 |
208 | - `[String] data` - data to send in pong request(optional)
209 |
210 | Example:
211 |
212 | ``` ruby
213 | ws.pong 'Hello'
214 | ```
215 |
216 | ## Migrating from EM-WebSocket
217 |
218 | 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.
219 |
220 | ## Using self-signed certificate
221 |
222 | Read more [here](https://github.com/kanaka/websockify/wiki/Encrypted-Connections).
223 |
224 | ## Support
225 |
226 | If you like my work then consider supporting me:
227 |
228 | [](https://en.cryptobadges.io/donate/bc1qmxfc703ezscvd4qv0dvp7hwy7vc4kl6currs5e)
229 |
230 | [](https://en.cryptobadges.io/donate/0xA7048d5F866e2c3206DC95ebFa988fF987c0BccB)
231 |
232 | ## License
233 |
234 | (The MIT License)
235 |
236 | Copyright © 2012 Bernard Potocki
237 |
238 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
239 |
240 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
241 |
242 | THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
243 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
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
17 |
--------------------------------------------------------------------------------
/autobahn.json:
--------------------------------------------------------------------------------
1 | {
2 | "options": {"failByDrop": false},
3 | "outdir": "./autobahn",
4 |
5 | "servers": [
6 | {"agent": "WebSocket-EventMachine-Server
(1.0.0)", "url": "ws://localhost:9001", "options": {"version": 18}},
7 | {"agent": "EM-WebSocket
(0.3.8)", "url": "ws://localhost:8080", "options": {"version": 18}}
8 | ],
9 |
10 | "cases": ["*"],
11 | "exclude-cases": [],
12 | "exclude-agent-cases": {}
13 | }
14 |
--------------------------------------------------------------------------------
/examples/autobahn_server.rb:
--------------------------------------------------------------------------------
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
25 |
--------------------------------------------------------------------------------
/examples/echo_server.rb:
--------------------------------------------------------------------------------
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
46 |
--------------------------------------------------------------------------------
/lib/websocket-eventmachine-server.rb:
--------------------------------------------------------------------------------
1 | require 'websocket/eventmachine/server'
2 |
--------------------------------------------------------------------------------
/lib/websocket/eventmachine/server.rb:
--------------------------------------------------------------------------------
1 | require 'websocket-eventmachine-base'
2 |
3 | module WebSocket
4 | module EventMachine
5 |
6 | # WebSocket Server (using EventMachine)
7 | # @example
8 | # WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => 8080) do |ws|
9 | # ws.onopen { ws.send "Hello Client!"}
10 | # ws.onmessage { |msg| ws.send "Pong: #{msg}" }
11 | # ws.onclose { puts "WebSocket closed" }
12 | # ws.onerror { |e| puts "Error: #{e}" }
13 | # end
14 | class Server < Base
15 |
16 | ###########
17 | ### API ###
18 | ###########
19 |
20 | # Start server
21 | # @param options [Hash] The request arguments
22 | # @option args [String] :host The host IP/DNS name
23 | # @option args [Integer] :port The port to connect too(default = 80)
24 | def self.start(options, &block)
25 | ::EventMachine::start_server(options[:host], options[:port], self, options) do |c|
26 | block.call(c)
27 | end
28 | end
29 |
30 | # Initialize connection
31 | # @param args [Hash] Arguments for server
32 | # @option args [Boolean] :debug Should server log debug data?
33 | # @option args [Boolean] :secure If true then server will run over SSL
34 | # @option args [Boolean] :secure_proxy If true then server will use wss protocol but will not encrypt connection. Usefull for sll proxies.
35 | # @option args [Hash] :tls_options Options for SSL if secure = true
36 | def initialize(args)
37 | @debug = !!args[:debug]
38 | @secure = !!args[:secure]
39 | @secure_proxy = args[:secure_proxy] || @secure
40 | @tls_options = args[:tls_options] || {}
41 | end
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 |
75 | # Called when connection is opened.
76 | # No parameters are passed to block
77 | def onopen(&blk); super; end
78 |
79 | # Called when connection is closed.
80 | # No parameters are passed to block
81 | def onclose(&blk); super; end
82 |
83 | # Called when error occurs.
84 | # One parameter passed to block:
85 | # error - string with error message
86 | def onerror(&blk); super; end
87 |
88 | # Called when message is received.
89 | # Two parameters passed to block:
90 | # message - string with received message
91 | # type - type of message. Valid values are :text and :binary
92 | def onmessage(&blk); super; end
93 |
94 | # Called when ping message is received
95 | # One parameter passed to block:
96 | # message - string with ping message
97 | def onping(&blk); super; end
98 |
99 | # Called when pong message is received
100 | # One parameter passed to block:
101 | # message - string with pong message
102 | def onpong(&blk); super; end
103 |
104 | # Send data
105 | # @param data [String] Data to send
106 | # @param args [Hash] Arguments for send
107 | # @option args [String] :type Type of frame to send - available types are "text", "binary", "ping", "pong" and "close"
108 | # @option args [Integer] :code Code for close frame
109 | # @return [Boolean] true if data was send, otherwise call on_error if needed
110 | def send(data, args = {}); super; end
111 |
112 | # Close connection
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
115 |
116 | # Send ping message
117 | # @return [Boolean] false if protocol version is not supporting ping requests
118 | def ping(data = ''); super; end
119 |
120 | # Send pong message
121 | # @return [Boolean] false if protocol version is not supporting pong requests
122 | def pong(data = ''); super; end
123 |
124 | end
125 | end
126 | end
127 |
--------------------------------------------------------------------------------
/lib/websocket/eventmachine/server/version.rb:
--------------------------------------------------------------------------------
1 | module WebSocket
2 | module EventMachine
3 | class Server
4 | VERSION = '1.0.1'
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/websocket-eventmachine-server.gemspec:
--------------------------------------------------------------------------------
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-eventmachine-base', '~> 1.0'
16 |
17 | s.files = `git ls-files`.split("\n")
18 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20 | s.require_paths = ["lib"]
21 | end
22 |
--------------------------------------------------------------------------------