jscmd 0.0.2

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.
data/bin/jscmd ADDED
@@ -0,0 +1,44 @@
1
+ #! /opt/local/bin/ruby
2
+
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError
6
+ # no rubygems to load, so we fail silently
7
+ end
8
+
9
+ if File.exist?(File.dirname(__FILE__) + "/../lib/jscmd.rb")
10
+ $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
11
+ end
12
+
13
+ require 'jscmd'
14
+ require 'optparse'
15
+
16
+ OPTIONS = {
17
+ :port => 9000
18
+ }
19
+ MANDATORY_OPTIONS = %w()
20
+
21
+ parser = OptionParser.new do |opts|
22
+ opts.banner = <<BANNER
23
+ JS Commander: Remote JavaScript Console
24
+
25
+ Usage: #{File.basename($0)} [options]
26
+
27
+ Options are:
28
+ BANNER
29
+ opts.separator ""
30
+ opts.on("-p", "--port=PORT", Integer,
31
+ "Start proxy server on the specified port.",
32
+ "Default: 9000") { |OPTIONS[:port]| }
33
+ opts.on("-h", "--help",
34
+ "Show this help message.") { puts opts; exit }
35
+ opts.parse!(ARGV)
36
+
37
+ if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
38
+ puts opts; exit
39
+ end
40
+ end
41
+
42
+ # do stuff
43
+ server = JSCommander::Broker.new(:Port => OPTIONS[:port], :AccessLog => [], :ProxyVia => nil)
44
+ server.start
data/lib/jscmd.rb ADDED
@@ -0,0 +1 @@
1
+ Dir[File.join(File.dirname(__FILE__), 'jscmd/**/*.rb')].sort.each { |lib| require lib }
@@ -0,0 +1,253 @@
1
+ ({
2
+ createXHR: function() {
3
+ if (window.ActiveXObject) {
4
+ try {
5
+ return new ActiveXObject("Msxml2.XMLHTTP");
6
+ } catch (e) {
7
+ try {
8
+ return new ActiveXObject("Microsoft.XMLHTTP");
9
+ } catch (e2) {
10
+ return null;
11
+ }
12
+ }
13
+ } else if (window.XMLHttpRequest) {
14
+ return new XMLHttpRequest();
15
+ } else {
16
+ return null;
17
+ }
18
+ },
19
+
20
+ queue: [],
21
+
22
+ evaluate: function(script, recordValue) {
23
+ function dir(o) {
24
+ var list = [];
25
+ var maxNameLen = 0;
26
+ var valueLenLimit = 200;
27
+ for (name in o) {
28
+ var value = null;
29
+ try {
30
+ value = "" + (o[name] != null ? o[name] : null);
31
+ } catch (e) {
32
+ value = "[Error: " + e + "]";
33
+ // value = "[Error]";
34
+ }
35
+ value = value.replace(/\\/g, "\\\\");
36
+ value = value.replace(/\r/g, "");
37
+ value = value.replace(/\n/g, "\\n");
38
+ if (value.length > valueLenLimit) value = value.substring(0, valueLenLimit) + "...";
39
+ list.push([name, value]);
40
+ if (name.length > maxNameLen) maxNameLen = name.length;
41
+ }
42
+ for (var i = 0; i < list.length; i++) {
43
+ var name = list[i][0], value = list[i][1];
44
+ list[i] = name + (new Array(maxNameLen + 3 - name.length).join(" ")) + value;
45
+ }
46
+ return list.sort().join("\n");
47
+ }
48
+ var $_ = this.lastValue;
49
+ var value = eval(script);
50
+ if (recordValue) {
51
+ this.lastValue = value;
52
+ }
53
+ return value;
54
+ },
55
+
56
+ poll: function() {
57
+ var content = this.queue.length > 0 ? this.queue.shift() : "";
58
+ var xhr = this.createXHR();
59
+ xhr.open("POST", "/_remote_js_proxy/poll", true);
60
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
61
+ var self = this;
62
+ xhr.onreadystatechange = function() {
63
+ if (xhr.readyState == 4) {
64
+ delete self.currentXHR;
65
+ try {
66
+ if (xhr.status == 200) {
67
+ var text = xhr.responseText;
68
+ if (text) {
69
+ var value = null;
70
+ try {
71
+ var option = text.substring(0, 1);
72
+ var script = text.substring(1, text.length);
73
+ if (option == 'P') {
74
+ // get properties for code completion
75
+ var obj = self.evaluate.apply(window, [script, false]);
76
+ value = "V" + self.getAllProperties(obj).join(",");
77
+ } else if (option == 'E') {
78
+ // just evaluate
79
+ value = "V" + self.evaluate.apply(window, [script, true]);
80
+ } else {
81
+ value = "Ebad request: " + text;
82
+ }
83
+ } catch (e) {
84
+ value = "E" + e.toString();
85
+ }
86
+ self.queue.push(value);
87
+ }
88
+ }
89
+ } catch (e) {
90
+ // failed to poll; ignore
91
+ }
92
+ if (!self.aborted) {
93
+ if (self.queue.length > 0) {
94
+ self.poll();
95
+ } else {
96
+ // if polling failed, try again later
97
+ setTimeout(function() { self.poll() }, 5000);
98
+ }
99
+ }
100
+ }
101
+ }
102
+ this.currentXHR = xhr;
103
+ if (this.queue.length > 0) {
104
+ // mark that there are more messages pending
105
+ content = "+" + content;
106
+ }
107
+ xhr.send(encodeURI(content));
108
+ },
109
+
110
+ startPoll: function() {
111
+ this.startPoll = function() {}
112
+ if (window.top.parent != window) return;
113
+ this.poll();
114
+ },
115
+
116
+ abortPoll: function(retry) {
117
+ if (!retry) {
118
+ // don't retry any more
119
+ this.aborted = true;
120
+ }
121
+ if (this.currentXHR) {
122
+ this.currentXHR.abort();
123
+ }
124
+ },
125
+
126
+ pushValue: function(value) {
127
+ this.queue.push(value);
128
+ this.abortPoll(true);
129
+ this.startPoll(); // in case startPoll has not been called yet
130
+ },
131
+
132
+ attachEvent: function(event, handler) {
133
+ if (window.addEventListener) {
134
+ window.addEventListener(event, handler, false);
135
+ } else {
136
+ window.attachEvent("on" + event, handler);
137
+ }
138
+ },
139
+
140
+ getAllProperties: function(obj) {
141
+ var result = [];
142
+ for (var i in obj) {
143
+ result.push(i);
144
+ }
145
+ for (var i = 0; i < this.dontEnums.length; i++) {
146
+ var e = this.dontEnums[i];
147
+ if (Object.prototype.toString.apply(obj).match(/object (\w+)/)) {
148
+ if (e.check(obj, RegExp.$1)) {
149
+ for (var k in e.properties) {
150
+ var names = e.properties[k].split(/\s+/);
151
+ for (var j = 0; j < names.length; j++) {
152
+ if (obj[names[j]]) {
153
+ result.push(names[j]);
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+ return result;
161
+ },
162
+
163
+ // dontEnums is copied from jsh. thanks!
164
+ // http://d.hatena.ne.jp/brazil/20061022
165
+ dontEnums: [
166
+ {check: function(obj, objType){return objType=='String'}, properties: {
167
+ F:'charAt charCodeAt concat indexOf lastIndexOf match replace search slice split substr substring toLowerCase toUpperCase valueOf '+
168
+ 'anchor big blink bold fixed fontcolor fontsize italics link small strike sub sup',
169
+ N:'length'
170
+ }},
171
+ {check: function(obj, objType){return objType=='Array'}, properties: {
172
+ F:'concat join pop push unshift shift sort reverse slice splice toString valueOf '+
173
+ 'every filter forEach map some',
174
+ N:'length'
175
+ }},
176
+ {check: function(obj, objType){return objType=='Boolean'}, properties: {
177
+ F:'toString valueOf'
178
+ }},
179
+ {check: function(obj, objType){return objType=='Date'}, properties: {
180
+ F:'getDate getYear getFullYear getHours getMilliseconds getMinutes getMonth getSeconds getTime getDay '+
181
+ 'setDate setYear setFullYear setHours setMilliseconds setMinutes setMonth setSeconds setTime '+
182
+ 'getUTCDate getUTCFullYear getUTCHours getUTCMilliseconds getUTCMinutes getUTCMonth getUTCSeconds getUTCDay '+
183
+ 'setUTCDate setUTCFullYear setUTCHours setUTCMilliseconds setUTCMinutes setUTCMonth setUTCSeconds '+
184
+ 'toGMTString toLocaleString toUTCString UTC getTimezoneOffset parse toString valueOf toSource'
185
+ }},
186
+ {check: function(obj, objType){return objType=='Number'}, properties: {
187
+ F:'toExponential toFixed toPrecision toSource toString valueOf'
188
+ }},
189
+ {check: function(obj, objType){return objType=='RegExp'}, properties: {
190
+ F:'exec test toSource toString',
191
+ B:'global ignoreCase multiline',
192
+ N:'lastIndex',
193
+ S:'source'
194
+ }},
195
+ {check: function(obj, objType){return obj.navigator}, properties: {
196
+ F:'Array String Number Date RegExp Boolean '+
197
+ 'escape unescape decodeURI decodeURIComponent encodeURI encodeURIComponent '+
198
+ 'eval isFinite isNaN parseFloat parseInt',
199
+ O:'Math undefined',
200
+ N:'Infinity NaN'
201
+ }},
202
+ {check: function(obj, objType){return objType=='Math'}, properties: {
203
+ F:'abs acos asin atan atan2 ceil cos exp floor log max min pow random round sin sqrt tan',
204
+ N:'E LN2 LN10 LOG2E LOG10E PI SQRT1_2 SQRT2'
205
+ }},
206
+ {check: function(obj, objType){return obj.MAX_VALUE}, properties: {
207
+ N:'MAX_VALUE MIN_VALUE NaN NEGATIVE_INFINITY POSITIVE_INFINITY'
208
+ }},
209
+ {check: function(obj, objType){return obj.fromCharCode}, properties: {
210
+ F:'fromCharCode'
211
+ }},
212
+ {check: function(obj, objType){return objType=='Function'}, properties: {
213
+ F:'apply call toSource toString'
214
+ }}
215
+ ],
216
+
217
+ init: function() {
218
+ var self = this;
219
+
220
+ this.attachEvent("abort", function() { self.abortPoll() });
221
+ this.attachEvent("unload", function() { self.abortPoll() });
222
+ this.attachEvent("pagehide", function() { self.abortPoll() });
223
+
224
+ this.attachEvent("load", function() { self.startPoll() });
225
+ this.attachEvent("pageshow", function() { self.startPoll() });
226
+
227
+ window.onerror = function(message, file, line) {
228
+ self.pushValue("E" +
229
+ "Message: " + message + "\n" +
230
+ "File: " + file + "\n" +
231
+ "Line: " + line + "\n");
232
+ }
233
+ setTimeout(function() { self.startPoll() }, 3000);
234
+
235
+ if (!window.console) {
236
+ window.console = {};
237
+ var levels = ["debug", "info", "warn", "error"];
238
+ for (var i = 0; i < levels.length; i++) {
239
+ (function() {
240
+ var level = levels[i];
241
+ window.console[level] = function() {
242
+ var message = [];
243
+ for (var i = 0; i < arguments.length; i++) {
244
+ message.push(arguments[i]);
245
+ }
246
+ self.pushValue("V[" + level + "] " + message.join(", "));
247
+ }
248
+ })();
249
+ }
250
+ window.console.log = window.console.info;
251
+ }
252
+ }
253
+ }).init();
@@ -0,0 +1,140 @@
1
+ #
2
+ # asynchttpproxy.rb: Asynchronous HTTP Proxy Server
3
+ #
4
+ # Copyright 2007 Shinya Kasatani
5
+ #
6
+ # The original WEBrick::HTTPProxyServer sends response to the client
7
+ # after the whole content has been downloaded to the proxy,
8
+ # while this AsyncHTTPProxyServer sends the response concurrently while
9
+ # downloading the content.
10
+ #
11
+ # Based on webrick/httpproxy.rb. The original copyright notice follows:
12
+ #
13
+ # httpproxy.rb -- HTTPProxy Class
14
+ #
15
+ # Author: IPR -- Internet Programming with Ruby -- writers
16
+ # Copyright (c) 2002 GOTO Kentaro
17
+ # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
18
+ # reserved.
19
+ #
20
+
21
+ require 'stringio'
22
+ require 'webrick/httpproxy'
23
+
24
+ module WEBrick
25
+ class AsyncHTTPProxyServer < HTTPProxyServer
26
+ def proxy_service(req, res)
27
+ # Proxy Authentication
28
+ proxy_auth(req, res)
29
+
30
+ # Create Request-URI to send to the origin server
31
+ uri = req.request_uri
32
+ path = uri.path.dup
33
+ path << "?" << uri.query if uri.query
34
+
35
+ # Choose header fields to transfer
36
+ header = Hash.new
37
+ choose_header(req, header)
38
+ set_via(header)
39
+
40
+ # select upstream proxy server
41
+ if proxy = proxy_uri(req, res)
42
+ proxy_host = proxy.host
43
+ proxy_port = proxy.port
44
+ if proxy.userinfo
45
+ credentials = "Basic " + [proxy.userinfo].pack("m*")
46
+ credentials.chomp!
47
+ header['proxy-authorization'] = credentials
48
+ end
49
+ end
50
+
51
+
52
+ response = nil
53
+ q = Queue.new
54
+ p_reader, p_writer = IO.pipe
55
+ thread = Thread.start do
56
+ begin
57
+ @logger.debug "downloading #{uri}"
58
+ http = Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port)
59
+ http.start{
60
+ if @config[:ProxyTimeout]
61
+ ################################## these issues are
62
+ http.open_timeout = 30 # secs # necessary (maybe bacause
63
+ http.read_timeout = 60 # secs # Ruby's bug, but why?)
64
+ ##################################
65
+ end
66
+ http_req = nil
67
+ http_req_body = nil
68
+ case req.request_method
69
+ when "GET"
70
+ http_req = Net::HTTP::Get.new(path, header)
71
+ when "POST"
72
+ http_req = Net::HTTP::Post.new(path, header)
73
+ http_req_body = req.body || ""
74
+ when "HEAD"
75
+ http_req = Net::HTTP::Head.new(path, header)
76
+ else
77
+ raise HTTPStatus::MethodNotAllowed,
78
+ "unsupported method `#{req.request_method}'."
79
+ end
80
+ http.request(http_req, http_req_body) do |response|
81
+ q.push response
82
+ size = 0
83
+ last_size = 0
84
+ response.read_body do |str|
85
+ last_size = size
86
+ size += str.size
87
+ if last_size / 500000 != size / 500000
88
+ @logger.debug "downloading #{uri}: size=#{size}"
89
+ end
90
+ p_writer.write str
91
+ end
92
+ @logger.debug "finished downloading #{uri}: size=#{size}"
93
+ p_writer.close
94
+ end
95
+ }
96
+ rescue Exception => err
97
+ logger.debug("#{err.class}: #{err.message}")
98
+ q.push err
99
+ # raise HTTPStatus::ServiceUnavailable, err.message
100
+ end
101
+ end
102
+ response = q.pop
103
+ if response.is_a?(Exception)
104
+ @logger.debug "failed to download #{uri}"
105
+ raise HTTPStatus::ServiceUnavailable, response.message
106
+ end
107
+
108
+ # Persistent connction requirements are mysterious for me.
109
+ # So I will close the connection in every response.
110
+ res['proxy-connection'] = "close"
111
+ res['connection'] = "close"
112
+
113
+ # Convert Net::HTTP::HTTPResponse to WEBrick::HTTPProxy
114
+ res.status = response.code.to_i
115
+ choose_header(response, res)
116
+ set_cookie(response, res)
117
+ set_via(res)
118
+ res.body = p_reader
119
+ def res.flush_body
120
+ if @body.is_a?(IO)
121
+ begin
122
+ str_body = @body.read
123
+ ensure
124
+ @body.close
125
+ end
126
+ @body = str_body
127
+ end
128
+ @body
129
+ end
130
+
131
+ @logger.debug "downloading #{uri}: content-length=#{res.header['content-length']}"
132
+
133
+ # Process contents
134
+ if handler = @config[:ProxyContentHandler]
135
+ handler.call(req, res)
136
+ end
137
+ end
138
+ end
139
+ end
140
+
@@ -0,0 +1,339 @@
1
+ #
2
+ # jscommander.rb: Remote JavaScript Console
3
+ #
4
+ # Copyright 2007 Shinya Kasatani
5
+ #
6
+
7
+ require 'uri'
8
+ require 'thread'
9
+ require 'webrick'
10
+ require 'zlib'
11
+
12
+ module JSCommander
13
+ SCRIPT_DIR = "/_remote_js_proxy/"
14
+
15
+ class PipeQueue
16
+ attr_reader :reader, :writer
17
+
18
+ def initialize
19
+ @reader, @writer = IO.pipe
20
+ end
21
+
22
+ def push(obj)
23
+ data = Marshal.dump(obj)
24
+ @writer.write([data.size].pack("N") + data)
25
+ end
26
+
27
+ def pop
28
+ size = @reader.read(4).unpack("N")[0]
29
+ Marshal.load(@reader.read(size))
30
+ end
31
+ end
32
+
33
+ # types are: V: value, E: error, A: active message from proxy
34
+ Message = Struct.new(:value, :type, :clients)
35
+
36
+ class Broker
37
+ attr_reader :cmd_queue, :msg_queue
38
+
39
+ def initialize(opts = {})
40
+ @cmd_queue = PipeQueue.new
41
+ @msg_queue = PipeQueue.new
42
+ @proxy = ProxyServer.new(self, opts)
43
+ @shell = Shell.new(self)
44
+ end
45
+
46
+ def start
47
+ @proxy_pid = fork do
48
+ begin
49
+ Signal.trap('INT') {}
50
+ Signal.trap('TERM') { @proxy.shutdown }
51
+ IO.for_fd(0).close
52
+ @proxy.start
53
+ rescue Exception => e
54
+ p e
55
+ end
56
+ end
57
+ Signal.trap("TERM") { shutdown }
58
+ @shell.run # @shell.run should block
59
+ end
60
+
61
+ def shutdown
62
+ $stderr.puts "shutdown"
63
+ Process.kill "TERM", @proxy_pid
64
+ Process.waitall
65
+ exit(0)
66
+ end
67
+ end
68
+
69
+ class Shell
70
+ class SimpleConsole
71
+ def show_banner
72
+ end
73
+
74
+ def readline
75
+ line = $stdin.readline
76
+ line.chomp! if line
77
+ line
78
+ end
79
+
80
+ def close
81
+ end
82
+ end
83
+
84
+ class ReadlineConsole
85
+ HISTORY_PATH = "~/.jscmd_history"
86
+
87
+ def history_path
88
+ File.expand_path(HISTORY_PATH)
89
+ end
90
+
91
+ def initialize(shell)
92
+ @shell = shell
93
+ require 'readline'
94
+ if File.exist?(history_path)
95
+ hist = File.readlines(history_path).map{|line| line.chomp}
96
+ Readline::HISTORY.push(*hist)
97
+ end
98
+ Readline.basic_word_break_characters = " \t\n\\`@><=;|&{([+-*/%"
99
+ Readline.completion_append_character = nil
100
+ Readline.completion_proc = proc do |word|
101
+ if word =~ /\.$/
102
+ @shell.object_props($`).map{|name| word + name}
103
+ elsif word =~ /\.([^.]+)$/
104
+ prefix = $1
105
+ parent = $`
106
+ props = @shell.object_props(parent)
107
+ props.select{|name|name[0...(prefix.size)] == prefix}.map{|name| "#{parent}.#{name}"}
108
+ else
109
+ props = @shell.object_props("this")
110
+ prefix = word
111
+ props.select{|name|name[0...(prefix.size)] == prefix}
112
+ end
113
+ end
114
+ end
115
+
116
+ def show_banner
117
+ puts "Press Ctrl+D to exit."
118
+ end
119
+
120
+ def close
121
+ open(history_path, "ab") do |f|
122
+ Readline::HISTORY.each{|line| f.puts(line)}
123
+ end
124
+ end
125
+
126
+ def readline
127
+ line = Readline.readline("#{@shell.clients}> ", true)
128
+ Readline::HISTORY.pop if /^\s*$/ =~ line
129
+ line
130
+ end
131
+ end
132
+
133
+ def console
134
+ @console ||= $stdin.tty? ? ReadlineConsole.new(self) : SimpleConsole.new
135
+ end
136
+
137
+ attr_reader :clients
138
+
139
+ def initialize(broker)
140
+ @broker = broker
141
+ @clients = nil
142
+ @msg_lock = Mutex.new
143
+ end
144
+
145
+ def send_script(line, &handler)
146
+ @msg_lock.synchronize do
147
+ @broker.cmd_queue.push(line)
148
+ yield read_msg
149
+ end
150
+ end
151
+
152
+ def read_msg
153
+ msg = @broker.msg_queue.pop
154
+ # p msg
155
+ @clients = msg.clients
156
+ msg
157
+ end
158
+
159
+ def object_props(object)
160
+ send_script("P" + object) do |r|
161
+ if r.type == "V" && r.value
162
+ r.value.split(/,/)
163
+ else
164
+ []
165
+ end
166
+ end
167
+ end
168
+
169
+ def run
170
+ # read and print messages in background
171
+ Thread.start do
172
+ while IO.select([@broker.msg_queue.reader], nil, nil, nil)
173
+ @msg_lock.synchronize do
174
+ if IO.select([@broker.msg_queue.reader], nil, nil, 0)
175
+ msg = read_msg
176
+ puts msg.value || ""
177
+ Process.kill "INT", $$ # interrupt readline
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ console.show_banner
184
+
185
+ begin
186
+ loop do
187
+ break unless line = console.readline
188
+ send_script("E" + line) do |msg|
189
+ puts msg.value if msg.value
190
+ end
191
+ end
192
+ rescue Interrupt
193
+ retry
194
+ rescue SystemExit
195
+ return
196
+ rescue Exception => e
197
+ $stderr.puts "#{e.inspect} at:\n#{e.backtrace.join("\n")}"
198
+ ensure
199
+ begin
200
+ console.close
201
+ rescue Exception => e
202
+ $stderr.puts "failed to close console: #{e.inspect}"
203
+ end
204
+ end
205
+ @broker.shutdown
206
+ end
207
+ end
208
+
209
+ class ProxyServer < WEBrick::AsyncHTTPProxyServer
210
+ def initialize(broker, args = {})
211
+ @broker = broker
212
+ @cmd_queue = PipeQueue.new
213
+ @clients = []
214
+ super({:ProxyContentHandler => method(:handle_content).to_proc}.merge(args))
215
+ end
216
+
217
+ def start
218
+ Thread.start do
219
+ sleep 1
220
+ begin
221
+ poll_cmd_queue
222
+ rescue Exception => e
223
+ p e
224
+ end
225
+ end
226
+ super
227
+ end
228
+
229
+ def service(req, res)
230
+ if req.path == "#{SCRIPT_DIR}agent.js"
231
+ res.content_type = "application/x-javascript"
232
+ res.body = File.read(File.join(File.dirname(__FILE__), "agent.js"))
233
+ elsif req.path == "#{SCRIPT_DIR}poll"
234
+ serve_script(req, res)
235
+ else
236
+ if req.header["accept-encoding"] && !req.header["accept-encoding"].empty?
237
+ if req.header["accept-encoding"].first.split(/,/).include?("gzip")
238
+ req.header["accept-encoding"] = ["gzip"]
239
+ else
240
+ req.header.delete "accept-encoding"
241
+ end
242
+ end
243
+ super
244
+ end
245
+ end
246
+
247
+ def format_clients
248
+ @clients.map{|c|"[#{URI.parse(c.header["referer"].to_s).host}]"}.join(",")
249
+ end
250
+
251
+ def poll_cmd_queue
252
+ while @status == :Running
253
+ cmd = @broker.cmd_queue.pop
254
+ cmd_line = cmd[1...(cmd.size)]
255
+ if cmd_line.strip.empty?
256
+ @broker.msg_queue.push(Message.new(nil, "V", format_clients))
257
+ else
258
+ @cmd_queue.push(cmd)
259
+ end
260
+ end
261
+ end
262
+
263
+ def serve_script(req, res)
264
+ # puts "serve:#{req.body}"
265
+ @clients << req
266
+ if req.body && req.body =~ /^(\+?)([VE])/
267
+ more = $1 == '+'
268
+ type = $2
269
+ # V: value
270
+ # E: error
271
+ @broker.msg_queue.push Message.new(URI.decode($'), type, format_clients)
272
+ if more
273
+ res.content_type = "text/plain"
274
+ res.body = ''
275
+ return
276
+ end
277
+ else
278
+ # empty message - maybe a new client connected?
279
+ @broker.msg_queue.push Message.new(nil, "A", format_clients)
280
+ end
281
+ while @status == :Running
282
+ req_socket = req.instance_eval{@socket}
283
+ sockets = [req_socket, @cmd_queue.reader]
284
+ if @clients.first != req
285
+ # don't poll cmd_queue if this is request is not the first one
286
+ sockets.pop
287
+ end
288
+ r = IO.select(sockets, nil, nil, 1)
289
+ if r
290
+ if r[0].include?(@cmd_queue.reader)
291
+ line = @cmd_queue.pop
292
+ res.content_type = "text/plain"
293
+ res.body = line
294
+ return
295
+ elsif r[0].include?(req_socket)
296
+ @clients.delete(req); req = nil
297
+ @broker.msg_queue.push(Message.new("aborted", "A", format_clients))
298
+ raise WEBrick::HTTPStatus::EOFError
299
+ end
300
+ end
301
+ end
302
+ # server is shutting down
303
+ raise WEBrick::HTTPStatus::EOFError
304
+ ensure
305
+ @clients.delete(req) if req
306
+ end
307
+
308
+ def handle_content(req, res)
309
+ # $stderr.puts "handle_content:type=#{res.content_type}, status=#{res.status}, encoding=#{res.header["content-encoding"]}"
310
+ if res.content_type =~ %r{^text/html} && res.status == 200
311
+ res.flush_body
312
+ # we cannot always trust content_type, so check if the content looks like html
313
+ body = res.body
314
+ if res.header["content-encoding"] == "gzip"
315
+ body = Zlib::GzipReader.new(StringIO.new(body)).read
316
+ end
317
+ if body =~ /^\s*</
318
+ body = body.dup
319
+ # puts "injecting javascript"
320
+ script_tag = '<script type="text/javascript" src="/_remote_js_proxy/agent.js"></script>'
321
+ unless body.sub!(%r{<head( .*?)?>}i){|s|s+script_tag}
322
+ body = script_tag + body
323
+ end
324
+ if res.header["content-encoding"] == "gzip"
325
+ io = StringIO.new
326
+ writer = Zlib::GzipWriter.new(io)
327
+ writer.write(body)
328
+ writer.close
329
+ res.body = io.string
330
+ else
331
+ res.body = body
332
+ end
333
+ res.content_length = res.body.size if res.content_length
334
+ end
335
+ end
336
+ end
337
+ end
338
+ end
339
+