faye-websocket 0.2.0 → 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.

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