em-simple_telnet 0.0.1 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.rdoc +47 -0
  2. data/lib/em-simple_telnet.rb +83 -82
  3. metadata +22 -5
data/README.rdoc ADDED
@@ -0,0 +1,47 @@
1
+ == EventMachine::Protocols::SimpleTelnet
2
+
3
+ Provides telnet client functionality.
4
+
5
+ This class derived from the Net::Telnet class in Ruby's standard library.
6
+ It was developed with simplicity in mind. It tries to hide the complexity
7
+ that normally comes with asynchronous programming using the Fiber class
8
+ found in Ruby 1.9.
9
+
10
+ == Overview
11
+
12
+ The telnet protocol allows a client to login remotely to a user account on a
13
+ server and execute commands via a shell. The equivalent is done by creating
14
+ a EventMachine::Protocols::SimpleTelnet instance with the <tt>:host</tt>
15
+ option set to your host along with a block which defines the task to be done
16
+ on the host. Inside the task you can issue one or more #cmd calls. After the
17
+ block is executed, #close is automatically called.
18
+
19
+ This class can also be used to connect to non-telnet services, such as SMTP
20
+ or HTTP. In this case, you normally want to provide the <tt>:port</tt>
21
+ option to specify the port to connect to, and set the <tt>:telnet_mode</tt>
22
+ option to +false+ to prevent the client from attempting to interpret telnet
23
+ command sequences. Generally, #login will not work with other protocols,
24
+ and you have to handle authentication yourself.
25
+
26
+ == Examples
27
+
28
+ EventMachine.run do
29
+
30
+ opts = {
31
+ host: "localhost",
32
+ username: "user",
33
+ password: "secret",
34
+ }
35
+
36
+ EM::P::SimpleTelnet.new(opts) do |host|
37
+ # already logged in
38
+ puts host.cmd("ls -la")
39
+ end
40
+ end
41
+
42
+ == References
43
+
44
+ There are a large number of RFCs relevant to the Telnet protocol.
45
+ RFCs 854-861 define the base protocol. For a complete listing
46
+ of relevant RFCs, see
47
+ http://www.omnifarious.org/~hopper/technical/telnet-rfc.html
@@ -15,27 +15,18 @@ require "eventmachine"
15
15
  # still working on a deferred action or not.
16
16
  #
17
17
  module EventMachine # :nodoc:
18
- def self.defers_finished?
19
- (not defined? @threadqueue or (tq=@threadqueue).nil? or tq.empty? ) and
20
- (not defined? @resultqueue or (rq=@resultqueue).nil? or rq.empty? ) and
21
- (not defined? @threadpool or (tp=@threadpool).nil? or tp.none? {|t|t[:working]})
22
- end
18
+ # ensure they're always defined
19
+ @threadpool = @threadqueue = @resultqueue = nil
23
20
 
24
- def self.spawn_threadpool
25
- until @threadpool.size == @threadpool_size.to_i
26
- thread = Thread.new do
27
- Thread.current.abort_on_exception = true
28
- while true
29
- Thread.current[:working] = false
30
- op, cback = *@threadqueue.pop
31
- Thread.current[:working] = true
32
- result = op.call
33
- @resultqueue << [result, cback]
34
- EventMachine.signal_loopbreak
35
- end
36
- end
37
- @threadpool << thread
38
- end
21
+ ##
22
+ # Returns +true+ if all deferred actions are done executing and their
23
+ # callbacks have been fired.
24
+ #
25
+ def self.defers_finished?
26
+ return false if @threadqueue and not @threadqueue.empty?
27
+ return false if @resultqueue and not @resultqueue.empty?
28
+ return false if @threadpool and @threadqueue.num_waiting != @threadpool.size
29
+ return true
39
30
  end
40
31
  end
41
32
 
@@ -261,32 +252,39 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
261
252
  opts
262
253
  ]
263
254
 
264
- # start establishing the connection
265
- connection = EventMachine.connect(*params)
255
+ begin
256
+ # start establishing the connection
257
+ connection = EventMachine.connect(*params)
266
258
 
267
- # set callback to be executed when connection establishing
268
- # fails/succeeds
269
- f = Fiber.current
270
- connection.connection_state_callback = lambda do |obj=nil|
271
- @connection_state_callback = nil
272
- f.resume obj
273
- end
259
+ # set callback to be executed when connection establishing
260
+ # fails/succeeds
261
+ f = Fiber.current
262
+ connection.connection_state_callback = ->(o){ f.resume(o) }
274
263
 
275
- # block here and get result from establishing connection
276
- state = Fiber.yield
264
+ # block here and get result from establishing connection
265
+ state = Fiber.yield
277
266
 
278
- # raise if exception (e.g. Telnet::ConnectionFailed)
279
- raise state if state.is_a? Exception
267
+ # raise if exception (e.g. Telnet::ConnectionFailed)
268
+ raise state if state.is_a? Exception
280
269
 
281
- # login
282
- connection.instance_eval { login }
270
+ # login
271
+ connection.instance_eval { login }
283
272
 
284
- begin
285
- yield connection
273
+ begin
274
+ yield connection
275
+ ensure
276
+ # Use #close so a subclass can execute some kind of logout command
277
+ # before the connection is closed.
278
+ connection.close
279
+ end
286
280
  ensure
287
- # Use #close so a subclass can execute some kind of logout command
288
- # before the connection is closed.
289
- connection.close
281
+ # close the connection in any case
282
+ if connection
283
+ connection.close_connection_after_writing
284
+
285
+ # give some time to send the remaining data, which should be nothing
286
+ EventMachine.add_timer(2) { connection.close_connection }
287
+ end
290
288
  end
291
289
 
292
290
  return connection
@@ -574,7 +572,7 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
574
572
  # we only need to do something if there's a connection state callback
575
573
  return unless @connection_state_callback
576
574
 
577
- # we ensure there's no timer running to check the input buffer
575
+ # we ensure there's no timer running for checking the input buffer
578
576
  if @check_input_buffer_timer
579
577
  @check_input_buffer_timer.cancel
580
578
  @check_input_buffer_timer = nil
@@ -588,9 +586,9 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
588
586
  # We postpone checking the input buffer by one second because the regular
589
587
  # expression matches can get quite slow.
590
588
  #
591
- # So as long as data is received (continuously), the input buffer is not
592
- # checked. It's only checked one second after the whole output has been
593
- # received.
589
+ # So as long as data is being received (continuously), the input buffer
590
+ # is not checked. It's only checked one second after the whole output
591
+ # has been received.
594
592
  @check_input_buffer_timer = EventMachine::Timer.new(1) do
595
593
  @check_input_buffer_timer = nil
596
594
  check_input_buffer
@@ -607,33 +605,32 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
607
605
 
608
606
  ##
609
607
  # Checks the input buffer (<tt>@input_buffer</tt>) for the prompt we're
610
- # waiting for. Calls the proc in <tt>@connection_state_callback</tt> if the
608
+ # waiting for. Calls #call_connection_state_callback with the output if the
611
609
  # prompt has been found. Thus, call this method *only* if
612
610
  # <tt>@connection_state_callback</tt> is set!
613
611
  #
614
- # If <tt>@telnet_options[:wait_time]</tt> is set, this amount of seconds is
615
- # waited (call to <tt>@connection_state_callback</tt> is scheduled) after
616
- # seeing what looks like the prompt before firing the
617
- # <tt>@connection_state_callback</tt> is fired, so more data can come until
618
- # the real prompt is reached. This is useful for commands which will cause
619
- # multiple prompts to be sent.
612
+ # If <tt>@telnet_options[:wait_time]</tt> is set, it will wait this amount
613
+ # of seconds after seeing what looks like the prompt before calling
614
+ # #call_connection_state_callback. This way, more data can be received
615
+ # until the real prompt is received. This is useful for commands that send
616
+ # multiple prompts.
620
617
  #
621
618
  def check_input_buffer
622
- if md = @input_buffer.match(@telnet_options[:prompt])
623
- blk = lambda do
624
- @last_prompt = md.to_s # remember last prompt
625
- output = md.pre_match + @last_prompt
626
- @input_buffer = md.post_match
627
- @connection_state_callback.call(output)
628
- end
619
+ return unless md = @input_buffer.match(@telnet_options[:prompt])
629
620
 
630
- if s = @telnet_options[:wait_time]
631
- # fire @connection_state_callback after s seconds
632
- @wait_time_timer = EventMachine::Timer.new(s, &blk)
633
- else
634
- # fire @connection_state_callback now
635
- blk.call
636
- end
621
+ blk = lambda do
622
+ @last_prompt = md.to_s # remember last prompt
623
+ output = md.pre_match + @last_prompt
624
+ @input_buffer = md.post_match
625
+ call_connection_state_callback(output)
626
+ end
627
+
628
+ if s = @telnet_options[:wait_time] and s > 0
629
+ # fire @connection_state_callback after s seconds
630
+ @wait_time_timer = EventMachine::Timer.new(s, &blk)
631
+ else
632
+ # fire @connection_state_callback now
633
+ blk.call
637
634
  end
638
635
  end
639
636
 
@@ -675,10 +672,7 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
675
672
  f = Fiber.current
676
673
 
677
674
  # will be called by #receive_data to resume at "Fiber.yield" below
678
- @connection_state_callback = lambda do |output|
679
- @connection_state_callback = nil
680
- f.resume(output)
681
- end
675
+ @connection_state_callback = ->(output){ f.resume(output) }
682
676
 
683
677
  result = Fiber.yield
684
678
 
@@ -769,8 +763,7 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
769
763
  log_command(opts[:hide] ? "<hidden command>" : command)
770
764
 
771
765
  # send the command
772
- sendcmd = opts[:raw_command] ? :print : :puts
773
- self.__send__(sendcmd, command)
766
+ opts[:raw_command] ? print(command) : puts(command)
774
767
 
775
768
  # wait for the output
776
769
  waitfor(opts[:prompt], opts)
@@ -855,10 +848,10 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
855
848
  # Decreases <tt>@@_telnet_connection_count</tt> by one and calls #close_logs.
856
849
  #
857
850
  # After that and if <tt>@connection_state_callback</tt> is set, it takes a
858
- # look on <tt>@connection_state</tt>. If it was <tt>:connecting</tt>, calls
859
- # <tt>@connection_state_callback</tt> with a new instance of
860
- # ConnectionFailed. If it was <tt>:waiting_for_prompt</tt>, calls the
861
- # callback with a new instance of TimeoutError.
851
+ # look at <tt>@connection_state</tt>. If it was <tt>:connecting</tt>, calls
852
+ # #call_connection_state_callback with a new instance of ConnectionFailed.
853
+ # If it was <tt>:waiting_for_prompt</tt>, calls the same method with a new
854
+ # instance of TimeoutError.
862
855
  #
863
856
  # Finally, the <tt>@connection_state</tt> is set to +closed+.
864
857
  #
@@ -888,23 +881,31 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
888
881
  @connection_state = :closed
889
882
  end
890
883
 
884
+ ##
885
+ # Calls the @connection_state_callback with _obj_ and sets it to +nil+.
886
+ #
887
+ # Call this method *only* if <tt>@connection_state_callback</tt> is set!
888
+ #
889
+ def call_connection_state_callback(obj=nil)
890
+ callback, @connection_state_callback = @connection_state_callback, nil
891
+ callback.call(obj)
892
+ end
893
+
891
894
  ##
892
895
  # Called by EventMachine after the connection is successfully established.
893
896
  #
894
897
  def connection_completed
895
898
  @connection_state = :connected
896
- @connection_state_callback.call if @connection_state_callback
899
+ call_connection_state_callback if @connection_state_callback
897
900
  end
898
901
 
899
902
  ##
900
- # Tells EventMachine to close the connection after sending what's in the
901
- # output buffer. Redefine this method to execute some logout command like
902
- # +exit+ or +logout+ before the connection is closed. Don't forget: The
903
- # command will probably not return a prompt, so use #puts, which doesn't
904
- # wait for a prompt.
903
+ # Redefine this method to execute some logout command like +exit+ or
904
+ # +logout+ before the connection is closed. Don't forget: The command will
905
+ # probably not return a prompt, so use #puts, which doesn't wait for a
906
+ # prompt.
905
907
  #
906
908
  def close
907
- close_connection_after_writing
908
909
  end
909
910
 
910
911
  ##
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: em-simple_telnet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,16 +10,34 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
  date: 2011-11-21 00:00:00.000000000 Z
13
- dependencies: []
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0.beta.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.0.beta.2
14
30
  description: This library provides a very simple way to connect to telnet servers
15
31
  using EventMachine in a seemingly synchronous manner.
16
32
  email:
17
33
  - paddor@gmail.com
18
34
  executables: []
19
35
  extensions: []
20
- extra_rdoc_files: []
36
+ extra_rdoc_files:
37
+ - README.rdoc
21
38
  files:
22
39
  - lib/em-simple_telnet.rb
40
+ - README.rdoc
23
41
  homepage: http://github.com/paddor/em-simple_telnet
24
42
  licenses: []
25
43
  post_install_message:
@@ -40,9 +58,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
40
58
  version: '0'
41
59
  requirements: []
42
60
  rubyforge_project:
43
- rubygems_version: 1.8.11
61
+ rubygems_version: 1.8.23
44
62
  signing_key:
45
63
  specification_version: 3
46
64
  summary: Simple telnet client on EventMachine
47
65
  test_files: []
48
- has_rdoc: