em-websocket-server 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
File without changes
@@ -0,0 +1,23 @@
1
+ spec = Gem::Specification.new do |s|
2
+ s.name = 'em-websocket-server'
3
+ s.version = '0.1'
4
+ s.date = '2009-12-14'
5
+ s.summary = 'An evented ruby websocket server built on top of EventMachine'
6
+ s.email = "dan.simpson@gmail.com"
7
+ s.homepage = "http://github.com/dansimpson/em-websocket-server"
8
+ s.description = "An evented ruby websocket server built on top of EventMachine"
9
+ s.has_rdoc = false
10
+
11
+
12
+ s.authors = ["Dan Simpson"]
13
+ s.add_dependency('eventmachine', '>= 0.12.4')
14
+
15
+
16
+ s.files = [
17
+ "README.markdown",
18
+ "em-websocket-server.gemspec",
19
+ "examples/chat.rb",
20
+ "examples/chat.html",
21
+ "lib/em-websocket-server.rb"
22
+ ]
23
+ end
@@ -0,0 +1,38 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>node.websocket.js test</title>
5
+
6
+ <style type="text/css" media="screen">
7
+ #time { background: #ccc; }
8
+ </style>
9
+ </head>
10
+ <body>
11
+
12
+ <h1>websocketed demo</h1>
13
+
14
+ <h2>The time is <span id="time">opening socket</span></h2>
15
+ <p>The information for the time is provided by the server. No Date() or intervals here!</p>
16
+
17
+ <script>
18
+ var webSocket = new WebSocket('ws://192.168.0.2:8000/time');
19
+
20
+ webSocket.onopen = function(event){
21
+ document.getElementById('time').innerHTML = 'waiting for socket';
22
+ webSocket.send('start');
23
+ };
24
+
25
+ webSocket.onmessage = function(event){
26
+ console.log(event);
27
+ var object = JSON.parse(event.data);
28
+ document.getElementById('time').innerHTML = object.time;
29
+ };
30
+
31
+ webSocket.onclose = function(event){
32
+ document.getElementById('time').innerHTML = 'socket closed';
33
+ };
34
+
35
+ </script>
36
+
37
+ </body>
38
+ </html>
data/examples/chat.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+
4
+ class TimeServer < WebSocketServer
5
+
6
+ def on_connect
7
+ @timer = EM.add_periodic_timer(5) do
8
+ sync_time
9
+ end
10
+ end
11
+
12
+ def on_disconnect
13
+ @timer
14
+ end
15
+
16
+ def on_receive msg
17
+ puts "msg rcv"
18
+ end
19
+
20
+ def sync_time
21
+ send_message({ :time => Time.now }.to_json)
22
+ end
23
+
24
+ end
25
+
26
+
27
+ EM.epoll
28
+ EM.set_descriptor_table_size(10240)
29
+
30
+ EM.run do
31
+ EM.start_server "0.0.0.0", 8000, TimeServer
32
+ end
@@ -0,0 +1,164 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+
4
+ class WebSocketServer < EM::Connection
5
+
6
+ @@logger = nil
7
+ @@num_connections = 0
8
+ @@path_regex = /^GET (\/[^\s]*) HTTP\/1\.1$/
9
+ @@header_regex = /^([^:]+):\s*([^$]+)/
10
+ @@callbacks = {}
11
+ @@accepted_origins = []
12
+
13
+ attr_accessor :connected,
14
+ :headers,
15
+ :path
16
+
17
+ def initialize *args
18
+ super
19
+ @connected = false
20
+ end
21
+
22
+ def valid_origin?
23
+ true
24
+ #@@accepted_origins.include?(@headers["Origin"])
25
+ end
26
+
27
+ def valid_path?
28
+ true
29
+ #@@callbacks.key?(@path)
30
+ end
31
+
32
+ def valid_upgrade?
33
+ true
34
+ #@headers["Upgrade"] =~ /websocket/i
35
+ end
36
+
37
+ def origin
38
+ @headers["Origin"]
39
+ end
40
+
41
+ def host
42
+ @headers["Host"]
43
+ end
44
+
45
+ def self.path name, &block
46
+ @@callbacks[name] = block
47
+ end
48
+
49
+ #tcp connection established
50
+ def post_init
51
+ @@num_connections += 1
52
+ end
53
+
54
+ #must be public for em
55
+ def unbind
56
+ @@num_connections -= 1
57
+ on_disconnect
58
+ end
59
+
60
+ # Frames need to start with 0x00-0x7f byte and end with
61
+ # an 0xFF byte. Per spec, we can also set the first
62
+ # byte to a value betweent 0x80 and 0xFF, followed by
63
+ # a leading length indicator. No support yet
64
+ def send_message msg
65
+ send_data "\x00#{msg}\xff"
66
+ end
67
+
68
+ protected
69
+
70
+ #override this method
71
+ def on_receive msg
72
+ log msg
73
+ end
74
+
75
+ #override this method
76
+ def on_connect
77
+ log "connected"
78
+ end
79
+
80
+ #override this method
81
+ def on_disconnect
82
+ log "disconnected"
83
+ end
84
+
85
+ def log msg
86
+ if @@logger
87
+ @@logger.info msg
88
+ else
89
+ puts msg
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ # when the connection receives data from the client
96
+ # we either handshake, accept the start command
97
+ # or handle the message at the app layer
98
+ def receive_data data
99
+ unless @connected
100
+ handshake data
101
+ else
102
+ msg = data.gsub(/^(\x00)|(\xff)$/, "")
103
+
104
+ if msg == "start"
105
+ on_connect
106
+ else
107
+ on_receive msg
108
+ end
109
+ end
110
+ end
111
+
112
+ # parse the headers, validate the origin and path
113
+ # and respond with appropiate headers for a
114
+ # healthy relationship with the client
115
+ def handshake data
116
+
117
+ #convert the headers to a hash
118
+ parse_headers data
119
+
120
+ # close the connection if the connection
121
+ # originates from an invalid source
122
+ close_connection unless valid_origin?
123
+
124
+ # close the connection if a callback
125
+ # is not registered for the path
126
+ close_connection unless valid_path?
127
+
128
+ # don't respond to non-websocket HTTP requests
129
+ close_connection unless valid_upgrade?
130
+
131
+ #complete the handshake
132
+ send_headers
133
+
134
+ @connected = true
135
+ end
136
+
137
+ # send the handshake response headers to
138
+ # complete the initial com
139
+ def send_headers
140
+
141
+ response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
142
+ response << "Upgrade: WebSocket\r\n"
143
+ response << "Connection: Upgrade\r\n"
144
+ response << "WebSocket-Origin: #{origin}\r\n"
145
+ response << "WebSocket-Location: ws://#{host}#{path}\r\n\r\n"
146
+
147
+ send_data response
148
+ end
149
+
150
+ # turn http style headers into a ruby hash
151
+ # TODO: this is probably not done "well"
152
+ def parse_headers data
153
+ lines = data.split("\r\n")
154
+
155
+ @path = @@path_regex.match(lines.shift)[1]
156
+ @headers = {}
157
+
158
+ lines.each do |line|
159
+ kvp = @@header_regex.match(line)
160
+ @headers[kvp[1].strip] = kvp[2].strip
161
+ end
162
+ end
163
+
164
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-websocket-server
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Dan Simpson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-14 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: eventmachine
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.12.4
24
+ version:
25
+ description: An evented ruby websocket server built on top of EventMachine
26
+ email: dan.simpson@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - README.markdown
35
+ - em-websocket-server.gemspec
36
+ - examples/chat.rb
37
+ - examples/chat.html
38
+ - lib/em-websocket-server.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/dansimpson/em-websocket-server
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.5
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: An evented ruby websocket server built on top of EventMachine
67
+ test_files: []
68
+