Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit with first run at the server, along with some partiall…

…y working examples (chat and

time sync).  Soon to add readme, docs, and gem.
  • Loading branch information...
commit 020b9c7b0dc948b4aafc8d076474765bf6289c05 1 parent 691d984
Dan Simpson authored
View
3  .gitignore
@@ -0,0 +1,3 @@
+*.gem
+*.gemspec
+
View
40 examples/chat.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+<head>
+ <title>em-websocket-server test</title>
+</head>
+<body>
+
+
+<h1>em-websocket-server chat demo</h1>
+<div id="chat">connecting....</div>
+
+
+
+<script>
+
+ var webSocket = new WebSocket('ws://192.168.0.2:8000/time');
+
+ webSocket.onopen = function(event){
+ document.getElementById('chat').innerHTML = 'connected';
+ };
+
+ webSocket.onmessage = function(event){
+ var object = JSON.parse(event.data);
+ document.getElementById('chat').innerHTML += "<b>" + object.user + "</b>" + object.message;
+ };
+
+ webSocket.onclose = function(event){
+ document.getElementById('chat').innerHTML = 'socket closed';
+ };
+
+ document.getElementById('chatty').addEventListener("onkeypress", function() {
+ alert("X")
+ });
+
+</script>
+
+<input type="text" onkeypress="webSocket.send('hi');" />
+
+</body>
+</html>
View
33 examples/chat.rb
@@ -0,0 +1,33 @@
+$:.unshift File.dirname(__FILE__) + '/../lib'
+
+require 'rubygems'
+require 'em-websocket-server'
+require 'json'
+
+$chatroom = EM::Channel.new
+
+class ChatServer < WebSocketServer
+
+ def on_connect
+ @sid = $chatroom.subscribe do |msg|
+ send_message msg
+ end
+ end
+
+ def on_disconnect
+ $chatroom.unsubscribe(@sid)
+ end
+
+ def on_receive msg
+ $chatroom.push msg
+ end
+
+end
+
+
+EM.epoll
+EM.set_descriptor_table_size(10240)
+
+EM.run do
+ EM.start_server "0.0.0.0", 8000, ChatServer
+end
View
29 examples/timesync.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html>
+<head>
+ <title>em-websocket-server test</title>
+</head>
+<body>
+
+<h1>em-websocket-server timesync demo</h1>
+<h2 id="time">opening socket</h2>
+
+<script>
+ var webSocket = new WebSocket('ws://192.168.0.2:8000/time');
+
+ webSocket.onopen = function(event){
+ document.getElementById('time').innerHTML = 'waiting for socket';
+ };
+
+ webSocket.onmessage = function(event){
+ var object = JSON.parse(event.data);
+ document.getElementById('time').innerHTML = object.time;
+ };
+
+ webSocket.onclose = function(event){
+ document.getElementById('time').innerHTML = 'socket closed';
+ };
+</script>
+
+</body>
+</html>
View
35 examples/timesync.rb
@@ -0,0 +1,35 @@
+$:.unshift File.dirname(__FILE__) + '/../lib'
+
+require 'rubygems'
+require 'em-websocket-server'
+require 'json'
+
+class TimeServer < WebSocketServer
+
+ def on_connect
+ @timer = EM.add_periodic_timer(5) do
+ sync_time
+ end
+ end
+
+ def on_disconnect
+ @timer
+ end
+
+ def on_receive msg
+ puts "msg rcv"
+ end
+
+ def sync_time
+ send_message({ :time => Time.now }.to_json)
+ end
+
+end
+
+
+EM.epoll
+EM.set_descriptor_table_size(10240)
+
+EM.run do
+ EM.start_server "0.0.0.0", 8000, TimeServer
+end
View
158 lib/em-websocket-server.rb
@@ -0,0 +1,158 @@
+require 'rubygems'
+require 'eventmachine'
+
+class WebSocketServer < EM::Connection
+
+ @@logger = nil
+ @@num_connections = 0
+ @@path_regex = /^GET (\/[^\s]*) HTTP\/1\.1$/
+ @@header_regex = /^([^:]+):\s*([^$]+)/
+ @@callbacks = {}
+ @@accepted_origins = []
+
+ attr_accessor :connected,
+ :headers,
+ :path
+
+ def initialize *args
+ super
+ @connected = false
+ end
+
+ def valid_origin?
+ @@accepted_origins.empty? || @@accepted_origins.include?(origin)
+ end
+
+ #not doing anything with this yet
+ def valid_path?
+ true
+ end
+
+ def valid_upgrade?
+ @headers["Upgrade"] =~ /websocket/i
+ end
+
+ def origin
+ @headers["Origin"]
+ end
+
+ def host
+ @headers["Host"]
+ end
+
+ def self.path name, &block
+ @@callbacks[name] = block
+ end
+
+ #tcp connection established
+ def post_init
+ @@num_connections += 1
+ end
+
+ #must be public for em
+ def unbind
+ @@num_connections -= 1
+ on_disconnect
+ end
+
+ # Frames need to start with 0x00-0x7f byte and end with
+ # an 0xFF byte. Per spec, we can also set the first
+ # byte to a value betweent 0x80 and 0xFF, followed by
+ # a leading length indicator. No support yet
+ def send_message msg
+ send_data "\x00#{msg}\xff"
+ end
+
+ protected
+
+ #override this method
+ def on_receive msg
+ log msg
+ end
+
+ #override this method
+ def on_connect
+ log "connected"
+ end
+
+ #override this method
+ def on_disconnect
+ log "disconnected"
+ end
+
+ def log msg
+ if @@logger
+ @@logger.info msg
+ else
+ puts msg
+ end
+ end
+
+ private
+
+ # when the connection receives data from the client
+ # we either handshake, accept the start command
+ # or handle the message at the app layer
+ def receive_data data
+ unless @connected
+ handshake data
+ else
+ on_receive data.gsub(/^(\x00)|(\xff)$/, "")
+ end
+ end
+
+ # parse the headers, validate the origin and path
+ # and respond with appropiate headers for a
+ # healthy relationship with the client
+ def handshake data
+
+ #convert the headers to a hash
+ parse_headers data
+
+ # close the connection if the connection
+ # originates from an invalid source
+ close_connection unless valid_origin?
+
+ # close the connection if a callback
+ # is not registered for the path
+ close_connection unless valid_path?
+
+ # don't respond to non-websocket HTTP requests
+ close_connection unless valid_upgrade?
+
+ #complete the handshake
+ send_headers
+
+ @connected = true
+
+ on_connect
+ end
+
+ # send the handshake response headers to
+ # complete the initial com
+ def send_headers
+
+ response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
+ response << "Upgrade: WebSocket\r\n"
+ response << "Connection: Upgrade\r\n"
+ response << "WebSocket-Origin: #{origin}\r\n"
+ response << "WebSocket-Location: ws://#{host}#{path}\r\n\r\n"
+
+ send_data response
+ end
+
+ # turn http style headers into a ruby hash
+ # TODO: this is probably not done "well"
+ def parse_headers data
+ lines = data.split("\r\n")
+
+ @path = @@path_regex.match(lines.shift)[1]
+ @headers = {}
+
+ lines.each do |line|
+ kvp = @@header_regex.match(line)
+ @headers[kvp[1].strip] = kvp[2].strip
+ end
+ end
+
+end
Please sign in to comment.
Something went wrong with that request. Please try again.