faye-websocket 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of faye-websocket might be problematic. Click here for more details.

@@ -16,22 +16,26 @@
16
16
  socket = new Socket('ws://' + location.hostname + ':' + location.port + '/', protos),
17
17
  index = 0;
18
18
 
19
- socket.onopen = function() {
20
- logger.innerHTML += '<li>OPEN: ' + socket.protocol + '</li>';
21
- socket.send('Hello, world');
19
+ var log = function(text) {
20
+ logger.innerHTML += '<li>' + text + '</li>';
22
21
  };
23
22
 
23
+ socket.addEventListener('open', function() {
24
+ log('OPEN: ' + socket.protocol);
25
+ socket.send('Hello, world');
26
+ });
27
+
24
28
  socket.onerror = function(event) {
25
- logger.innerHTML += '<li>ERROR: ' + error.message + '</li>';
29
+ log('ERROR: ' + event.message);
26
30
  };
27
31
 
28
- socket.addEventListener('message', function(event) {
29
- logger.innerHTML += '<li>MESSAGE: ' + event.data + '</li>';
32
+ socket.onmessage = function(event) {
33
+ log('MESSAGE: ' + event.data);
30
34
  setTimeout(function() { socket.send(++index + ' ' + event.data) }, 2000);
31
- });
35
+ };
32
36
 
33
37
  socket.onclose = function(event) {
34
- logger.innerHTML += '<li>CLOSE: ' + event.code + ', ' + event.reason + '</li>';
38
+ log('CLOSE: ' + event.code + ', ' + event.reason);
35
39
  };
36
40
  </script>
37
41
 
@@ -0,0 +1,47 @@
1
+ class Goliath::Connection
2
+ attr_accessor :socket_stream
3
+ alias :goliath_receive_data :receive_data
4
+
5
+ def receive_data(data)
6
+ if @serving == :websocket
7
+ socket_stream.receive(data) if socket_stream
8
+ else
9
+ goliath_receive_data(data)
10
+ socket_stream.receive(@parser.upgrade_data) if socket_stream
11
+ @serving = :websocket if @api.websocket?
12
+ end
13
+ end
14
+
15
+ def unbind
16
+ super
17
+ ensure
18
+ socket_stream.fail if socket_stream
19
+ end
20
+ end
21
+
22
+ class Goliath::API
23
+ include Faye::WebSocket::Adapter
24
+ end
25
+
26
+ class Goliath::Request
27
+ alias :goliath_process :process
28
+
29
+ def process
30
+ env['em.connection'] = conn
31
+ goliath_process
32
+ end
33
+ end
34
+
35
+ class Goliath::Response
36
+ alias :goliath_head :head
37
+ alias :goliath_headers_output :headers_output
38
+
39
+ def head
40
+ (status == 101) ? '' : goliath_head
41
+ end
42
+
43
+ def headers_output
44
+ (status == 101) ? '' : goliath_headers_output
45
+ end
46
+ end
47
+
@@ -0,0 +1,32 @@
1
+ # WebSocket extensions for Rainbows
2
+ # Based on code from the Cramp project
3
+ # http://github.com/lifo/cramp
4
+
5
+ # Copyright (c) 2009-2011 Pratik Naik
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ module Faye
27
+ class WebSocket
28
+ autoload :RainbowsClient, File.expand_path('../rainbows_client', __FILE__)
29
+ end
30
+ end
31
+
32
+ Rainbows::O[:em_client_class] = "Faye::WebSocket::RainbowsClient"
@@ -0,0 +1,70 @@
1
+ # WebSocket extensions for Rainbows
2
+ # Based on code from the Cramp project
3
+ # http://github.com/lifo/cramp
4
+
5
+ # Copyright (c) 2009-2011 Pratik Naik
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ module Faye
27
+ class WebSocket
28
+
29
+ class RainbowsClient < Rainbows::EventMachine::Client
30
+ include Faye::WebSocket::Adapter
31
+ attr_accessor :socket_stream
32
+
33
+ def receive_data(data)
34
+ return super unless @state == :websocket
35
+ socket_stream.receive(data) if socket_stream
36
+ end
37
+
38
+ def app_call(*args)
39
+ @env['em.connection'] = self
40
+ if args.first == NULL_IO and @hp.content_length == 0 and websocket?
41
+ prepare_request_body
42
+ else
43
+ super
44
+ end
45
+ end
46
+
47
+ def on_read(data)
48
+ if @state == :body and websocket? and @hp.body_eof?
49
+ @state = :websocket
50
+ @input.rewind
51
+ app_call StringIO.new(@buf)
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+ def unbind
58
+ super
59
+ ensure
60
+ socket_stream.fail if socket_stream
61
+ end
62
+
63
+ def write_headers(*args)
64
+ super unless async_connection?
65
+ end
66
+ end
67
+
68
+ end
69
+ end
70
+
@@ -24,52 +24,39 @@
24
24
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
25
 
26
26
  class Thin::Connection
27
- def receive_data(data)
28
- trace { data }
29
-
30
- case @serving
31
- when :websocket
32
- callback = @request.env[Thin::Request::WEBSOCKET_RECEIVE_CALLBACK]
33
- callback.call(data) if callback
34
- else
35
- if @request.parse(data)
36
- if @request.websocket?
37
- @request.env['em.connection'] = self
38
- @response.persistent!
39
- @response.websocket = true
40
- @serving = :websocket
41
- end
42
-
43
- process
44
- end
27
+ attr_accessor :socket_stream
28
+
29
+ alias :thin_process :process
30
+ alias :thin_receive_data :receive_data
31
+
32
+ def process
33
+ if @serving != :websocket and @request.websocket?
34
+ @serving = :websocket
45
35
  end
46
- rescue Thin::InvalidRequest => e
47
- log "!! Invalid request"
48
- log_error e
49
- close_connection
36
+ if @request.async_connection?
37
+ @request.env['em.connection'] = self
38
+ @response.persistent!
39
+ @response.async = true
40
+ end
41
+ thin_process
42
+ end
43
+
44
+ def receive_data(data)
45
+ return thin_receive_data(data) unless @serving == :websocket
46
+ socket_stream.receive(data) if socket_stream
50
47
  end
51
48
  end
52
49
 
53
50
  class Thin::Request
54
- WEBSOCKET_RECEIVE_CALLBACK = 'websocket.receive_callback'.freeze
55
- def websocket?
56
- @env['HTTP_CONNECTION'] and
57
- @env['HTTP_CONNECTION'].split(/\s*,\s*/).include?('Upgrade') and
58
- ['WebSocket', 'websocket'].include?(@env['HTTP_UPGRADE'])
59
- end
51
+ include Faye::WebSocket::Adapter
60
52
  end
61
53
 
62
54
  class Thin::Response
63
- # Headers for sending Websocket upgrade
64
- attr_accessor :websocket
65
-
66
- def each
67
- yield(head) unless websocket
68
- if @body.is_a?(String)
69
- yield @body
70
- else
71
- @body.each { |chunk| yield chunk }
72
- end
55
+ attr_accessor :async
56
+ alias :thin_head :head
57
+
58
+ def head
59
+ async ? '' : thin_head
73
60
  end
74
61
  end
75
62
 
@@ -0,0 +1,118 @@
1
+ require File.expand_path('../websocket', __FILE__) unless defined?(Faye::WebSocket)
2
+
3
+ module Faye
4
+ class EventSource
5
+ DEFAULT_PING = 10
6
+ DEFAULT_RETRY = 5
7
+
8
+ include WebSocket::API::EventTarget
9
+ include WebSocket::API::ReadyStates
10
+ attr_reader :env, :url, :ready_state
11
+
12
+ def self.eventsource?(env)
13
+ accept = (env['HTTP_ACCEPT'] || '').split(/\s*,\s*/)
14
+ accept.include?('text/event-stream')
15
+ end
16
+
17
+ def self.determine_url(env)
18
+ secure = if env.has_key?('HTTP_X_FORWARDED_PROTO')
19
+ env['HTTP_X_FORWARDED_PROTO'] == 'https'
20
+ else
21
+ env['HTTP_ORIGIN'] =~ /^https:/i
22
+ end
23
+
24
+ scheme = secure ? 'https:' : 'http:'
25
+ "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }"
26
+ end
27
+
28
+ def initialize(env, options = {})
29
+ @env = env
30
+ @ping = options[:ping] || DEFAULT_PING
31
+ @retry = (options[:retry] || DEFAULT_RETRY).to_f
32
+ @url = EventSource.determine_url(env)
33
+ @stream = Stream.new(self)
34
+
35
+ @ready_state = CONNECTING
36
+
37
+ callback = @env['async.callback']
38
+ callback.call([101, {}, @stream])
39
+
40
+ @stream.write("HTTP/1.1 200 OK\r\n" +
41
+ "Content-Type: text/event-stream\r\n" +
42
+ "Cache-Control: no-cache, no-store\r\n" +
43
+ "\r\n\r\n" +
44
+ "retry: #{ (@retry * 1000).floor }\r\n\r\n")
45
+
46
+ @ping_timer = EventMachine.add_periodic_timer(@ping) do
47
+ @stream.write(":\r\n\r\n")
48
+ end
49
+
50
+ @ready_state = OPEN
51
+ end
52
+
53
+ def last_event_id
54
+ @env['HTTP_LAST_EVENT_ID'] || ''
55
+ end
56
+
57
+ def rack_response
58
+ [ -1, {}, [] ]
59
+ end
60
+
61
+ def send(message, options = {})
62
+ return false unless @ready_state == OPEN
63
+
64
+ message = WebSocket.encode(message).
65
+ gsub(/(\r\n|\r|\n)/, '\1data: ')
66
+
67
+ frame = ""
68
+ frame << "event: #{options[:event]}\r\n" if options[:event]
69
+ frame << "id: #{options[:id]}\r\n" if options[:id]
70
+ frame << "data: #{message}\r\n\r\n"
71
+
72
+ @stream.write(frame)
73
+ true
74
+ end
75
+
76
+ def close
77
+ return if [CLOSING, CLOSED].include?(@ready_state)
78
+ @ready_state = CLOSED
79
+ EventMachine.cancel_timer(@ping_timer)
80
+ @stream.close_connection_after_writing
81
+ event = WebSocket::API::Event.new('close')
82
+ event.init_event('close', false, false)
83
+ dispatch_event(event)
84
+ end
85
+ end
86
+
87
+ class EventSource::Stream
88
+ include EventMachine::Deferrable
89
+
90
+ extend Forwardable
91
+ def_delegators :@connection, :close_connection, :close_connection_after_writing
92
+
93
+ def initialize(event_source)
94
+ @event_source = event_source
95
+ @connection = event_source.env['em.connection']
96
+ @stream_send = event_source.env['stream.send']
97
+
98
+ @connection.socket_stream = self if @connection.respond_to?(:socket_stream)
99
+ end
100
+
101
+ def each(&callback)
102
+ @stream_send ||= callback
103
+ end
104
+
105
+ def fail
106
+ @event_source.close
107
+ end
108
+
109
+ def receive(data)
110
+ end
111
+
112
+ def write(data)
113
+ return unless @stream_send
114
+ @stream_send.call(data)
115
+ end
116
+ end
117
+ end
118
+
@@ -12,18 +12,18 @@ require 'digest/md5'
12
12
  require 'digest/sha1'
13
13
  require 'forwardable'
14
14
  require 'net/http'
15
+ require 'stringio'
15
16
  require 'uri'
16
-
17
17
  require 'eventmachine'
18
- require 'thin'
19
- require File.dirname(__FILE__) + '/thin_extensions'
20
18
 
21
19
  module Faye
20
+ autoload :EventSource, File.expand_path('../eventsource', __FILE__)
21
+
22
22
  class WebSocket
23
-
24
23
  root = File.expand_path('../websocket', __FILE__)
25
24
  require root + '/../../faye_websocket_mask'
26
25
 
26
+ autoload :Adapter, root + '/adapter'
27
27
  autoload :API, root + '/api'
28
28
  autoload :Client, root + '/client'
29
29
  autoload :Draft75Parser, root + '/draft75_parser'
@@ -33,6 +33,18 @@ module Faye
33
33
  # http://www.w3.org/International/questions/qa-forms-utf-8.en.php
34
34
  UTF8_MATCH = /^([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*$/
35
35
 
36
+ ADAPTERS = {
37
+ 'thin' => :Thin,
38
+ 'rainbows' => :Rainbows,
39
+ 'goliath' => :Goliath
40
+ }
41
+
42
+ def self.load_adapter(backend)
43
+ const = Kernel.const_get(ADAPTERS[backend]) rescue nil
44
+ require(backend) unless const
45
+ require File.expand_path("../adapters/#{backend}", __FILE__)
46
+ end
47
+
36
48
  def self.encode(string, validate_encoding = false)
37
49
  if Array === string
38
50
  return nil if validate_encoding and !valid_utf8?(string)
@@ -46,6 +58,12 @@ module Faye
46
58
  UTF8_MATCH =~ byte_array.pack('C*') ? true : false
47
59
  end
48
60
 
61
+ def self.websocket?(env)
62
+ env['HTTP_CONNECTION'] and
63
+ env['HTTP_CONNECTION'].split(/\s*,\s*/).include?('Upgrade') and
64
+ ['WebSocket', 'websocket'].include?(env['HTTP_UPGRADE'])
65
+ end
66
+
49
67
  def self.parser(env)
50
68
  if env['HTTP_SEC_WEBSOCKET_VERSION']
51
69
  HybiParser
@@ -56,6 +74,17 @@ module Faye
56
74
  end
57
75
  end
58
76
 
77
+ def self.determine_url(env)
78
+ secure = if env.has_key?('HTTP_X_FORWARDED_PROTO')
79
+ env['HTTP_X_FORWARDED_PROTO'] == 'https'
80
+ else
81
+ env['HTTP_ORIGIN'] =~ /^https:/i
82
+ end
83
+
84
+ scheme = secure ? 'wss:' : 'ws:'
85
+ "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }"
86
+ end
87
+
59
88
  extend Forwardable
60
89
  def_delegators :@parser, :version
61
90
 
@@ -63,16 +92,17 @@ module Faye
63
92
  include API
64
93
 
65
94
  def initialize(env, supported_protos = nil)
66
- @env = env
67
- @callback = @env['async.callback']
68
- @stream = Stream.new(self, @env['em.connection'])
69
- @callback.call [200, {}, @stream]
95
+ @env = env
96
+ @stream = Stream.new(self)
70
97
 
71
- @url = determine_url
98
+ @url = WebSocket.determine_url(@env)
72
99
  @ready_state = CONNECTING
73
100
  @buffered_amount = 0
74
101
 
75
102
  @parser = WebSocket.parser(@env).new(self, :protocols => supported_protos)
103
+
104
+ @callback = @env['async.callback']
105
+ @callback.call([101, {}, @stream])
76
106
  @stream.write(@parser.handshake_response)
77
107
 
78
108
  @ready_state = OPEN
@@ -80,28 +110,21 @@ module Faye
80
110
  event = Event.new('open')
81
111
  event.init_event('open', false, false)
82
112
  dispatch_event(event)
83
-
84
- @env[Thin::Request::WEBSOCKET_RECEIVE_CALLBACK] = lambda do |data|
85
- response = @parser.parse(data)
86
- @stream.write(response) if response
87
- end
88
113
  end
89
114
 
90
115
  def protocol
91
116
  @parser.protocol || ''
92
117
  end
93
118
 
119
+ def rack_response
120
+ [ -1, {}, [] ]
121
+ end
122
+
94
123
  private
95
124
 
96
- def determine_url
97
- secure = if @env.has_key?('HTTP_X_FORWARDED_PROTO')
98
- @env['HTTP_X_FORWARDED_PROTO'] == 'https'
99
- else
100
- @env['HTTP_ORIGIN'] =~ /^https:/i
101
- end
102
-
103
- scheme = secure ? 'wss:' : 'ws:'
104
- "#{ scheme }//#{ @env['HTTP_HOST'] }#{ @env['REQUEST_URI'] }"
125
+ def parse(data)
126
+ response = @parser.parse(data)
127
+ @stream.write(response) if response
105
128
  end
106
129
  end
107
130
 
@@ -111,24 +134,35 @@ module Faye
111
134
  extend Forwardable
112
135
  def_delegators :@connection, :close_connection, :close_connection_after_writing
113
136
 
114
- def initialize(web_socket, connection)
115
- @web_socket = web_socket
116
- @connection = connection
137
+ def initialize(web_socket)
138
+ @web_socket = web_socket
139
+ @connection = web_socket.env['em.connection']
140
+ @stream_send = web_socket.env['stream.send']
141
+
142
+ @connection.socket_stream = self if @connection.respond_to?(:socket_stream)
117
143
  end
118
144
 
119
145
  def each(&callback)
120
- @data_callback = callback
146
+ @stream_send ||= callback
121
147
  end
122
148
 
123
149
  def fail
124
150
  @web_socket.close(1006, '', false)
125
151
  end
126
152
 
153
+ def receive(data)
154
+ @web_socket.__send__(:parse, data)
155
+ end
156
+
127
157
  def write(data)
128
- return unless @data_callback
129
- @data_callback.call(data)
158
+ return unless @stream_send
159
+ @stream_send.call(data)
130
160
  end
131
161
  end
132
-
162
+ end
163
+
164
+ Faye::WebSocket::ADAPTERS.each do |name, const|
165
+ klass = Kernel.const_get(const) rescue nil
166
+ Faye::WebSocket.load_adapter(name) if klass
133
167
  end
134
168