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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 / 2011-05-10
4
+
5
+ - support for WebSocket drafts 05 and 06
6
+ - fix tests in Ruby 1.9.2
7
+ - better documentation
8
+
3
9
  ## 0.2.1 / 2011-04-01
4
10
 
5
11
  - bugfix: env passed to callbacks should be valid now
data/example/example.ru CHANGED
@@ -1,4 +1,4 @@
1
- require 'lib/rack/websocket'
1
+ require './lib/rack/websocket'
2
2
 
3
3
  class MyApp < Rack::WebSocket::Application
4
4
  def on_open(env)
@@ -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
@@ -11,6 +11,7 @@ module Rack
11
11
  end
12
12
  end
13
13
 
14
+ # Set 'async.connection' Rack env
14
15
  def pre_process_with_websocket
15
16
  @request.env['async.connection'] = self
16
17
  pre_process_without_websocket
@@ -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 Thin
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
- def trigger_on_message(msg)
17
- @app.on_message(msg)
18
- end
19
- def trigger_on_open
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
- def trigger_on_error(error)
26
- @app.on_error(error)
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
- def dispatch(request)
49
- return false if request.nil?
50
- debug [:inbound_headers, request]
51
- @handler = HandlerFactory.build_with_request(self, request, request['body'], @ssl, @debug)
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
@@ -3,6 +3,7 @@ module Rack
3
3
  module Handler
4
4
  class Stub < Base
5
5
 
6
+ # Always close socket
6
7
  def call(env)
7
8
  raise 'Unknown handler!'
8
9
  close_websocket
@@ -5,50 +5,47 @@ module Rack
5
5
  module Handler
6
6
  class Thin < Base
7
7
 
8
- autoload :Connection, "#{ROOT_PATH}/websocket/handler/thin/connection"
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
- @conn = Connection.new(self, socket, :debug => @options[:debug])
16
- @conn.dispatch(request) ? async_response : failure_response
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 @conn
21
- @conn.send data
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 @conn
29
- @conn.close_websocket
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['Path'] = env['REQUEST_PATH'].to_s
40
- request['Method'] = env['REQUEST_METHOD']
41
- request['Query'] = env['QUERY_STRING'].to_s
42
- request['body'] = env['rack.input'].read
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.capitalize.gsub('_','-')] ||= value
48
+ request[$1.downcase.gsub('_','-')] ||= value
52
49
  end
53
50
  end
54
51
 
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  module WebSocket
3
- VERSION = "0.2.1"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
data/spec/thin_spec.rb CHANGED
@@ -30,6 +30,7 @@ def stop_thin_server
30
30
  sleep 0.1
31
31
  @server.stop!
32
32
  @thread.kill
33
+ sleep 0.1
33
34
  raise "Reactor still running, wtf?" if EventMachine.reactor_running?
34
35
  end
35
36
 
@@ -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.2.1'
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
- segments:
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-04-01 00:00:00 +02:00
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
- hash: 21
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.2
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