em-simple_telnet 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/em-simple_telnet.rb +204 -102
  3. metadata +3 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8c4aab9248d4b5ccdcf26df97d65c76538547400
4
- data.tar.gz: c0f70fd5b4944e728c69efb73e0670c7b652b094
3
+ metadata.gz: 78218823aba97e9250159f56bbb7c666a52178f8
4
+ data.tar.gz: 3035ce24632a5f23788253e8ff9c022175711965
5
5
  SHA512:
6
- metadata.gz: 0826dd91834c7a2c0de84af593b8c3b5adfbbb5027e02e2474116f405613a550a855c47af9b92bdd0cd291e2f38aea2cb036640ac37b6ba205bed5c755cdcdd9
7
- data.tar.gz: aeb3ce7d37acff62fd09dad0956517ac4069478426d429a1d5cc5c5b6c87ea77ee215163a7045bd8a81efbf3dc886250883002d4793373c250a376990fcbe0f3
6
+ metadata.gz: 7d7c0638b72d5c0b1fe0cee058b9391d965edf491af743a6ab19572656d6e1cb717aecf1f05304a4b5441e0c57875dc209b1e48cd6c875b6a532292559ebd937
7
+ data.tar.gz: 3f0bb2ee015a659e426afce49fd3148fde96bee0e501cce2e8e909763c47e89046a79513b769eceab498ffda37c10decab1ebe35095c0ab0bd13774653a8e21a
@@ -187,6 +187,7 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
187
187
  fiber = Fiber.new do | callback |
188
188
  connection = connect(opts, &blk)
189
189
  callback.call if callback
190
+ @@_fiber_statuses.delete opts[:hostname]
190
191
  end
191
192
 
192
193
  if EventMachine.reactor_running?
@@ -229,19 +230,11 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
229
230
  # start establishing the connection
230
231
  connection = EventMachine.connect(*params)
231
232
 
232
- # set callback to be executed when connection establishing
233
- # fails/succeeds
234
- f = Fiber.current
235
- connection.connection_state_callback = ->(o){ f.resume(o) }
236
-
237
- # block here and get result from establishing connection
238
- state = Fiber.yield
239
-
240
- # raise if exception (e.g. Telnet::ConnectionFailed)
241
- raise state if state.is_a? Exception
233
+ # will be resumed by #connection_completed or #unbind
234
+ connection.pause_and_wait_for_result
242
235
 
243
236
  # login
244
- connection.instance_eval { login }
237
+ connection.__send__(:login)
245
238
 
246
239
  begin
247
240
  yield connection
@@ -365,11 +358,13 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
365
358
 
366
359
  @logged_in = nil
367
360
  @connection_state = :connecting
368
- @connection_state_callback = nil
361
+ f = Fiber.current
362
+ @fiber_resumer = ->(result = nil){ f.resume(result) }
369
363
  @input_buffer = ""
370
364
  @input_rest = ""
371
365
  @wait_time_timer = nil
372
366
  @check_input_buffer_timer = nil
367
+ @recently_received_data = "" if $DEBUG
373
368
 
374
369
  setup_logging
375
370
  end
@@ -386,8 +381,13 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
386
381
  # used telnet options Hash
387
382
  attr_reader :telnet_options
388
383
 
389
- # the callback executed after connection established or failed
390
- attr_accessor :connection_state_callback
384
+ # the callback executed again and again to resume this connection's Fiber
385
+ attr_accessor :fiber_resumer
386
+
387
+ def connection_state_callback
388
+ warn "#connection_state_callback deprecated. use #fiber_resumer instead"
389
+ fiber_resumer
390
+ end
391
391
 
392
392
  # last prompt matched
393
393
  attr_reader :last_prompt
@@ -428,6 +428,7 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
428
428
  #
429
429
  def timeout= seconds
430
430
  @telnet_options[:timeout] = seconds
431
+ # warn "#{node}: Setting comm_inactivity_timeout to #{seconds} ...".yellow
431
432
  set_comm_inactivity_timeout( seconds )
432
433
  end
433
434
 
@@ -492,6 +493,8 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
492
493
  # #log_output. Then calls #process_payload.
493
494
  #
494
495
  def receive_data data
496
+ @last_received_data = Time.now
497
+ @recently_received_data << data if $DEBUG
495
498
  if @telnet_options[:telnet_mode]
496
499
  c = @input_rest + data
497
500
  se_pos = c.rindex(/#{IAC}#{SE}/no) || 0
@@ -536,8 +539,8 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
536
539
 
537
540
  ##
538
541
  # Appends _buf_ to the <tt>@input_buffer</tt>.
539
- # Then cancels the @wait_time_timer if there was one.
540
- # Does nothing else if there's no @connection_state_callback.
542
+ # Then cancels the @wait_time_timer and @check_input_buffer_timer if they're
543
+ # set.
541
544
  #
542
545
  # Does some performance optimizations in case the input buffer is becoming
543
546
  # huge and finally calls #check_input_buffer.
@@ -546,77 +549,95 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
546
549
  # append output from server to input buffer and log it
547
550
  @input_buffer << buf
548
551
 
549
- # cancel the timer for wait_time value because we received more data
550
- if @wait_time_timer
551
- @wait_time_timer.cancel
552
- @wait_time_timer = nil
553
- end
552
+ case @connection_state
553
+ when :waiting_for_prompt
554
554
 
555
- # we only need to do something if there's a connection state callback
556
- return unless @connection_state_callback
555
+ # cancel the timer for wait_time value because we received more data
556
+ if @wait_time_timer
557
+ @wait_time_timer.cancel
558
+ @wait_time_timer = nil
559
+ end
557
560
 
558
- # we ensure there's no timer running for checking the input buffer
559
- if @check_input_buffer_timer
560
- @check_input_buffer_timer.cancel
561
- @check_input_buffer_timer = nil
562
- end
561
+ # we ensure there's no timer running for checking the input buffer
562
+ if @check_input_buffer_timer
563
+ @check_input_buffer_timer.cancel
564
+ @check_input_buffer_timer = nil
565
+ end
563
566
 
564
- if @input_buffer.size >= 100_000
565
- ##
566
- # if the input buffer is really big
567
- #
567
+ if @input_buffer.size >= 100_000
568
+ ##
569
+ # if the input buffer is really big
570
+ #
571
+
572
+ # We postpone checking the input buffer by one second because the regular
573
+ # expression matches can get quite slow.
574
+ #
575
+ # So as long as data is being received (continuously), the input buffer
576
+ # is not checked. It's only checked one second after the whole output
577
+ # has been received.
578
+ @check_input_buffer_timer = EventMachine::Timer.new(1) do
579
+ @check_input_buffer_timer = nil
580
+ check_input_buffer
581
+ end
582
+ else
583
+ ##
584
+ # as long as the input buffer is small
585
+ #
568
586
 
569
- # We postpone checking the input buffer by one second because the regular
570
- # expression matches can get quite slow.
571
- #
572
- # So as long as data is being received (continuously), the input buffer
573
- # is not checked. It's only checked one second after the whole output
574
- # has been received.
575
- @check_input_buffer_timer = EventMachine::Timer.new(1) do
576
- @check_input_buffer_timer = nil
587
+ # check the input buffer now
577
588
  check_input_buffer
578
589
  end
590
+ when :listening
591
+ @fiber_resumer.(buf)
592
+ when :connected, :sleeping
593
+ if $VERBOSE
594
+ warn "#{node}: Discarding data that was received while not waiting " +
595
+ "for data (state = #{@connection_state.inspect}): #{buf.inspect}"
596
+ end
579
597
  else
580
- ##
581
- # as long as the input buffer is small
582
- #
583
-
584
- # check the input buffer now
585
- check_input_buffer
598
+ raise "Don't know what to do with received data while being in " +
599
+ "connection state #{@connection_state.inspect}"
586
600
  end
587
601
  end
588
602
 
589
603
  ##
590
604
  # Checks the input buffer (<tt>@input_buffer</tt>) for the prompt we're
591
- # waiting for. Calls #call_connection_state_callback with the output if the
592
- # prompt has been found. Thus, call this method *only* if
593
- # <tt>@connection_state_callback</tt> is set!
605
+ # waiting for. Calls @fiber_resumer with the output if the
606
+ # prompt has been found.
594
607
  #
595
608
  # If <tt>@telnet_options[:wait_time]</tt> is set, it will wait this amount
596
609
  # of seconds after seeing what looks like the prompt before calling
597
- # #call_connection_state_callback. This way, more data can be received
610
+ # @fiber_resumer. This way, more data can be received
598
611
  # until the real prompt is received. This is useful for commands that send
599
612
  # multiple prompts.
600
613
  #
601
614
  def check_input_buffer
602
615
  return unless md = @input_buffer.match(@telnet_options[:prompt])
603
616
 
604
- blk = lambda do
605
- @last_prompt = md.to_s # remember last prompt
606
- output = md.pre_match + @last_prompt
607
- @input_buffer = md.post_match
608
- call_connection_state_callback(output)
609
- end
610
-
611
617
  if s = @telnet_options[:wait_time] and s > 0
612
- # fire @connection_state_callback after s seconds
613
- @wait_time_timer = EventMachine::Timer.new(s, &blk)
618
+ # resume Fiber after s seconds
619
+ @wait_time_timer = EventMachine::Timer.new(s) { process_match_data(md) }
614
620
  else
615
- # fire @connection_state_callback now
616
- blk.call
621
+ # resume Fiber now
622
+ process_match_data(md)
617
623
  end
618
624
  end
619
625
 
626
+ # Takes out the @last_prompt from _md_ (MatchData) and remembers it.
627
+ # Resumes the fiber (using @fiber_resumer) with the output (which
628
+ # includes the prompt and everything before).
629
+ def process_match_data(md)
630
+ @last_prompt = md.to_s # remember the prompt matched
631
+ output = md.pre_match + @last_prompt
632
+ @input_buffer = md.post_match
633
+ @fiber_resumer.(output)
634
+ end
635
+
636
+ @@_fiber_statuses = {}
637
+ def self._fiber_statuses
638
+ @@_fiber_statuses
639
+ end
640
+
620
641
  ##
621
642
  # Read data from the host until a certain sequence is matched.
622
643
  #
@@ -633,6 +654,9 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
633
654
  # * +:wait_time+ (actually used by #check_input_buffer)
634
655
  #
635
656
  def waitfor prompt=nil, opts={}
657
+ if closed?
658
+ abort "Can't wait for anything when connection is already closed!"
659
+ end
636
660
  options_were = @telnet_options
637
661
  timeout_was = self.timeout if opts.key?(:timeout)
638
662
  opts[:prompt] = prompt if prompt
@@ -649,25 +673,78 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
649
673
 
650
674
  # so #unbind knows we were waiting for a prompt (in case that inactivity
651
675
  # timeout fires)
652
- @connection_state = :waiting_for_prompt
676
+ self.connection_state = :waiting_for_prompt
653
677
 
654
- # for the block in @connection_state_callback
655
- f = Fiber.current
678
+ pause_and_wait_for_result
679
+ ensure
680
+ @telnet_options = options_were
681
+ self.timeout = timeout_was if opts.key?(:timeout)
682
+
683
+ # #unbind could have been called in the meantime
684
+ self.connection_state = :connected if !closed?
685
+ end
656
686
 
657
- # will be called by #receive_data to resume at "Fiber.yield" below
658
- @connection_state_callback = ->(output){ f.resume(output) }
687
+ # Pauses the current Fiber. When resumed, returns the value passed. If the
688
+ # value passed is an Exeption, it's raised.
689
+ def pause_and_wait_for_result
690
+ @@_fiber_statuses[node] = :paused
691
+ result = nil
692
+ while result == nil
693
+ # measure how long Fiber is paused
694
+ if $DEBUG
695
+ before_pause = Time.now
696
+ result = Fiber.yield
697
+ pause_duration = Time.now - before_pause
698
+
699
+ m = "#{node}: Fiber was paused for #{pause_duration * 1000}ms and " +
700
+ "is resumed with: #{result.inspect}"
701
+ result.nil? ? warn(m.red) : warn(m)
702
+ else
703
+ result = Fiber.yield
704
+ end
705
+ end
659
706
 
660
- result = Fiber.yield
707
+ @@_fiber_statuses[node] = :running
661
708
 
662
709
  raise result if result.is_a? Exception
663
710
  return result
711
+ end
712
+
713
+ # Identifier for this connection. Like an IP address or hostname. In this
714
+ # case, it's <tt>@telnet_options[:host]</tt>.
715
+ def node
716
+ @telnet_options[:host]
717
+ end
718
+
719
+ # Listen for anything that's received from the node. Each received chunk
720
+ # will be yielded to the block passed. To make it stop listening, the block
721
+ # should +return+ or +raise+ something.
722
+ #
723
+ # The default timeout during listening is 90 seconds. Use the option
724
+ # +:timeout+ to change this.
725
+ def listen(opts = {}, &blk)
726
+ self.connection_state = :listening
727
+ timeout(opts.fetch(:timeout, 90)) do
728
+ yield pause_and_wait_for_result while true
729
+ end
664
730
  ensure
665
- @telnet_options = options_were
666
- self.timeout = timeout_was if opts.key?(:timeout)
667
- @connection_state = :connected
731
+ self.connection_state = :connected
732
+ end
733
+
734
+ # Passes argument to #send_data.
735
+ def write(s)
736
+ send_data(s)
668
737
  end
669
738
 
670
- alias :write :send_data
739
+ # Raises Errno::ENOTCONN in case the connection is closed (#unbind has been
740
+ # called before). Also contains some debugging stuff depending on $DEBUG.
741
+ def send_data(s)
742
+ raise Errno::ENOTCONN, "Connection is already closed." if closed?
743
+ @last_sent_data = Time.now
744
+ print_recently_received_data if $DEBUG
745
+ warn "#{node}: Sending #{s.inspect}" if $DEBUG
746
+ super
747
+ end
671
748
 
672
749
  ##
673
750
  # Sends a string to the host.
@@ -830,56 +907,74 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
830
907
  #
831
908
  # Decreases <tt>@@_telnet_connection_count</tt> by one and calls #close_logs.
832
909
  #
833
- # After that and if <tt>@connection_state_callback</tt> is set, it takes a
910
+ # After that is set, it takes a
834
911
  # look at <tt>@connection_state</tt>. If it was <tt>:connecting</tt>, calls
835
- # #call_connection_state_callback with a new instance of ConnectionFailed.
912
+ # @fiber_resumer with a new instance of ConnectionFailed.
836
913
  # If it was <tt>:waiting_for_prompt</tt>, calls the same method with a new
837
914
  # instance of TimeoutError.
838
915
  #
839
916
  # Finally, the <tt>@connection_state</tt> is set to +closed+.
840
917
  #
841
- def unbind
918
+ def unbind(reason)
919
+ @unbound_at = Time.now
920
+ prev_conn_state = @connection_state
921
+ self.connection_state = :closed
922
+ if $DEBUG
923
+ warn "#{node}: unbinding because of: " + reason.inspect.red.bold
924
+ end
925
+ @@unbound_at[node] = [ Time.now, prev_conn_state ]
842
926
  @@_telnet_connection_count -= 1
843
927
  close_logs
844
928
 
845
- if @connection_state_callback
846
- # if we were connecting or waiting for a prompt, return an exception to
847
- # #waitfor
848
- case @connection_state
849
- when :connecting
850
- call_connection_state_callback(ConnectionFailed.new)
851
- when :waiting_for_prompt
852
- error = TimeoutError.new
853
-
854
- # set hostname and command
855
- if hostname = @telnet_options[:host]
856
- error.hostname = hostname
857
- end
858
- error.command = @last_command if @last_command
929
+ # if we were connecting or waiting for a prompt, return an exception to
930
+ # #waitfor
931
+ case prev_conn_state
932
+ when :waiting_for_prompt, :listening
933
+ # NOTE: reason should be Errno::ETIMEDOUT in these cases.
934
+ error = TimeoutError.new
859
935
 
860
- call_connection_state_callback(error)
936
+ # set hostname and command
937
+ if hostname = @telnet_options[:host]
938
+ error.hostname = hostname
861
939
  end
862
- end
940
+ error.command = @last_command if @last_command
863
941
 
864
- @connection_state = :closed
865
- end
942
+ @fiber_resumer.(error)
943
+ when :sleeping, :connected
866
944
 
867
- ##
868
- # Calls the @connection_state_callback with _obj_ and sets it to +nil+.
869
- #
870
- # Call this method *only* if <tt>@connection_state_callback</tt> is set!
871
- #
872
- def call_connection_state_callback(obj=nil)
873
- callback, @connection_state_callback = @connection_state_callback, nil
874
- callback.call(obj)
945
+ when :connecting
946
+ @fiber_resumer.(ConnectionFailed.new)
947
+ else
948
+ m = "#{node}: bad connection state #{prev_conn_state.inspect} " +
949
+ "while unbinding"
950
+ warn m.red
951
+ debugger
952
+ end
875
953
  end
876
954
 
955
+ @@unbound_at = {}
956
+
957
+ def self.unbound_at() @@unbound_at end
958
+
877
959
  ##
878
960
  # Called by EventMachine after the connection is successfully established.
879
961
  #
880
962
  def connection_completed
881
- @connection_state = :connected
882
- call_connection_state_callback if @connection_state_callback
963
+ self.connection_state = :connected
964
+ @fiber_resumer.(:connection_completed)
965
+
966
+ # print received data in a more readable way
967
+ if $DEBUG
968
+ EventMachine.add_periodic_timer(0.5) { print_recently_received_data }
969
+ end
970
+ end
971
+
972
+ # Prints recently received data (@recently_received), if there's any, and
973
+ # empties that buffer afterwards.
974
+ def print_recently_received_data
975
+ return if @recently_received_data.empty?
976
+ warn "#{node}: Received: #{@recently_received_data.inspect}".cyan
977
+ @recently_received_data = ""
883
978
  end
884
979
 
885
980
  ##
@@ -907,6 +1002,13 @@ class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
907
1002
 
908
1003
  private
909
1004
 
1005
+ # Sets the @connection_state to _new_state_. Raises if current (old) state is
1006
+ # :closed, because that can't be changed.
1007
+ def connection_state=(new_state)
1008
+ raise Errno::ENOTCONN, "Connection is already closed." if closed?
1009
+ @connection_state = new_state
1010
+ end
1011
+
910
1012
  ##
911
1013
  # Sets up output and command logging.
912
1014
  #
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: em-simple_telnet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-04-03 00:00:00.000000000 Z
11
+ date: 2013-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eventmachine
@@ -54,9 +54,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
54
54
  version: '0'
55
55
  requirements: []
56
56
  rubyforge_project:
57
- rubygems_version: 2.0.0
57
+ rubygems_version: 2.0.2
58
58
  signing_key:
59
59
  specification_version: 4
60
60
  summary: Simple telnet client on EventMachine
61
61
  test_files: []
62
- has_rdoc: true