faye-tls1-websocket 0.8.0

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