jscmd 0.1.0 → 0.2.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.
@@ -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: []