websocket-rack 0.2.1 → 0.3.0
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/CHANGELOG.md +6 -0
- data/example/example.ru +1 -1
- data/lib/rack/websocket/application.rb +17 -10
- data/lib/rack/websocket/extensions/common.rb +6 -0
- data/lib/rack/websocket/extensions/thin/connection.rb +1 -0
- data/lib/rack/websocket/handler.rb +3 -2
- data/lib/rack/websocket/handler/base.rb +8 -0
- data/lib/rack/websocket/handler/{thin → base}/connection.rb +36 -40
- data/lib/rack/websocket/handler/stub.rb +1 -0
- data/lib/rack/websocket/handler/thin.rb +18 -21
- data/lib/rack/websocket/version.rb +1 -1
- data/spec/thin_spec.rb +1 -0
- data/websocket-rack.gemspec +1 -1
- metadata +5 -36
- data/lib/rack/websocket/handler/thin/handler_factory.rb +0 -56
data/CHANGELOG.md
CHANGED
data/example/example.ru
CHANGED
@@ -1,31 +1,36 @@
|
|
1
1
|
module Rack
|
2
2
|
module WebSocket
|
3
3
|
class Application
|
4
|
-
|
4
|
+
|
5
5
|
DEFAULT_OPTIONS = {}
|
6
|
-
|
6
|
+
|
7
7
|
class << self
|
8
8
|
attr_accessor :websocket_handler
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
|
+
# Standard WebSocket calls
|
11
12
|
def on_open(env); end
|
12
13
|
def on_message(env, msg); end
|
13
14
|
def on_close(env); end
|
14
15
|
def on_error(env, error); end
|
15
|
-
|
16
|
+
|
17
|
+
# Initializer
|
16
18
|
def initialize(options = {})
|
17
19
|
@options = DEFAULT_OPTIONS.merge(options)
|
18
20
|
end
|
19
|
-
|
21
|
+
|
22
|
+
# Detect handler and duplicate it's instance
|
20
23
|
def call(env)
|
21
24
|
detect_handler(env)
|
22
25
|
dup._call(env)
|
23
26
|
end
|
24
|
-
|
27
|
+
|
28
|
+
# Forward call to duplicated handler
|
25
29
|
def _call(env)
|
26
30
|
websocket_handler.call(env)
|
27
31
|
end
|
28
|
-
|
32
|
+
|
33
|
+
# Forward all missing methods to handler
|
29
34
|
def method_missing(sym, *args, &block)
|
30
35
|
if websocket_handler && websocket_handler.respond_to?(sym)
|
31
36
|
websocket_handler.send sym, *args, &block
|
@@ -33,13 +38,15 @@ module Rack
|
|
33
38
|
super
|
34
39
|
end
|
35
40
|
end
|
36
|
-
|
41
|
+
|
37
42
|
private
|
38
|
-
|
43
|
+
|
44
|
+
# Detect handler
|
39
45
|
def detect_handler(env)
|
40
46
|
self.class.websocket_handler ||= Handler.detect(env)
|
41
47
|
end
|
42
|
-
|
48
|
+
|
49
|
+
# Create and cache handler for current server
|
43
50
|
def websocket_handler
|
44
51
|
@websocket_handler ||= self.class.websocket_handler.new(self, @options || {})
|
45
52
|
end
|
@@ -18,10 +18,13 @@ module Rack
|
|
18
18
|
|
19
19
|
attr_accessor :websocket
|
20
20
|
|
21
|
+
# Is this connection WebSocket?
|
21
22
|
def websocket?
|
22
23
|
!self.websocket.nil?
|
23
24
|
end
|
24
25
|
|
26
|
+
# Skip default receive_data if this is
|
27
|
+
# WebSocket connection
|
25
28
|
def receive_data_with_websocket(data)
|
26
29
|
if self.websocket?
|
27
30
|
self.websocket.receive_data(data)
|
@@ -30,6 +33,8 @@ module Rack
|
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
36
|
+
# Skip standard unbind it this is
|
37
|
+
# WebSocket connection
|
33
38
|
def unbind_with_websocket
|
34
39
|
if self.websocket?
|
35
40
|
self.websocket.unbind
|
@@ -38,6 +43,7 @@ module Rack
|
|
38
43
|
end
|
39
44
|
end
|
40
45
|
|
46
|
+
# Send flash policy file if requested
|
41
47
|
def receive_data_with_flash_policy_file(data)
|
42
48
|
# thin require data to be proper http request - in it's not
|
43
49
|
# then @request.parse raises exception and data isn't parsed
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module Rack
|
2
2
|
module WebSocket
|
3
3
|
module Handler
|
4
|
-
|
4
|
+
|
5
5
|
autoload :Base, "#{ROOT_PATH}/websocket/handler/base"
|
6
6
|
autoload :Stub, "#{ROOT_PATH}/websocket/handler/stub"
|
7
7
|
autoload :Thin, "#{ROOT_PATH}/websocket/handler/thin"
|
8
|
-
|
8
|
+
|
9
|
+
# Detect current server using software Rack string
|
9
10
|
def self.detect(env)
|
10
11
|
server_software = env['SERVER_SOFTWARE']
|
11
12
|
if server_software.match(/\Athin /i)
|
@@ -3,34 +3,42 @@ module Rack
|
|
3
3
|
module Handler
|
4
4
|
class Base
|
5
5
|
|
6
|
+
autoload :Connection, "#{ROOT_PATH}/websocket/handler/base/connection"
|
7
|
+
|
6
8
|
def on_open; @parent.on_open(@env); end # Fired when a client is connected.
|
7
9
|
def on_message(msg); @parent.on_message(@env, msg); end # Fired when a message from a client is received.
|
8
10
|
def on_close; @parent.on_close(@env); end # Fired when a client is disconnected.
|
9
11
|
def on_error(error); @parent.on_error(@env, error); end # Fired when error occurs.
|
10
12
|
|
13
|
+
# Set application as parent and forward options
|
11
14
|
def initialize(parent, options = {})
|
12
15
|
@parent = parent
|
13
16
|
@options = options[:backend] || {}
|
14
17
|
end
|
15
18
|
|
19
|
+
# Implemented in subclass
|
16
20
|
def call(env)
|
17
21
|
raise 'Not implemented'
|
18
22
|
end
|
19
23
|
|
24
|
+
# Implemented in subclass
|
20
25
|
def send_data(data)
|
21
26
|
raise 'Not implemented'
|
22
27
|
end
|
23
28
|
|
29
|
+
# Implemented in subclass
|
24
30
|
def close_websocket
|
25
31
|
raise 'Not implemented'
|
26
32
|
end
|
27
33
|
|
28
34
|
protected
|
29
35
|
|
36
|
+
# Standard async response
|
30
37
|
def async_response
|
31
38
|
[-1, {}, []]
|
32
39
|
end
|
33
40
|
|
41
|
+
# Standard 400 response
|
34
42
|
def failure_response
|
35
43
|
[ 400, {'Content-Type' => 'text/plain'}, [ 'Bad request' ] ]
|
36
44
|
end
|
@@ -3,29 +3,47 @@ require 'addressable/uri'
|
|
3
3
|
module Rack
|
4
4
|
module WebSocket
|
5
5
|
module Handler
|
6
|
-
class
|
6
|
+
class Base
|
7
7
|
class Connection < ::EventMachine::WebSocket::Connection
|
8
8
|
|
9
|
+
#########################
|
10
|
+
### EventMachine part ###
|
11
|
+
#########################
|
12
|
+
|
9
13
|
# Overwrite new from EventMachine
|
14
|
+
# we need to skip standard procedure called
|
15
|
+
# when socket is created - this is just a stub
|
10
16
|
def self.new(*args)
|
11
17
|
instance = allocate
|
12
18
|
instance.__send__(:initialize, *args)
|
13
19
|
instance
|
14
20
|
end
|
15
21
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@app.on_open
|
21
|
-
end
|
22
|
-
def trigger_on_close
|
23
|
-
@app.on_close
|
22
|
+
# Overwrite send_data from EventMachine
|
23
|
+
# delegate send_data to rack server
|
24
|
+
def send_data(*args)
|
25
|
+
@socket.send_data(*args)
|
24
26
|
end
|
25
|
-
|
26
|
-
|
27
|
+
|
28
|
+
# Overwrite close_connection from EventMachine
|
29
|
+
# delegate close_connection to rack server
|
30
|
+
def close_connection(*args)
|
31
|
+
@socket.close_connection(*args)
|
27
32
|
end
|
28
33
|
|
34
|
+
#########################
|
35
|
+
### EM-WebSocket part ###
|
36
|
+
#########################
|
37
|
+
|
38
|
+
# Overwrite triggers from em-websocket
|
39
|
+
def trigger_on_message(msg); @app.on_message(msg); end
|
40
|
+
def trigger_on_open; @app.on_open; end
|
41
|
+
def trigger_on_close; @app.on_close; end
|
42
|
+
def trigger_on_error(error); @app.on_error(error); true; end
|
43
|
+
|
44
|
+
# Overwrite initialize from em-websocket
|
45
|
+
# set all standard options and disable
|
46
|
+
# EM connection inactivity timeout
|
29
47
|
def initialize(app, socket, options = {})
|
30
48
|
@app = app
|
31
49
|
@socket = socket
|
@@ -45,41 +63,19 @@ module Rack
|
|
45
63
|
debug [:initialize]
|
46
64
|
end
|
47
65
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
66
|
+
# Overwrite dispath from em-websocket
|
67
|
+
# we already have request headers parsed so
|
68
|
+
# we can skip it and call build_with_request
|
69
|
+
def dispatch(data)
|
70
|
+
return false if data.nil?
|
71
|
+
debug [:inbound_headers, data]
|
72
|
+
@handler = EventMachine::WebSocket::HandlerFactory.build_with_request(self, data, data['Body'], @ssl, @debug)
|
52
73
|
unless @handler
|
53
74
|
# The whole header has not been received yet.
|
54
75
|
return false
|
55
76
|
end
|
56
77
|
@handler.run
|
57
78
|
return true
|
58
|
-
rescue => e
|
59
|
-
debug [:error, e]
|
60
|
-
process_bad_request(e)
|
61
|
-
return false
|
62
|
-
end
|
63
|
-
|
64
|
-
def process_bad_request(reason)
|
65
|
-
trigger_on_error(reason)
|
66
|
-
send_data "HTTP/1.1 400 Bad request\r\n\r\n"
|
67
|
-
close_connection_after_writing
|
68
|
-
end
|
69
|
-
|
70
|
-
def close_with_error(message)
|
71
|
-
trigger_on_error(message)
|
72
|
-
close_connection_after_writing
|
73
|
-
end
|
74
|
-
|
75
|
-
# Overwrite send_data from EventMachine
|
76
|
-
def send_data(*args)
|
77
|
-
@socket.send_data(*args)
|
78
|
-
end
|
79
|
-
|
80
|
-
# Overwrite close_connection from EventMachine
|
81
|
-
def close_connection(*args)
|
82
|
-
@socket.close_connection(*args)
|
83
79
|
end
|
84
80
|
|
85
81
|
end
|
@@ -5,50 +5,47 @@ module Rack
|
|
5
5
|
module Handler
|
6
6
|
class Thin < Base
|
7
7
|
|
8
|
-
|
9
|
-
autoload :HandlerFactory, "#{ROOT_PATH}/websocket/handler/thin/handler_factory"
|
10
|
-
|
8
|
+
# Build request from Rack env
|
11
9
|
def call(env)
|
12
10
|
@env = env
|
13
11
|
socket = env['async.connection']
|
14
12
|
request = request_from_env(env)
|
15
|
-
@
|
16
|
-
@
|
13
|
+
@connection = Connection.new(self, socket, :debug => @options[:debug])
|
14
|
+
@connection.dispatch(request) ? async_response : failure_response
|
17
15
|
end
|
18
16
|
|
17
|
+
# Forward send_data to server
|
19
18
|
def send_data(data)
|
20
|
-
if @
|
21
|
-
@
|
19
|
+
if @connection
|
20
|
+
@connection.send data
|
22
21
|
else
|
23
22
|
raise WebSocketError, "WebSocket not opened"
|
24
23
|
end
|
25
24
|
end
|
26
25
|
|
26
|
+
# Forward close_websocket to server
|
27
27
|
def close_websocket
|
28
|
-
if @
|
29
|
-
@
|
28
|
+
if @connection
|
29
|
+
@connection.close_websocket
|
30
30
|
else
|
31
31
|
raise WebSocketError, "WebSocket not opened"
|
32
32
|
end
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
private
|
36
|
-
|
36
|
+
|
37
|
+
# Parse Rack env to em-websocket-compatible format
|
38
|
+
# this probably should be moved to Base in future
|
37
39
|
def request_from_env(env)
|
38
40
|
request = {}
|
39
|
-
request['
|
40
|
-
request['
|
41
|
-
request['
|
42
|
-
request['
|
43
|
-
|
44
|
-
request['Sec-WebSocket-Draft'] = env['HTTP_SEC_WEBSOCKET_DRAFT']
|
45
|
-
request['Sec-WebSocket-Key1'] = env['HTTP_SEC_WEBSOCKET_KEY1']
|
46
|
-
request['Sec-WebSocket-Key2'] = env['HTTP_SEC_WEBSOCKET_KEY2']
|
47
|
-
request['Sec-WebSocket-Protocol'] = env['HTTP_SEC_WEBSOCKET_PROTOCOL']
|
41
|
+
request['path'] = env['REQUEST_PATH'].to_s
|
42
|
+
request['method'] = env['REQUEST_METHOD']
|
43
|
+
request['query'] = env['QUERY_STRING'].to_s
|
44
|
+
request['Body'] = env['rack.input'].read
|
48
45
|
|
49
46
|
env.each do |key, value|
|
50
47
|
if key.match(/HTTP_(.+)/)
|
51
|
-
request[$1.
|
48
|
+
request[$1.downcase.gsub('_','-')] ||= value
|
52
49
|
end
|
53
50
|
end
|
54
51
|
|
data/spec/thin_spec.rb
CHANGED
data/websocket-rack.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.description = %q{Rack-based WebSocket server}
|
14
14
|
|
15
15
|
s.add_dependency 'rack'
|
16
|
-
s.add_dependency 'em-websocket', '~> 0.
|
16
|
+
s.add_dependency 'em-websocket', '~> 0.3.0'
|
17
17
|
s.add_dependency 'thin' # Temporary until we support more servers
|
18
18
|
s.add_development_dependency 'rspec', '~> 2.4.0'
|
19
19
|
s.add_development_dependency 'mocha'
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: websocket-rack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 21
|
5
4
|
prerelease:
|
6
|
-
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
- 1
|
10
|
-
version: 0.2.1
|
5
|
+
version: 0.3.0
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Bernard Potocki
|
@@ -15,7 +10,7 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-
|
13
|
+
date: 2011-05-10 00:00:00 +02:00
|
19
14
|
default_executable:
|
20
15
|
dependencies:
|
21
16
|
- !ruby/object:Gem::Dependency
|
@@ -26,9 +21,6 @@ dependencies:
|
|
26
21
|
requirements:
|
27
22
|
- - ">="
|
28
23
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 3
|
30
|
-
segments:
|
31
|
-
- 0
|
32
24
|
version: "0"
|
33
25
|
type: :runtime
|
34
26
|
version_requirements: *id001
|
@@ -40,12 +32,7 @@ dependencies:
|
|
40
32
|
requirements:
|
41
33
|
- - ~>
|
42
34
|
- !ruby/object:Gem::Version
|
43
|
-
|
44
|
-
segments:
|
45
|
-
- 0
|
46
|
-
- 2
|
47
|
-
- 1
|
48
|
-
version: 0.2.1
|
35
|
+
version: 0.3.0
|
49
36
|
type: :runtime
|
50
37
|
version_requirements: *id002
|
51
38
|
- !ruby/object:Gem::Dependency
|
@@ -56,9 +43,6 @@ dependencies:
|
|
56
43
|
requirements:
|
57
44
|
- - ">="
|
58
45
|
- !ruby/object:Gem::Version
|
59
|
-
hash: 3
|
60
|
-
segments:
|
61
|
-
- 0
|
62
46
|
version: "0"
|
63
47
|
type: :runtime
|
64
48
|
version_requirements: *id003
|
@@ -70,11 +54,6 @@ dependencies:
|
|
70
54
|
requirements:
|
71
55
|
- - ~>
|
72
56
|
- !ruby/object:Gem::Version
|
73
|
-
hash: 31
|
74
|
-
segments:
|
75
|
-
- 2
|
76
|
-
- 4
|
77
|
-
- 0
|
78
57
|
version: 2.4.0
|
79
58
|
type: :development
|
80
59
|
version_requirements: *id004
|
@@ -86,9 +65,6 @@ dependencies:
|
|
86
65
|
requirements:
|
87
66
|
- - ">="
|
88
67
|
- !ruby/object:Gem::Version
|
89
|
-
hash: 3
|
90
|
-
segments:
|
91
|
-
- 0
|
92
68
|
version: "0"
|
93
69
|
type: :development
|
94
70
|
version_requirements: *id005
|
@@ -122,10 +98,9 @@ files:
|
|
122
98
|
- lib/rack/websocket/extensions/thin/connection.rb
|
123
99
|
- lib/rack/websocket/handler.rb
|
124
100
|
- lib/rack/websocket/handler/base.rb
|
101
|
+
- lib/rack/websocket/handler/base/connection.rb
|
125
102
|
- lib/rack/websocket/handler/stub.rb
|
126
103
|
- lib/rack/websocket/handler/thin.rb
|
127
|
-
- lib/rack/websocket/handler/thin/connection.rb
|
128
|
-
- lib/rack/websocket/handler/thin/handler_factory.rb
|
129
104
|
- lib/rack/websocket/version.rb
|
130
105
|
- spec/spec_helper.rb
|
131
106
|
- spec/support/all_drafts.rb
|
@@ -147,23 +122,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
147
122
|
requirements:
|
148
123
|
- - ">="
|
149
124
|
- !ruby/object:Gem::Version
|
150
|
-
hash: 3
|
151
|
-
segments:
|
152
|
-
- 0
|
153
125
|
version: "0"
|
154
126
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
127
|
none: false
|
156
128
|
requirements:
|
157
129
|
- - ">="
|
158
130
|
- !ruby/object:Gem::Version
|
159
|
-
hash: 3
|
160
|
-
segments:
|
161
|
-
- 0
|
162
131
|
version: "0"
|
163
132
|
requirements: []
|
164
133
|
|
165
134
|
rubyforge_project:
|
166
|
-
rubygems_version: 1.6.
|
135
|
+
rubygems_version: 1.6.1
|
167
136
|
signing_key:
|
168
137
|
specification_version: 3
|
169
138
|
summary: Rack-based WebSocket server
|
@@ -1,56 +0,0 @@
|
|
1
|
-
module Rack
|
2
|
-
module WebSocket
|
3
|
-
module Handler
|
4
|
-
class Thin
|
5
|
-
class HandlerFactory < ::EventMachine::WebSocket::HandlerFactory
|
6
|
-
|
7
|
-
# Bottom half of em-websocket HandlerFactory
|
8
|
-
# Taken from http://github.com/dj2/em-websocket
|
9
|
-
# This method is also used in experimental branch of Goliath
|
10
|
-
def self.build_with_request(connection, request, remains, secure = false, debug = false)
|
11
|
-
version = request['Sec-WebSocket-Key1'] ? 76 : 75
|
12
|
-
case version
|
13
|
-
when 75
|
14
|
-
if !remains.empty?
|
15
|
-
raise ::EventMachine::WebSocket::HandshakeError, "Extra bytes after header"
|
16
|
-
end
|
17
|
-
when 76
|
18
|
-
if remains.length < 8
|
19
|
-
# The whole third-key has not been received yet.
|
20
|
-
return nil
|
21
|
-
elsif remains.length > 8
|
22
|
-
raise ::EventMachine::WebSocket::HandshakeError, "Extra bytes after third key"
|
23
|
-
end
|
24
|
-
request['Third-Key'] = remains
|
25
|
-
else
|
26
|
-
raise ::EventMachine::WebSocket::WebSocketError, "Must not happen"
|
27
|
-
end
|
28
|
-
|
29
|
-
unless request['Connection'] == 'Upgrade' and request['Upgrade'] == 'WebSocket'
|
30
|
-
raise ::EventMachine::WebSocket::HandshakeError, "Connection and Upgrade headers required"
|
31
|
-
end
|
32
|
-
|
33
|
-
# transform headers
|
34
|
-
protocol = (secure ? "wss" : "ws")
|
35
|
-
request['Host'] = Addressable::URI.parse("#{protocol}://"+request['Host'])
|
36
|
-
|
37
|
-
if version = request['Sec-WebSocket-Draft']
|
38
|
-
if version == '1' || version == '2' || version == '3'
|
39
|
-
# We'll use handler03 - I believe they're all compatible
|
40
|
-
::EventMachine::WebSocket::Handler03.new(connection, request, debug)
|
41
|
-
else
|
42
|
-
# According to spec should abort the connection
|
43
|
-
raise ::EventMachine::WebSocket::WebSocketError, "Unknown draft version: #{version}"
|
44
|
-
end
|
45
|
-
elsif request['Sec-WebSocket-Key1']
|
46
|
-
::EventMachine::WebSocket::Handler76.new(connection, request, debug)
|
47
|
-
else
|
48
|
-
::EventMachine::WebSocket::Handler75.new(connection, request, debug)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|