websocket-rack 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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