em-websocket-server 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +0 -0
- data/em-websocket-server.gemspec +23 -0
- data/examples/chat.html +38 -0
- data/examples/chat.rb +32 -0
- data/lib/em-websocket-server.rb +164 -0
- metadata +68 -0
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
|
data/examples/chat.html
ADDED
@@ -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
|
+
|