talkshow 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0defe1121946da84d88bfc719622b659c34d5033
4
+ data.tar.gz: c3e5c0855a41a9e698e543dcf3d1599bc067a80e
5
+ SHA512:
6
+ metadata.gz: b3df5f71a04d10ceb8c07b713aff6de3c950c19ebbf1ac0606583ea34d94458de2a5a2cf22ff8cb40b42ec2594df659763342b08cdb3a5f91d4f33a5baef1784
7
+ data.tar.gz: 2fa658549eea26110bf46d37889768c44b1f6286ecffc790390c222d57cf164505292dd538710c4ecedcdd07dbe8e6c0785b73ed95f9c4c887c6a2d149407c84
@@ -0,0 +1,241 @@
1
+ require 'net/http'
2
+ require 'thread'
3
+ require 'json'
4
+ require 'talkshow/server'
5
+ require 'talkshow/timeout'
6
+ require 'talkshow/javascript_error'
7
+ require 'talkshow/queue'
8
+
9
+
10
+ #Main class for talking to a talkshow instrumented js application.
11
+ #
12
+ #This is the only class you need to worry about, and there are only
13
+ #a few important methods.
14
+ #
15
+ # Create the Talkshow client object:
16
+ # ts = Talkshow.new()
17
+ # Start up the server
18
+ # ts.start_server()
19
+ # Start executing javascript:
20
+ # ts.execute( 'alert "Hello world!"' )
21
+ class Talkshow
22
+ attr_accessor :type
23
+ attr_accessor :thread
24
+
25
+ # Create a new Talkshow object to get going:
26
+ def initialize
27
+ end
28
+
29
+ # Start up the Talkshow webserver
30
+ # This will be triggered if you don't do it -- but it takes a few
31
+ # seconds to start up the thin server, so you are better off
32
+ # issuing this yourself
33
+ def start_server(options = {})
34
+
35
+ # Backward compatibility
36
+ if options.is_a? String
37
+ url = options
38
+ port = nil
39
+ logfile = nil
40
+ else
41
+ url = options[:url]
42
+ port = options[:port]
43
+ logfile = options[:logfile]
44
+ end
45
+
46
+ url = ENV['TALKSHOW_REMOTE_URL'] if ENV['TALKSHOW_REMOTE_URL']
47
+ port = ENV['TALKSHOW_PORT'] if ENV['TALKSHOW_PORT']
48
+ logfile = ENV['TALKSHOW_LOG'] if ENV['TALKSHOW_LOG']
49
+
50
+ Talkshow::Server.set_port port if port
51
+ Talkshow::Server.set_logfile logfile if logfile
52
+
53
+ if !url
54
+ @type = :thread
55
+ @question_queue = ::Queue.new
56
+ @answer_queue = ::Queue.new
57
+ @thread = Thread.new do
58
+ Talkshow::Server.question_queue(@question_queue)
59
+ Talkshow::Server.answer_queue(@answer_queue)
60
+ Talkshow::Server.run!
61
+ end
62
+ else
63
+ @type = :remote
64
+ @question_queue = Talkshow::Queue.new(url)
65
+ @answer_queue = Talkshow::Queue.new(url)
66
+ end
67
+
68
+ end
69
+
70
+ # Stop the webserver
71
+ def stop_server
72
+ @thread.exit
73
+ end
74
+
75
+ # Invoke a function in the javascript application
76
+ # invoke requires a function name (including the namespace).
77
+ # Arguments are specified as an array reference.
78
+ #
79
+ # Some examples:
80
+ # ts.invoke( 'alert' )
81
+ # ts.invoke( 'alert', ['Hello world'])
82
+ # ts.invoke( 'window.alert', ['Hello world'] )
83
+ def invoke( function, args, timeout=6 )
84
+ send_question( {
85
+ type: 'invocation',
86
+ function: function,
87
+ args: args
88
+ }, timeout)
89
+ end
90
+
91
+ # Send a javascript instruction to the client
92
+ def execute( command, timeout=6 )
93
+ send_question( { type: 'code', message: command }, timeout)
94
+ end
95
+
96
+ # Load in a javascript file and execute remotely
97
+ def execute_file( filename )
98
+ text = File.read(filename)
99
+ execute(text)
100
+ end
101
+
102
+ private
103
+
104
+ def soft_pop
105
+ begin
106
+ @answer_queue.pop(true)
107
+ rescue => e
108
+ nil
109
+ end
110
+ end
111
+
112
+ def non_blocking_pop(timeout)
113
+ sleep_time = 0.1
114
+ answer = nil
115
+ catch(:done) do
116
+ (timeout/sleep_time).to_i.times { |i|
117
+ answer = soft_pop
118
+ throw :done if answer
119
+ sleep sleep_time
120
+ }
121
+ end
122
+ answer
123
+ end
124
+
125
+
126
+ # listen for an answer for a specific id, with a timeout, and also reconstitute
127
+ # any chunked responses
128
+ def listen_for_answer(id, timeout)
129
+
130
+ if ENV['TIMEOUT_MULTIPLIER']
131
+ timeout = ENV['TIMEOUT_MULTIPLIER'].to_i * timeout
132
+ end
133
+
134
+ answer = non_blocking_pop(timeout)
135
+ if !answer
136
+ raise Talkshow::Timeout.new
137
+ end
138
+
139
+ mismatch_retry = 3
140
+ if answer[:id].to_i != id.to_i && mismatch_retry >= 0
141
+ puts "Talkshow warning: message mismatch (#{answer[:id]} vs #{id})" unless answer[:id].to_i == 0
142
+ answer = non_blocking_pop(timeout)
143
+ mismatch_retry -= 1
144
+ end
145
+
146
+ if !answer
147
+ raise Talkshow::Timeout.new
148
+ end
149
+
150
+ chunks = answer[:chunks]
151
+ if chunks
152
+ answers = [answer]
153
+
154
+ i = 1
155
+ nil_count = 0
156
+ while ( i < chunks.to_i && nil_count < 3 ) do
157
+ candidate = non_blocking_pop(1)
158
+ if !candidate
159
+ nil_count += 1
160
+ next
161
+ end
162
+ if candidate[:id].to_i != id.to_i
163
+ puts "Talkshow warning: message mismatch (#{candidate[:id]} vs #{id.to_i})"
164
+ next
165
+ end
166
+
167
+ nil_count = 0
168
+ i += 1
169
+ answers << candidate
170
+ end
171
+
172
+ if answers.count < chunks.to_i
173
+ raise "Couldn't reconstitute whole message"
174
+ end
175
+
176
+ sorted_answers = answers.sort_by{ |a| a[:payload].to_i }
177
+ data = sorted_answers.collect { |a| a[:data] }.join
178
+ answer[:data] = data
179
+ answer[:payload] = nil
180
+ end
181
+
182
+ answer
183
+ end
184
+
185
+
186
+ # Send message to js application
187
+ # Message is a hash that looks like:
188
+ # {
189
+ # type => message_type,
190
+ # message => command,
191
+ # }
192
+ # Timeout is optional, but a negative timeout returns without
193
+ # looking for an answer
194
+ def send_question( message, timeout )
195
+
196
+ # Start the server if it hasn't been started already
197
+ self.start_server if (self.type == :thread && !self.thread)
198
+
199
+ @answer_queue.clear();
200
+ message[:id] = rand(99999)
201
+
202
+ @question_queue.push( message )
203
+
204
+ # Negative timeout - fire and forget
205
+ # Should only be used if it is known not to return an answer
206
+ return nil if timeout < 0
207
+
208
+ answer = listen_for_answer(message[:id], timeout)
209
+
210
+ if answer[:status] == 'error'
211
+ raise Talkshow::JavascriptError.new( answer[:data] )
212
+ end
213
+
214
+ case answer[:object]
215
+ when 'boolean'
216
+ answer[:data] == 'true'
217
+ when 'number'
218
+ if answer[:data].include?('.')
219
+ answer[:data].to_f
220
+ else
221
+ answer[:data].to_i
222
+ end
223
+ when 'undefined'
224
+ if answer[:data] == 'undefined'
225
+ nil
226
+ else
227
+ answer[:data]
228
+ end
229
+ when 'string'
230
+ answer[:data].to_s
231
+ else
232
+ begin
233
+ JSON.parse(answer[:data])
234
+ rescue StandardError => e
235
+ answer[:data]
236
+ end
237
+ end
238
+ end
239
+
240
+ end
241
+
@@ -0,0 +1,99 @@
1
+ require 'net/http'
2
+ require "uri"
3
+
4
+ require 'thread'
5
+ require 'json'
6
+ require 'daemon'
7
+
8
+ require 'talkshow'
9
+ require 'talkshow/web_control'
10
+ require 'talkshow/server'
11
+
12
+ class Talkshow::Daemon
13
+ attr_accessor :thread
14
+ attr_accessor :port_requests
15
+ attr_accessor :processes
16
+
17
+ # Create a new Talkshow object to get going
18
+ def initialize
19
+ Dir.mkdir './logs' if !Dir.exists?('./logs')
20
+ Dir.mkdir './pids' if !Dir.exists?('./pids')
21
+ @processes = {}
22
+ @port_requests = ::Queue.new
23
+ end
24
+
25
+ def start_server
26
+ @thread = Thread.new do
27
+ Talkshow::WebControl.port_requests(@port_requests)
28
+ Talkshow::WebControl.processes(@processes)
29
+ Talkshow::WebControl.run!
30
+ end
31
+ p @thread
32
+ sleep 10
33
+ end
34
+
35
+ # Stop the webserver
36
+ def stop_server
37
+ @thread.exit
38
+ end
39
+
40
+ def run
41
+ self.start_server
42
+ loop do
43
+ deal_with_port_requests
44
+ sleep 5
45
+ check_processes
46
+ end
47
+ end
48
+
49
+ def deal_with_port_requests
50
+ begin
51
+ port = @port_requests.pop(true)
52
+ rescue
53
+ port = nil
54
+ end
55
+ if port
56
+ if @processes[port]
57
+ puts "Port request -- checking aliveness"
58
+ if check_status(port) == 'dead'
59
+ @processes[port] = spawn_process(port)
60
+ end
61
+ else
62
+ puts "New port request"
63
+ @processes[port] = spawn_process(port)
64
+ end
65
+ end
66
+ end
67
+
68
+ def spawn_process(port)
69
+ `TALKSHOW_PORT=#{port} bundle exec ./bin/talkshow_server.rb > logs/talkshow.#{port}.log 2>&1 &`
70
+ sleep 5
71
+ 'starting'
72
+ end
73
+
74
+ def check_status(port)
75
+ uri = URI.parse("http://localhost:#{port}/status")
76
+ begin
77
+ response = Net::HTTP.get_response(uri)
78
+ rescue
79
+ status = 'dead'
80
+ end
81
+
82
+ if !status
83
+ if response.code.to_i == 200
84
+ status = 'ok'
85
+ else
86
+ status = "dead #{response.code}"
87
+ end
88
+ end
89
+ status
90
+ end
91
+
92
+ def check_processes()
93
+ @processes.each do |port, status|
94
+ @processes[port] = check_status(port)
95
+ end
96
+ end
97
+
98
+ end
99
+
@@ -0,0 +1,4 @@
1
+ class Talkshow
2
+ class Talkshow::JavascriptError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,32 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require 'json'
4
+
5
+ class Talkshow::Queue
6
+ attr_accessor :url
7
+
8
+ def initialize(url)
9
+ @uri = URI.parse(url)
10
+ @http = Net::HTTP.new(@uri.host, @uri.port)
11
+ end
12
+
13
+ def clear
14
+ response = @http.request(Net::HTTP::Get.new('/answerqueue/clear'))
15
+ response
16
+ end
17
+
18
+ def pop(ignored)
19
+ response = @http.request(Net::HTTP::Get.new('/answerqueue/pop'))
20
+ object = JSON.parse(response.body, :symbolize_names => true)
21
+ object[:message]
22
+ end
23
+
24
+ def push(obj)
25
+ serialized_object = obj.to_json.to_s
26
+ request = Net::HTTP::Post.new('/questionqueue/push')
27
+ request.set_form_data( {'message' => serialized_object } )
28
+ response = @http.request(request)
29
+ nil
30
+ end
31
+
32
+ end
@@ -0,0 +1,218 @@
1
+ require 'sinatra/base'
2
+ require 'net/http'
3
+ require 'thread'
4
+ require 'json'
5
+ require 'logger'
6
+
7
+
8
+ class Queue
9
+ # Take a peek at what's in the array
10
+ def peek
11
+ @que
12
+ end
13
+ end
14
+
15
+ # Sinatra server that is launched by your test code
16
+ # to talk to the instrumented javascript application
17
+ class Talkshow
18
+ class Talkshow::Server < Sinatra::Base
19
+ configure do
20
+ set :port, ENV['TALKSHOW_PORT'] if ENV['TALKSHOW_PORT']
21
+ set :protection, except: :path_traversal
22
+ end
23
+
24
+ def self.set_port port
25
+ set :port, port
26
+ end
27
+
28
+ @@logfile = './talkshowserver.log'
29
+ def self.set_logfile file
30
+ @@logfile = file
31
+ @logger.close if @logger
32
+ @logger = nil
33
+ end
34
+
35
+ def self.question_queue(queue = nil)
36
+ if queue
37
+ @@question_queue = queue
38
+ end
39
+ @@question_queue
40
+ end
41
+
42
+ def self.answer_queue(queue = nil)
43
+ if queue
44
+ @@answer_queue = queue
45
+ end
46
+ @@answer_queue
47
+ end
48
+
49
+
50
+ def logger
51
+ if !@logger
52
+ @logger = Logger.new(@@logfile)
53
+ end
54
+ @logger
55
+ end
56
+
57
+ # Make this available externally
58
+ set :bind, '0.0.0.0'
59
+
60
+ get '/' do
61
+ questions = Talkshow::Server.question_queue.peek.to_json
62
+ answers = Talkshow::Server.answer_queue.peek.to_json
63
+
64
+ <<HERE
65
+ <html>
66
+ <body>
67
+ <div>
68
+ <h1>Talkshow process: #{$$}</h1>
69
+ <h2>Port: #{settings.port}</h2>
70
+ </div>
71
+ <div>
72
+ <p>#{questions}</p>
73
+ </div>
74
+ <div>
75
+ <p>#{answers}</p>
76
+ </div>
77
+ </body>
78
+ </html>
79
+ HERE
80
+ end
81
+
82
+ get '/status' do
83
+ 200
84
+ end
85
+
86
+ get '/talkshowhost' do
87
+ "Talkshow running on " + request.host.to_s
88
+ end
89
+
90
+ get '/question/:poll_id' do
91
+ t = Time.new()
92
+
93
+ json_hash = {
94
+ :time => t.to_s,
95
+ }
96
+
97
+ content = nil;
98
+ if Talkshow::Server.question_queue.empty?
99
+ logger.debug("question: nop")
100
+ json_hash[:type] = "nop"
101
+ json_hash[:message] = ""
102
+
103
+ else
104
+ content = Talkshow::Server.question_queue.pop if !Talkshow::Server.question_queue.empty?
105
+ id = content[:id]
106
+ json_hash[:id] = id
107
+ logger.info( "question ##{id}: #{content.to_s}" )
108
+
109
+ type = content[:type]
110
+ json_hash[:type] = type
111
+
112
+ if type == 'code'
113
+ json_hash[:content] = content[:message]
114
+ elsif type == 'invocation'
115
+ json_hash[:function] = content[:function]
116
+ json_hash[:args] = content[:args]
117
+ end
118
+ end
119
+
120
+ callback = params[:callback]
121
+
122
+ json = json_hash.to_json
123
+
124
+ logger.info( json )
125
+
126
+ if callback
127
+ content_type 'text/javascript'
128
+ "#{callback}( #{json} );"
129
+ else
130
+ content_type :json
131
+ json
132
+ end
133
+ end
134
+
135
+ # Deal with an answer, push it back to the main thread
136
+ def handle_answer(params, data)
137
+ if params[:status] != 'nop'
138
+
139
+ Talkshow::Server.answer_queue.push( {
140
+ :data => data,
141
+ :object => params[:object],
142
+ :status => params[:status],
143
+ :chunks => params[:chunks],
144
+ :payload => params[:payload],
145
+ :id => params[:id]
146
+ } )
147
+ end
148
+
149
+ logger.info( "/answer ##{params[:id]}"+ ( params[:chunks] ? "(#{params[:payload].to_i+1}/#{params[:chunks]})" : '') +": #{data}" )
150
+ if params[:id] == 0
151
+ logger.info( "Reset received, talkshow reloaded")
152
+ end
153
+
154
+ content_type 'text/javascript'
155
+ 'ts.ack();'
156
+ end
157
+
158
+ # Capture an answer
159
+ get '/answer/:poll_id/:id/:status/:object/:data' do
160
+ handle_answer(params, params[:data])
161
+ end
162
+
163
+ # Capture the case when a response has no data (empty string)
164
+ get '/answer/:poll_id/:id/:status/:object/' do
165
+ handle_answer(params, '')
166
+ end
167
+
168
+ # Capture older talkshow.js implementations that didn't escape urls properly
169
+ get '/answer/:poll_id/:id/:status/:object/*' do
170
+ logger.warn("WARNING: Unescaped url passed as data component for route '#{request.fullpath}'")
171
+ data = params[:splat].join('/')
172
+ handle_answer(params, data)
173
+ end
174
+
175
+ # Functions for remotely clearing queues
176
+ get '/answerqueue/clear' do
177
+ Talkshow::Server.answer_queue.clear()
178
+ end
179
+
180
+ get '/questionqueue/clear' do
181
+ Talkshow::Server.question_queue.clear()
182
+ end
183
+
184
+ # Push something onto the answer queue remotely
185
+ post '/questionqueue/push' do
186
+ logger.info( '/questionqueue/push' )
187
+ message = JSON.parse(params[:message], :symbolize_names => true)
188
+ logger.debug("Message pushed: #{message}")
189
+ Talkshow::Server.question_queue.push(message)
190
+ end
191
+
192
+ # Pop something from the answer queue
193
+ get '/answerqueue/pop' do
194
+ logger.info('answerqueue/pop')
195
+ begin
196
+ message = Talkshow::Server.answer_queue.pop(true)
197
+ rescue
198
+ message = nil
199
+ end
200
+ { :message => message }.to_json
201
+ end
202
+
203
+ get '/questionqueue' do
204
+ Talkshow::Server.question_queue.peek.to_json
205
+ end
206
+
207
+ get '/answerqueue' do
208
+ Talkshow::Server.answer_queue.peek.to_json
209
+ end
210
+
211
+ # Catch anything else and shout about it
212
+ get '/*' do
213
+ puts "[Talkshow server warning] Unhandled route: '#{request.fullpath}'"
214
+ logger.error("WARNING: Unhandled route: '#{request.fullpath}'")
215
+ end
216
+
217
+ end
218
+ end
@@ -0,0 +1,4 @@
1
+ class Talkshow
2
+ class Talkshow::Timeout < StandardError
3
+ end
4
+ end
@@ -0,0 +1,85 @@
1
+ require 'sinatra/base'
2
+ require 'net/http'
3
+ require 'thread'
4
+ require 'json'
5
+ require 'logger'
6
+
7
+ require 'talkshow/server'
8
+ require 'thread'
9
+
10
+
11
+
12
+ class Talkshow
13
+ class Talkshow::WebControl < Sinatra::Base
14
+
15
+ set :bind, '0.0.0.0'
16
+ configure do
17
+ set :port, ENV['WEB_CONTROLLER_PORT']
18
+ end
19
+
20
+ # Thread safe
21
+ def self.port_requests(queue = nil)
22
+ if queue
23
+ @@port_requests = queue
24
+ end
25
+ @@port_requests
26
+ end
27
+
28
+ # Read-only, not thread safe
29
+ def self.processes(hash)
30
+ if hash
31
+ @@processes = hash
32
+ end
33
+ @@processes
34
+ end
35
+
36
+
37
+ def logger
38
+ if !@logger
39
+ @logger = Logger.new('talkshow_webcontrol.log')
40
+ end
41
+ @logger
42
+ end
43
+
44
+ get '/' do
45
+
46
+ process_table = "<table>"
47
+ @@processes.each do |port, status|
48
+ process_table += "<tr><td>#{port}</td><td>#{status}</td></tr>"
49
+ end
50
+ process_table += "</table>"
51
+
52
+ <<HERE
53
+ <html>
54
+ <style>
55
+ html { height: 100%;}
56
+ body {background: #CCC; font-family: Arial, Helvetica, sans-serif; padding: 20px;}
57
+ </style>
58
+ <body>
59
+ <div>
60
+ <h1>Talkshow web control</h1>
61
+ <h2>PID: #{$$}</h2>
62
+ <h2>Port: #{settings.port}</h2>
63
+ </div>
64
+ <div>
65
+ <h2>Active Processes</h2>
66
+ #{process_table}
67
+ </div>
68
+ </body>
69
+ </html>
70
+ HERE
71
+ end
72
+
73
+ get '/port/:port' do
74
+ port = params[:port].to_i
75
+ if port > 4000
76
+ Talkshow::WebControl.port_requests.push(port)
77
+ end
78
+ end
79
+
80
+ get '/status' do
81
+ 200
82
+ end
83
+
84
+ end
85
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: talkshow
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.4.2
5
+ platform: ruby
6
+ authors:
7
+ - Joseph Haig
8
+ - David Buckhurst
9
+ - Jenna Brown
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2016-04-01 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: sinatra
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: thin
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: json
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ description: Ruby to Javascript communications bridge
58
+ email: joe.haig@bbc.co.uk
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - lib/talkshow.rb
64
+ - lib/talkshow/daemon.rb
65
+ - lib/talkshow/javascript_error.rb
66
+ - lib/talkshow/queue.rb
67
+ - lib/talkshow/server.rb
68
+ - lib/talkshow/timeout.rb
69
+ - lib/talkshow/web_control.rb
70
+ homepage: https://github.com/fmtvp/talkshow
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.5.0
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Talkshow ruby gem
94
+ test_files: []