em-websocket 0.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.rdoc +25 -0
- data/Rakefile +19 -0
- data/VERSION +1 -0
- data/examples/echo.rb +7 -0
- data/examples/test.html +22 -0
- data/lib/em-websocket.rb +8 -0
- data/lib/em-websocket/connection.rb +95 -0
- data/lib/em-websocket/websocket.rb +23 -0
- data/spec/helper.rb +6 -0
- data/spec/websocket_spec.rb +52 -0
- metadata +75 -0
data/README.rdoc
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
= EM-WebSocket
|
2
|
+
|
3
|
+
EventMachine based WebSocket server -- highly experimental, at this point, at least.
|
4
|
+
|
5
|
+
== Simple server example
|
6
|
+
|
7
|
+
EventMachine.run {
|
8
|
+
|
9
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
|
10
|
+
ws.onopen {
|
11
|
+
puts "WebSocket connection open"
|
12
|
+
|
13
|
+
# publish message to the client
|
14
|
+
ws.send "Hello Client"
|
15
|
+
}
|
16
|
+
|
17
|
+
ws.onclose { puts "Connection closed" }
|
18
|
+
ws.onmessage { |msg|
|
19
|
+
puts "Recieved message: #{msg}"
|
20
|
+
ws.send "Pong: #{msg}"
|
21
|
+
}
|
22
|
+
end
|
23
|
+
}
|
24
|
+
|
25
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gemspec|
|
6
|
+
gemspec.name = "em-websocket"
|
7
|
+
gemspec.summary = "EventMachine based WebSocket server"
|
8
|
+
gemspec.description = gemspec.summary
|
9
|
+
gemspec.email = "ilya@igvita.com"
|
10
|
+
gemspec.homepage = "http://github.com/igrigorik/em-websocket"
|
11
|
+
gemspec.authors = ["Ilya Grigorik"]
|
12
|
+
gemspec.add_dependency("eventmachine", ">= 0.12.9")
|
13
|
+
gemspec.rubyforge_project = "em-websocket"
|
14
|
+
end
|
15
|
+
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
19
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/examples/echo.rb
ADDED
data/examples/test.html
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'></script>
|
4
|
+
<script>
|
5
|
+
$(document).ready(function(){
|
6
|
+
function debug(str){ $("#debug").append("<p>" + str); };
|
7
|
+
|
8
|
+
ws = new WebSocket("ws://localhost:8080/");
|
9
|
+
ws.onmessage = function(evt) { $("#msg").append("<p>"+evt.data+"</p>"); };
|
10
|
+
ws.onclose = function() { debug("socket closed"); };
|
11
|
+
ws.onopen = function() {
|
12
|
+
debug("connected...");
|
13
|
+
ws.send("hello server");
|
14
|
+
};
|
15
|
+
});
|
16
|
+
</script>
|
17
|
+
</head>
|
18
|
+
<body>
|
19
|
+
<div id="debug"></div>
|
20
|
+
<div id="msg"></div>
|
21
|
+
</body>
|
22
|
+
</html>
|
data/lib/em-websocket.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module WebSocket
|
3
|
+
class Connection < EventMachine::Connection
|
4
|
+
|
5
|
+
# define WebSocket callbacks
|
6
|
+
def onopen(&blk); @onopen = blk; end
|
7
|
+
def onclose(&blk); @onclode = blk; end
|
8
|
+
def onmessage(&blk); @onmessage = blk; end
|
9
|
+
|
10
|
+
def initialize(options)
|
11
|
+
@options = options
|
12
|
+
@debug = options[:debug] || false
|
13
|
+
@state = :handshake
|
14
|
+
|
15
|
+
debug [:initialize]
|
16
|
+
end
|
17
|
+
|
18
|
+
def receive_data(data)
|
19
|
+
debug [:receive_data, data]
|
20
|
+
|
21
|
+
dispatch(data)
|
22
|
+
end
|
23
|
+
|
24
|
+
def unbind
|
25
|
+
debug [:unbind, :connection]
|
26
|
+
@onclose.call if @onclose
|
27
|
+
end
|
28
|
+
|
29
|
+
def dispatch(data = nil)
|
30
|
+
while case @state
|
31
|
+
when :handshake
|
32
|
+
new_request(data)
|
33
|
+
when :upgrade
|
34
|
+
send_upgrade
|
35
|
+
when :connected
|
36
|
+
process_message(data)
|
37
|
+
else raise RuntimeError, "invalid state: #{@state}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def new_request(data)
|
43
|
+
# TODO: verify WS headers
|
44
|
+
@state = :upgrade
|
45
|
+
end
|
46
|
+
|
47
|
+
def send_upgrade
|
48
|
+
upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
|
49
|
+
upgrade << "Upgrade: WebSocket\r\n"
|
50
|
+
upgrade << "Connection: Upgrade\r\n"
|
51
|
+
upgrade << "WebSocket-Origin: file://\r\n"
|
52
|
+
upgrade << "WebSocket-Location: ws://localhost:8080/\r\n\r\n"
|
53
|
+
|
54
|
+
# upgrade connection and notify client callback
|
55
|
+
# about completed handshake
|
56
|
+
send_data upgrade
|
57
|
+
|
58
|
+
@state = :connected
|
59
|
+
@onopen.call if @onopen
|
60
|
+
|
61
|
+
# stop dispatch, wait for messages
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
def process_message(data)
|
66
|
+
debug [:message, data]
|
67
|
+
@onmessage.call(data) if @onmessage
|
68
|
+
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
# should only be invoked after handshake, otherwise it
|
73
|
+
# will inject data into the header exchange
|
74
|
+
#
|
75
|
+
# frames need to start with 0x00-0x7f byte and end with
|
76
|
+
# an 0xFF byte. Per spec, we can also set the first
|
77
|
+
# byte to a value betweent 0x80 and 0xFF, followed by
|
78
|
+
# a leading length indicator
|
79
|
+
def send(data)
|
80
|
+
debug [:send, data]
|
81
|
+
send_data("\x00#{data}\xff")
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def debug(*data)
|
87
|
+
if @debug
|
88
|
+
require 'pp'
|
89
|
+
pp data
|
90
|
+
puts
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module WebSocket
|
3
|
+
|
4
|
+
def self.start(options, &blk)
|
5
|
+
EM.epoll
|
6
|
+
EM.run do
|
7
|
+
|
8
|
+
trap("TERM") { stop }
|
9
|
+
trap("INT") { stop }
|
10
|
+
|
11
|
+
EventMachine::start_server(options[:host], options[:port],
|
12
|
+
EventMachine::WebSocket::Connection, options) do |c|
|
13
|
+
blk.call(c)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.stop
|
19
|
+
puts "Terminating WebSocket Server"
|
20
|
+
EventMachine.stop
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec/helper'
|
2
|
+
|
3
|
+
describe EventMachine::WebSocket do
|
4
|
+
|
5
|
+
def failed
|
6
|
+
EventMachine.stop
|
7
|
+
fail
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should automatically complete WebSocket handshake" do
|
11
|
+
EM.run do
|
12
|
+
MSG = "Hello World!"
|
13
|
+
EventMachine.add_timer(0.1) do
|
14
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:8080/').get :timeout => 0
|
15
|
+
http.errback { failed }
|
16
|
+
http.callback { http.response_header.status.should == 101 }
|
17
|
+
|
18
|
+
http.stream { |msg|
|
19
|
+
msg.should == MSG
|
20
|
+
EventMachine.stop
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
|
25
|
+
ws.onopen {
|
26
|
+
puts "WebSocket connection open"
|
27
|
+
ws.send MSG
|
28
|
+
}
|
29
|
+
|
30
|
+
# TODO: need .terminate method on EM-http to invoke & test .onclose callback
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should fail on non WebSocket requests" do
|
36
|
+
pending
|
37
|
+
|
38
|
+
EM.run do
|
39
|
+
EventMachine.add_timer(0.1) do
|
40
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :timeout => 0
|
41
|
+
http.errback { failed }
|
42
|
+
http.callback {
|
43
|
+
http.response_header.status.should == 400
|
44
|
+
EventMachine.stop
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080, :debug => true) {}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-websocket
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ilya Grigorik
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-22 00:00:00 -05: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.9
|
24
|
+
version:
|
25
|
+
description: EventMachine based WebSocket server
|
26
|
+
email: ilya@igvita.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.rdoc
|
33
|
+
files:
|
34
|
+
- README.rdoc
|
35
|
+
- Rakefile
|
36
|
+
- VERSION
|
37
|
+
- examples/echo.rb
|
38
|
+
- examples/test.html
|
39
|
+
- lib/em-websocket.rb
|
40
|
+
- lib/em-websocket/connection.rb
|
41
|
+
- lib/em-websocket/websocket.rb
|
42
|
+
- spec/helper.rb
|
43
|
+
- spec/websocket_spec.rb
|
44
|
+
has_rdoc: true
|
45
|
+
homepage: http://github.com/igrigorik/em-websocket
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options:
|
50
|
+
- --charset=UTF-8
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project: em-websocket
|
68
|
+
rubygems_version: 1.3.5
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: EventMachine based WebSocket server
|
72
|
+
test_files:
|
73
|
+
- spec/helper.rb
|
74
|
+
- spec/websocket_spec.rb
|
75
|
+
- examples/echo.rb
|