├── .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 | [![Donate with Bitcoin](https://en.cryptobadges.io/badge/small/bc1qmxfc703ezscvd4qv0dvp7hwy7vc4kl6currs5e)](https://en.cryptobadges.io/donate/bc1qmxfc703ezscvd4qv0dvp7hwy7vc4kl6currs5e) 229 | 230 | [![Donate with Ethereum](https://en.cryptobadges.io/badge/small/0xA7048d5F866e2c3206DC95ebFa988fF987c0BccB)](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 | --------------------------------------------------------------------------------