jscmd 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,15 @@
1
1
  = *SVN*
2
2
 
3
+ = 0.2.0 2007-10-08
4
+
5
+ * Console API now works in Safari.
6
+ * Let polling time out after 15 seconds. (Can be configured with --reconnect-interval option)
7
+ * Fixed crashes when there are many commands sent from browser at once.
8
+ * Added --disable-proxy-injection option to turn off the debugging ability of proxy server.
9
+ * Added --trace-proxy-request option to trace the request and response sent to/from the proxy server.
10
+
11
+ = 0.1.0 2007-05-13
12
+
3
13
  * Refactored everything
4
14
  * Now acts as Stomp client
5
15
  * Embedded Stomp Server
data/bin/jscmd CHANGED
@@ -24,6 +24,7 @@ module JSCommander
24
24
  :sbind => "localhost",
25
25
  :sjournal => ".stompserver",
26
26
  :connect_to => "stomp://localhost:61613/",
27
+ :reconnect_interval => 15
27
28
  }
28
29
  MANDATORY_OPTIONS = %w()
29
30
 
@@ -66,14 +67,21 @@ BANNER
66
67
  opts.on("--connect-to=URL", String,
67
68
  "Specify URL of Stomp server in stomp://[user:pass@]host:port/ format.",
68
69
  "(default: stomp://localhost:61613/)") { |OPTIONS[:connect_to]| }
70
+ opts.on("--reconnect-interval=SECONDS", Integer,
71
+ "Configure polling timeout of the proxy server. (default: 15)") { |OPTIONS[:reconnect_interval]| }
72
+ opts.on("--disable-proxy-injection",
73
+ "Disable proxy injection and act as a plain proxy server.") { |OPTIONS[:disable_proxy_injection]| }
69
74
  opts.on("--debug-proxy",
70
75
  "Enable debugging in proxy server.") { |OPTIONS[:debug_proxy]| }
76
+ opts.on("--trace-proxy-request",
77
+ "Trace HTTP requests sent to proxy server.") { |OPTIONS[:trace_proxy_request]| }
71
78
  opts.on("--debug-msg=[LOGFILE]",
72
79
  "Print messages that are being sent.") { |OPTIONS[:debug_msg]| }
73
80
  opts.on("--never-fork",
74
81
  "Never fork - use threads only (experimental).") { |OPTIONS[:never_fork]| }
75
82
  opts.on("-h", "--help",
76
83
  "Show this help message.") { puts opts; exit }
84
+ opts.version = Jscmd::VERSION::STRING
77
85
  opts.parse!(ARGV)
78
86
 
79
87
  if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
@@ -289,10 +297,20 @@ BANNER
289
297
 
290
298
  class ProxyLauncher < ThreadLauncher
291
299
  def run
292
- proxy_options = {:Port => OPTIONS[:port], :BindAddress => OPTIONS[:bind], :AccessLog => [], :ProxyVia => nil}
300
+ proxy_options = {
301
+ :Port => OPTIONS[:port],
302
+ :BindAddress => OPTIONS[:bind],
303
+ :AccessLog => [],
304
+ :ProxyVia => nil,
305
+ :disable_proxy_injection => OPTIONS[:disable_proxy_injection],
306
+ :reconnect_interval => OPTIONS[:reconnect_interval]
307
+ }
293
308
  if OPTIONS[:debug_proxy]
294
309
  proxy_options[:Logger] = WEBrick::Log.new(nil, WEBrick::BasicLog::DEBUG)
295
310
  end
311
+ if OPTIONS[:trace_proxy_request]
312
+ proxy_options[:trace_request] = true
313
+ end
296
314
  proxy = ProxyServer.new(@broker, proxy_options)
297
315
  @proxy = proxy
298
316
  proxy.start
@@ -8,15 +8,15 @@ my $stomp = Net::Stomp->new({hostname => 'localhost', port => '61613'});
8
8
  $stomp->connect({login => '', passcode => ''});
9
9
  $stomp->subscribe({destination => "/topic/events"});
10
10
  my $id = rand();
11
- $stomp->send({destination => "/topic/commands",
12
- "jscmd.type" => "eval",
13
- "jscmd.id" => $id,
14
- body => "document.title"});
11
+ $stomp->send({destination => "/topic/commands",
12
+ "jscmd.type" => "eval",
13
+ "jscmd.id" => $id,
14
+ body => "document.title"});
15
15
  for (;;) {
16
16
  my $frame = $stomp->receive_frame;
17
17
  if ($id eq $frame->headers->{"jscmd.in-reply-to"}) {
18
- print $frame->body . "\n";
19
- last;
18
+ print $frame->body . "\n";
19
+ last;
20
20
  }
21
- }
21
+ }
22
22
  $stomp->disconnect;
@@ -1,5 +1,7 @@
1
1
  # Dir[File.join(File.dirname(__FILE__), 'jscmd/**/*.rb')].sort.each { |lib| require lib }
2
2
 
3
+ require "jscmd/version"
4
+
3
5
  # message
4
6
  require "jscmd/message"
5
7
 
@@ -12,3 +14,4 @@ require "jscmd/stompproxy"
12
14
  require "jscmd/proxyserver"
13
15
  require "jscmd/shell"
14
16
  require "jscmd/urlforwarder"
17
+
@@ -53,9 +53,32 @@
53
53
  return value;
54
54
  },
55
55
 
56
- poll: function() {
57
- var content = this.queue.length > 0 ? this.queue.shift() : {type: "connect", body: ""};
56
+ send: function() {
57
+ if (this.queue.length == 0) return;
58
+ var self = this;
59
+ this.request("send", {
60
+ content: function() { return self.queue.shift() },
61
+ setup: function(xhr) { xhr.setRequestHeader("X-JSCmd-More", "true"); },
62
+ nextRequest: function() { self.send() }
63
+ });
64
+ },
65
+
66
+ receive: function() {
67
+ var self = this;
68
+ this.request("receive", {
69
+ content: function() { return self.queue.length > 0 ? self.queue.shift() : {type: "connect", body: ""}},
70
+ setup: function(xhr) {},
71
+ nextRequest: function() { self.receive() }
72
+ });
73
+ },
74
+
75
+ request: function(name, handler) {
76
+ if (this.currentXHR[name]) return;
77
+ if (this.aborted) return;
78
+ var content = handler.content();
79
+ if (!content) return;
58
80
  var xhr = this.createXHR();
81
+ this.currentXHR[name] = xhr;
59
82
  xhr.open("POST", "/_remote_js_proxy/poll", true);
60
83
  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
61
84
  xhr.setRequestHeader("X-JSCmd-Agent-Id", this.agentId);
@@ -65,7 +88,7 @@
65
88
  var self = this;
66
89
  xhr.onreadystatechange = function() {
67
90
  if (xhr.readyState == 4) {
68
- delete self.currentXHR;
91
+ self.currentXHR[name] = null;
69
92
  try {
70
93
  if (xhr.status == 200) {
71
94
  var commandId = xhr.getResponseHeader("X-JSCmd-Command-Id");
@@ -84,6 +107,7 @@
84
107
  // just evaluate
85
108
  value = self.evaluate.apply(window, [script, true]);
86
109
  type = "value";
110
+ } else if (requestType == 'nop') {
87
111
  } else {
88
112
  value = "bad request: " + script;
89
113
  type = "error";
@@ -92,27 +116,30 @@
92
116
  value = e.toString();
93
117
  type = "error";
94
118
  }
95
- self.queue.push({inReplyTo: commandId, type: type, body: value});
119
+ if (type) {
120
+ self.queue.push({inReplyTo: commandId, type: type, body: value});
121
+ }
96
122
  }
123
+ } else {
124
+ setTimeout(handler.nextRequest, 5000);
125
+ return;
97
126
  }
98
127
  } catch (e) {
99
- // failed to poll; ignore
128
+ setTimeout(handler.nextRequest, 5000);
129
+ return;
100
130
  }
101
131
  if (!self.aborted) {
102
- if (self.queue.length > 0) {
103
- self.poll();
104
- } else {
105
- // if polling failed, try again later
106
- setTimeout(function() { self.poll() }, 5000);
107
- }
132
+ setTimeout(handler.nextRequest, 0);
108
133
  }
109
134
  }
110
135
  }
111
- this.currentXHR = xhr;
136
+ handler.setup(xhr);
137
+ /*
112
138
  if (this.queue.length > 0) {
113
139
  // mark that there are more messages pending
114
140
  xhr.setRequestHeader("X-JSCmd-More", "true");
115
141
  }
142
+ */
116
143
  xhr.setRequestHeader("X-JSCmd-Type", content.type);
117
144
  xhr.send(encodeURI(content.body));
118
145
  },
@@ -130,23 +157,21 @@
130
157
  this.startPoll = function() {}
131
158
  if (window.top.parent != window) return;
132
159
  this.generateAgentId();
133
- this.poll();
160
+ this.receive();
161
+ var self = this;
162
+ setInterval(function() { self.receive() }, 5000);
134
163
  },
135
164
 
136
- abortPoll: function(retry) {
137
- if (!retry) {
138
- // don't retry any more
139
- this.aborted = true;
140
- }
141
- if (this.currentXHR) {
142
- this.currentXHR.abort();
165
+ abortPoll: function() {
166
+ this.aborted = true;
167
+ if (this.currentXHR["receive"]) {
168
+ this.currentXHR["receive"].abort();
143
169
  }
144
170
  },
145
171
 
146
172
  pushValue: function(value) {
147
173
  this.queue.push(value);
148
- this.abortPoll(true);
149
- this.startPoll(); // in case startPoll has not been called yet
174
+ this.send();
150
175
  },
151
176
 
152
177
  attachEvent: function(event, handler) {
@@ -253,7 +278,7 @@
253
278
  }
254
279
  setTimeout(function() { self.startPoll() }, 3000);
255
280
 
256
- if (!window.console) {
281
+ //if (!window.console) {
257
282
  window.console = {};
258
283
  var levels = ["debug", "info", "warn", "error"];
259
284
  for (var i = 0; i < levels.length; i++) {
@@ -270,6 +295,8 @@
270
295
  })();
271
296
  }
272
297
  window.console.log = window.console.info;
273
- }
274
- }
298
+ //}
299
+ },
300
+
301
+ currentXHR: {}
275
302
  }).init();
@@ -22,7 +22,8 @@ type - Type of the message. (e.g. "eval")
22
22
  id - unique value that identifies this command. (optional; only used with "eval" and "properties" types)
23
23
 
24
24
  Types:
25
- ping - Request "update_clients". The body must be empty.
25
+ ping - Request the status of clients from shell to proxy server. It will not be routed to web browser. The body must be empty.
26
+ nop - Empty message sent from proxy server to web browser when there were no commands within reconnect_interval seconds.
26
27
  eval - Evaluate body.
27
28
  properties - Evaluate body and get list of properties.
28
29
 
@@ -27,15 +27,18 @@ end
27
27
  module JSCommander
28
28
  class ProxyServer < WEBrick::AsyncHTTPProxyServer
29
29
  SCRIPT_DIR = "/_remote_js_proxy/"
30
+ attr_reader :reconnect_interval
30
31
 
31
32
  class CommandDispatcher
32
33
  class CLIENT_ABORTED < StandardError; end
33
34
  class SHUTDOWN < StandardError; end
34
35
 
35
36
  def initialize(server, command_queue)
37
+ @server = server
36
38
  @client_mutex = Mutex.new
37
39
  @client_ready = ConditionVariable.new
38
40
  @clients = []
41
+ @last_clients = nil
39
42
  @thread = Thread.start do
40
43
  while server.status != :Shutdown
41
44
  command = command_queue.pop
@@ -55,12 +58,16 @@ module JSCommander
55
58
  end
56
59
  end
57
60
 
61
+ def last_clients
62
+ @last_clients
63
+ end
64
+
58
65
  def format_clients
59
- @clients.map{|c|"[#{c.hostname}]"}.join(",")
66
+ @last_clients = @clients.map{|c|"[#{c.hostname}]"}.join(",")
60
67
  end
61
68
 
62
69
  def new_client(req, &block)
63
- client = Client.new(req)
70
+ client = Client.new(req, @server.reconnect_interval)
64
71
  @client_mutex.synchronize do
65
72
  @clients << client
66
73
  @client_ready.signal
@@ -75,14 +82,25 @@ module JSCommander
75
82
  end
76
83
 
77
84
  class Client
78
- def initialize(req)
85
+ def initialize(req, reconnect_interval)
79
86
  @request = req
80
87
  @queue = Queue.new
88
+ @pop_time = nil
81
89
  req_socket = req.instance_eval{@socket}
82
90
  @thread = Thread.start do
83
91
  sleep 1 until req_socket.eof?
84
92
  @queue.push CLIENT_ABORTED
85
93
  end
94
+ @reconnect_thread = Thread.start do
95
+ loop do
96
+ if @pop_time && Time.now >= @pop_time + reconnect_interval
97
+ @pop_time = nil
98
+ @queue.push Message.new(nil, :type => "nop")
99
+ break
100
+ end
101
+ sleep 1
102
+ end
103
+ end
86
104
  end
87
105
 
88
106
  def hostname
@@ -98,12 +116,19 @@ module JSCommander
98
116
  @queue.push(command)
99
117
  end
100
118
 
119
+ def abort
120
+ @thread.kill
121
+ end
122
+
101
123
  def pop_command
124
+ @pop_time = Time.now
102
125
  r = @queue.pop
126
+ @pop_time = nil
103
127
  # thread is automatically stopped if r == CLIENT_ABORTED
104
128
  if r != CLIENT_ABORTED
105
129
  @thread.kill
106
130
  end
131
+ @reconnect_thread.kill
107
132
  r
108
133
  end
109
134
  end
@@ -124,7 +149,17 @@ module JSCommander
124
149
  end
125
150
  end
126
151
  @clients = []
127
- super({:ProxyContentHandler => method(:handle_content).to_proc}.merge(args))
152
+ options = args[:disable_proxy_injection] ? args : {:ProxyContentHandler => method(:handle_content).to_proc}.merge(args)
153
+ @trace_request = args[:trace_request]
154
+ @requests = 0
155
+ @reconnect_interval = args[:reconnect_interval]
156
+ super(options)
157
+ end
158
+
159
+ def trace(msg)
160
+ if @trace_request
161
+ $stderr.puts "[TRACE] " + msg
162
+ end
128
163
  end
129
164
 
130
165
  def service(req, res)
@@ -145,6 +180,19 @@ module JSCommander
145
180
  end
146
181
  end
147
182
 
183
+ alias_method :do_service, :service
184
+
185
+ def service(req, res)
186
+ @requests += 1
187
+ trace(">" * @requests + " #{req.unparsed_uri}")
188
+ begin
189
+ do_service(req, res)
190
+ ensure
191
+ trace("<" * @requests + " #{req.unparsed_uri}")
192
+ @requests -= 1
193
+ end
194
+ end
195
+
148
196
  def shutdown
149
197
  @command_dispatcher.shutdown
150
198
  super
@@ -156,7 +204,7 @@ module JSCommander
156
204
  @command_dispatcher.new_client(req) do |client|
157
205
  type = req.header["x-jscmd-type"].first
158
206
  if type && type != "connect"
159
- @broker.send("events", Message.new(URI.decode(req.body),
207
+ @broker.send("events", Message.new(URI.decode(req.body || ""),
160
208
  :type => type,
161
209
  :clients => @command_dispatcher.format_clients,
162
210
  "in-reply-to" => req.header["x-jscmd-in-reply-to"].first))
@@ -164,12 +212,15 @@ module JSCommander
164
212
  # immediately send empty response to wait for another event
165
213
  res.content_type = "text/plain"
166
214
  res.body = ''
215
+ client.abort
167
216
  return
168
217
  end
169
218
  else
170
219
  # new connection
171
- @broker.send("events", Message.new(nil, :type => "connect",
172
- :clients => @command_dispatcher.format_clients))
220
+ if @command_dispatcher.last_clients != @command_dispatcher.format_clients
221
+ @broker.send("events", Message.new(nil, :type => "connect",
222
+ :clients => @command_dispatcher.format_clients))
223
+ end
173
224
  end
174
225
 
175
226
  command = client.pop_command
@@ -81,6 +81,9 @@ module JSCommander
81
81
  @clients = nil
82
82
  @msg_lock = Mutex.new
83
83
  @wait_for_event = {}
84
+ @interrupt_lock = Mutex.new
85
+ @interrupt_ready_cv = ConditionVariable.new
86
+ @interrupt_ready = false
84
87
  end
85
88
 
86
89
  def generate_id
@@ -161,6 +164,10 @@ module JSCommander
161
164
  when "connect"
162
165
  puts
163
166
  end
167
+ @interrupt_lock.synchronize do
168
+ @interrupt_ready_cv.wait(@interrupt_lock) until @interrupt_ready
169
+ @interrupt_ready = false
170
+ end
164
171
  if Process.methods.include?("kill")
165
172
  Process.kill "INT", $$ # interrupt readline
166
173
  else
@@ -169,8 +176,12 @@ module JSCommander
169
176
  end
170
177
  end
171
178
  end
172
-
179
+
173
180
  begin
181
+ @interrupt_lock.synchronize do
182
+ @interrupt_ready = true
183
+ @interrupt_ready_cv.broadcast
184
+ end
174
185
  loop do
175
186
  break unless line = console.readline
176
187
  if line.chomp == ''
@@ -1,7 +1,7 @@
1
1
  module Jscmd #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 1
4
+ MINOR = 2
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: jscmd
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.0
7
- date: 2007-05-13 00:00:00 +09:00
6
+ version: 0.2.0
7
+ date: 2007-10-08 00:00:00 +09:00
8
8
  summary: JavaScript console for any browser
9
9
  require_paths:
10
10
  - lib
@@ -57,10 +57,13 @@ test_files:
57
57
  - test/test_helper.rb
58
58
  - test/test_proxyserver.rb
59
59
  - test/test_shell.rb
60
- rdoc_options: []
61
-
62
- extra_rdoc_files: []
63
-
60
+ rdoc_options:
61
+ - --main
62
+ - README.txt
63
+ extra_rdoc_files:
64
+ - History.txt
65
+ - Manifest.txt
66
+ - README.txt
64
67
  executables:
65
68
  - jscmd
66
69
  extensions: []