em-simple_telnet 0.0.9 → 0.0.10
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.
- checksums.yaml +4 -4
- data/lib/em-simple_telnet.rb +204 -102
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78218823aba97e9250159f56bbb7c666a52178f8
|
4
|
+
data.tar.gz: 3035ce24632a5f23788253e8ff9c022175711965
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d7c0638b72d5c0b1fe0cee058b9391d965edf491af743a6ab19572656d6e1cb717aecf1f05304a4b5441e0c57875dc209b1e48cd6c875b6a532292559ebd937
|
7
|
+
data.tar.gz: 3f0bb2ee015a659e426afce49fd3148fde96bee0e501cce2e8e909763c47e89046a79513b769eceab498ffda37c10decab1ebe35095c0ab0bd13774653a8e21a
|
data/lib/em-simple_telnet.rb
CHANGED
@@ -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
|
-
#
|
233
|
-
|
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.
|
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
|
-
|
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
|
390
|
-
attr_accessor :
|
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
|
540
|
-
#
|
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
|
-
|
550
|
-
|
551
|
-
@wait_time_timer.cancel
|
552
|
-
@wait_time_timer = nil
|
553
|
-
end
|
552
|
+
case @connection_state
|
553
|
+
when :waiting_for_prompt
|
554
554
|
|
555
|
-
|
556
|
-
|
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
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
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
|
-
|
565
|
-
|
566
|
-
|
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
|
-
|
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
|
-
|
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
|
592
|
-
# prompt has been found.
|
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
|
-
#
|
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
|
-
#
|
613
|
-
@wait_time_timer = EventMachine::Timer.new(s
|
618
|
+
# resume Fiber after s seconds
|
619
|
+
@wait_time_timer = EventMachine::Timer.new(s) { process_match_data(md) }
|
614
620
|
else
|
615
|
-
#
|
616
|
-
|
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
|
-
|
676
|
+
self.connection_state = :waiting_for_prompt
|
653
677
|
|
654
|
-
|
655
|
-
|
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
|
-
|
658
|
-
|
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
|
-
|
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
|
-
|
666
|
-
|
667
|
-
|
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
|
-
|
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
|
910
|
+
# After that is set, it takes a
|
834
911
|
# look at <tt>@connection_state</tt>. If it was <tt>:connecting</tt>, calls
|
835
|
-
#
|
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
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
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
|
-
|
936
|
+
# set hostname and command
|
937
|
+
if hostname = @telnet_options[:host]
|
938
|
+
error.hostname = hostname
|
861
939
|
end
|
862
|
-
|
940
|
+
error.command = @last_command if @last_command
|
863
941
|
|
864
|
-
|
865
|
-
|
942
|
+
@fiber_resumer.(error)
|
943
|
+
when :sleeping, :connected
|
866
944
|
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
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
|
-
|
882
|
-
|
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.
|
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-
|
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.
|
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
|