Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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.