faye-tls1-websocket 0.8.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.
@@ -0,0 +1,62 @@
1
+ # WebSocket extensions for Thin
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 a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in
15
+ # all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ # SOFTWARE.
24
+
25
+ class Thin::Connection
26
+ attr_accessor :socket_stream
27
+
28
+ alias :thin_process :process
29
+ alias :thin_receive_data :receive_data
30
+
31
+ def process
32
+ if @serving != :websocket and @request.websocket?
33
+ @serving = :websocket
34
+ end
35
+ if @request.socket_connection?
36
+ @request.env['em.connection'] = self
37
+ @response.persistent!
38
+ @response.async = true
39
+ end
40
+ thin_process
41
+ end
42
+
43
+ def receive_data(data)
44
+ return thin_receive_data(data) unless @serving == :websocket
45
+ socket_stream.receive(data) if socket_stream
46
+ end
47
+ end
48
+
49
+ class Thin::Request
50
+ include Faye::WebSocket::Adapter
51
+ end
52
+
53
+ class Thin::Response
54
+ attr_accessor :async
55
+ alias :thin_head :head
56
+
57
+ def head
58
+ return '' if async and status == 101
59
+ thin_head
60
+ end
61
+ end
62
+
@@ -0,0 +1,121 @@
1
+ require File.expand_path('../websocket', __FILE__) unless defined?(Faye::WebSocket)
2
+
3
+ module Faye
4
+ class EventSource
5
+
6
+ include WebSocket::API::EventTarget
7
+ attr_reader :env, :url, :ready_state
8
+
9
+ DEFAULT_RETRY = 5
10
+
11
+ def self.eventsource?(env)
12
+ return false unless env['REQUEST_METHOD'] == 'GET'
13
+ accept = (env['HTTP_ACCEPT'] || '').split(/\s*,\s*/)
14
+ accept.include?('text/event-stream')
15
+ end
16
+
17
+ def self.determine_url(env)
18
+ scheme = WebSocket.secure_request?(env) ? 'https:' : 'http:'
19
+ "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }"
20
+ end
21
+
22
+ def initialize(env, options = {})
23
+ WebSocket.ensure_reactor_running
24
+ super()
25
+
26
+ @env = env
27
+ @ping = options[:ping]
28
+ @retry = (options[:retry] || DEFAULT_RETRY).to_f
29
+ @url = EventSource.determine_url(env)
30
+ @stream = Stream.new(self)
31
+
32
+ @ready_state = WebSocket::API::CONNECTING
33
+
34
+ headers = ::WebSocket::Driver::Headers.new
35
+ if options[:headers]
36
+ options[:headers].each { |k,v| headers[k] = v }
37
+ end
38
+
39
+ if callback = @env['async.callback']
40
+ callback.call([101, {}, @stream])
41
+ end
42
+
43
+ @stream.write("HTTP/1.1 200 OK\r\n" +
44
+ "Content-Type: text/event-stream\r\n" +
45
+ "Cache-Control: no-cache, no-store\r\n" +
46
+ "Connection: close\r\n" +
47
+ headers.to_s +
48
+ "\r\n" +
49
+ "retry: #{ (@retry * 1000).floor }\r\n\r\n")
50
+
51
+ EventMachine.next_tick { open }
52
+
53
+ if @ping
54
+ @ping_timer = EventMachine.add_periodic_timer(@ping) { ping }
55
+ end
56
+ end
57
+
58
+ def last_event_id
59
+ @env['HTTP_LAST_EVENT_ID'] || ''
60
+ end
61
+
62
+ def rack_response
63
+ [ -1, {}, [] ]
64
+ end
65
+
66
+ private
67
+
68
+ def open
69
+ return unless @ready_state == WebSocket::API::CONNECTING
70
+
71
+ @ready_state = WebSocket::API::OPEN
72
+
73
+ event = WebSocket::API::Event.new('open')
74
+ event.init_event('open', false, false)
75
+ dispatch_event(event)
76
+ end
77
+
78
+ public
79
+
80
+ def send(message, options = {})
81
+ return false if @ready_state > WebSocket::API::OPEN
82
+
83
+ message = ::WebSocket::Driver.encode(message.to_s).
84
+ gsub(/(\r\n|\r|\n)/, '\1data: ')
85
+
86
+ frame = ""
87
+ frame << "event: #{options[:event]}\r\n" if options[:event]
88
+ frame << "id: #{options[:id]}\r\n" if options[:id]
89
+ frame << "data: #{message}\r\n\r\n"
90
+
91
+ @stream.write(frame)
92
+ true
93
+ end
94
+
95
+ def ping(message = nil)
96
+ return false if @ready_state > WebSocket::API::OPEN
97
+ @stream.write(":\r\n\r\n")
98
+ true
99
+ end
100
+
101
+ def close
102
+ return if [WebSocket::API::CLOSING, WebSocket::API::CLOSED].include?(@ready_state)
103
+
104
+ @ready_state = WebSocket::API::CLOSED
105
+ EventMachine.cancel_timer(@ping_timer)
106
+ @stream.close_connection_after_writing
107
+
108
+ event = WebSocket::API::Event.new('close')
109
+ event.init_event('close', false, false)
110
+ dispatch_event(event)
111
+ end
112
+
113
+ class Stream < RackStream
114
+ def fail
115
+ @socket_object.close
116
+ end
117
+ end
118
+
119
+ end
120
+ end
121
+
@@ -0,0 +1,70 @@
1
+ module Faye
2
+ class RackStream
3
+
4
+ include EventMachine::Deferrable
5
+
6
+ module Reader
7
+ attr_accessor :stream
8
+
9
+ def receive_data(data)
10
+ stream.receive(data)
11
+ end
12
+
13
+ def unbind
14
+ stream.fail
15
+ end
16
+ end
17
+
18
+ def initialize(socket_object)
19
+ @socket_object = socket_object
20
+ @connection = socket_object.env['em.connection']
21
+ @stream_send = socket_object.env['stream.send']
22
+
23
+ if socket_object.env['rack.hijack']
24
+ socket_object.env['rack.hijack'].call
25
+ @rack_hijack_io = socket_object.env['rack.hijack_io']
26
+ EventMachine.attach(@rack_hijack_io, Reader) do |reader|
27
+ @rack_hijack_io_reader = reader
28
+ reader.stream = self
29
+ end
30
+ end
31
+
32
+ @connection.socket_stream = self if @connection.respond_to?(:socket_stream)
33
+ end
34
+
35
+ def clean_rack_hijack
36
+ return unless @rack_hijack_io
37
+ @rack_hijack_io_reader.close_connection_after_writing
38
+ @rack_hijack_io = @rack_hijack_io_reader = nil
39
+ end
40
+
41
+ def close_connection
42
+ clean_rack_hijack
43
+ @connection.close_connection if @connection
44
+ end
45
+
46
+ def close_connection_after_writing
47
+ clean_rack_hijack
48
+ @connection.close_connection_after_writing if @connection
49
+ end
50
+
51
+ def each(&callback)
52
+ @stream_send ||= callback
53
+ end
54
+
55
+ def fail
56
+ end
57
+
58
+ def receive(data)
59
+ end
60
+
61
+ def write(data)
62
+ return @rack_hijack_io.write(data) if @rack_hijack_io
63
+ return @stream_send.call(data) if @stream_send
64
+ rescue => e
65
+ fail if EOFError === e
66
+ end
67
+
68
+ end
69
+ end
70
+
@@ -0,0 +1,100 @@
1
+ # API references:
2
+ #
3
+ # * http://dev.w3.org/html5/websockets/
4
+ # * http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-eventtarget
5
+ # * http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-event
6
+
7
+ require 'forwardable'
8
+ require 'stringio'
9
+ require 'uri'
10
+ require 'eventmachine-le'
11
+ require 'websocket/driver'
12
+
13
+ module Faye
14
+ autoload :EventSource, File.expand_path('../eventsource', __FILE__)
15
+ autoload :RackStream, File.expand_path('../rack_stream', __FILE__)
16
+
17
+ class WebSocket
18
+ root = File.expand_path('../websocket', __FILE__)
19
+
20
+ autoload :Adapter, root + '/adapter'
21
+ autoload :API, root + '/api'
22
+ autoload :Client, root + '/client'
23
+
24
+ ADAPTERS = {
25
+ 'goliath' => :Goliath,
26
+ 'rainbows' => :Rainbows,
27
+ 'thin' => :Thin
28
+ }
29
+
30
+ def self.determine_url(env)
31
+ scheme = secure_request?(env) ? 'wss:' : 'ws:'
32
+ "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }"
33
+ end
34
+
35
+ def self.ensure_reactor_running
36
+ Thread.new { EventMachine.run } unless EventMachine.reactor_running?
37
+ Thread.pass until EventMachine.reactor_running?
38
+ end
39
+
40
+ def self.load_adapter(backend)
41
+ const = Kernel.const_get(ADAPTERS[backend]) rescue nil
42
+ require(backend) unless const
43
+ path = File.expand_path("../adapters/#{backend}.rb", __FILE__)
44
+ require(path) if File.file?(path)
45
+ end
46
+
47
+ def self.secure_request?(env)
48
+ return true if env['HTTPS'] == 'on'
49
+ return true if env['HTTP_X_FORWARDED_SSL'] == 'on'
50
+ return true if env['HTTP_X_FORWARDED_SCHEME'] == 'https'
51
+ return true if env['HTTP_X_FORWARDED_PROTO'] == 'https'
52
+ return true if env['rack.url_scheme'] == 'https'
53
+
54
+ return false
55
+ end
56
+
57
+ def self.websocket?(env)
58
+ ::WebSocket::Driver.websocket?(env)
59
+ end
60
+
61
+ attr_reader :env
62
+ include API
63
+
64
+ def initialize(env, protocols = nil, options = {})
65
+ WebSocket.ensure_reactor_running
66
+
67
+ @env = env
68
+ @url = WebSocket.determine_url(@env)
69
+ @driver = ::WebSocket::Driver.rack(self, :max_length => options[:max_length], :protocols => protocols)
70
+ @stream = Stream.new(self)
71
+
72
+ if callback = @env['async.callback']
73
+ callback.call([101, {}, @stream])
74
+ end
75
+
76
+ super(options)
77
+ @driver.start
78
+ end
79
+
80
+ def write(data)
81
+ @stream.write(data)
82
+ end
83
+
84
+ def rack_response
85
+ [ -1, {}, [] ]
86
+ end
87
+
88
+ class Stream < RackStream
89
+ def fail
90
+ @socket_object.__send__(:finalize, '', 1006)
91
+ end
92
+
93
+ def receive(data)
94
+ @socket_object.__send__(:parse, data)
95
+ end
96
+ end
97
+
98
+ end
99
+ end
100
+
@@ -0,0 +1,21 @@
1
+ module Faye
2
+ class WebSocket
3
+
4
+ module Adapter
5
+ def websocket?
6
+ e = defined?(@env) ? @env : env
7
+ e && WebSocket.websocket?(e)
8
+ end
9
+
10
+ def eventsource?
11
+ e = defined?(@env) ? @env : env
12
+ e && EventSource.eventsource?(e)
13
+ end
14
+
15
+ def socket_connection?
16
+ websocket? or eventsource?
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,115 @@
1
+ require File.expand_path('../api/event_target', __FILE__)
2
+ require File.expand_path('../api/event', __FILE__)
3
+
4
+ module Faye
5
+ class WebSocket
6
+
7
+ module API
8
+ CONNECTING = 0
9
+ OPEN = 1
10
+ CLOSING = 2
11
+ CLOSED = 3
12
+
13
+ include EventTarget
14
+
15
+ extend Forwardable
16
+ def_delegators :@driver, :version
17
+
18
+ attr_reader :url, :ready_state, :buffered_amount
19
+
20
+ def initialize(options = {})
21
+ super()
22
+
23
+ if headers = options[:headers]
24
+ headers.each { |name, value| @driver.set_header(name, value) }
25
+ end
26
+
27
+ @ping = options[:ping]
28
+ @ping_id = 0
29
+ @ready_state = CONNECTING
30
+ @buffered_amount = 0
31
+
32
+ @driver.on(:open) { |e| open }
33
+ @driver.on(:message) { |e| receive_message(e.data) }
34
+ @driver.on(:close) { |e| finalize(e.reason, e.code) }
35
+
36
+ @driver.on(:error) do |error|
37
+ event = Event.new('error', :message => error.message)
38
+ event.init_event('error', false, false)
39
+ dispatch_event(event)
40
+ end
41
+
42
+ if @ping
43
+ @ping_timer = EventMachine.add_periodic_timer(@ping) do
44
+ @ping_id += 1
45
+ ping(@ping_id.to_s)
46
+ end
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def open
53
+ return unless @ready_state == CONNECTING
54
+ @ready_state = OPEN
55
+ event = Event.new('open')
56
+ event.init_event('open', false, false)
57
+ dispatch_event(event)
58
+ end
59
+
60
+ def receive_message(data)
61
+ return unless @ready_state == OPEN
62
+ event = Event.new('message')
63
+ event.init_event('message', false, false)
64
+ event.data = data
65
+ dispatch_event(event)
66
+ end
67
+
68
+ def finalize(reason = nil, code = nil)
69
+ return if @ready_state == CLOSED
70
+ @ready_state = CLOSED
71
+ EventMachine.cancel_timer(@ping_timer) if @ping_timer
72
+ @stream.close_connection_after_writing if @stream
73
+ event = Event.new('close', :code => code || 1000, :reason => reason || '')
74
+ event.init_event('close', false, false)
75
+ dispatch_event(event)
76
+ end
77
+
78
+ def parse(data)
79
+ @driver.parse(data)
80
+ end
81
+
82
+ public
83
+
84
+ def write(data)
85
+ @stream.write(data)
86
+ end
87
+
88
+ def send(message)
89
+ return false if @ready_state > OPEN
90
+ case message
91
+ when Numeric then @driver.text(message.to_s)
92
+ when String then @driver.text(message)
93
+ when Array then @driver.binary(message)
94
+ else false
95
+ end
96
+ end
97
+
98
+ def ping(message = '', &callback)
99
+ return false if @ready_state > OPEN
100
+ @driver.ping(message, &callback)
101
+ end
102
+
103
+ def close
104
+ @ready_state = CLOSING if @ready_state == OPEN
105
+ @driver.close
106
+ end
107
+
108
+ def protocol
109
+ @driver.protocol || ''
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+