Marko
Marko

Reputation: 1627

Ruby, Telnet, read multiline response without timeout

I need some hints/help, how can I read multiline response into variable. My current command results me multiline response but after that I get timeout.

Here's how my connection is setup:

connection = Net::Telnet.new('Host' => host,'Port' => 4800, 'Telnetmode' => false, 'Timeout' => 1)

Here's my request and how I save it:

puts "Weather request\n"
connection.cmd("{weather}"){ |c| print c }
parsed = JSON.parse(str)
puts "#{parsed}\n\n"

And here's the error:

/usr/lib/ruby/1.9.1/net/telnet.rb:558:in `waitfor': timed out while waiting for more data (Timeout::Error)
    from /usr/lib/ruby/1.9.1/net/telnet.rb:695:in `cmd'
    from ruby_check.rb:37:in `<main>'

My response is multiple JSON lines, like this:

{"City":"Tallinn", "Degrees":"23"}
{"City":"Berlin", "Degrees":"23"}
{"City":"Helsinki", "Degrees":"23"}
{"City":"Stockholm", "Degrees":"23"}

Upvotes: 4

Views: 1048

Answers (2)

georgebrock
georgebrock

Reputation: 30143

Why the timeout?

The Net::Telnet documentation says:

For some protocols, it will be possible to specify the Prompt option once when you create the Telnet object and use cmd() calls; for others, you will have to specify the response sequence to look for as the Match option to every cmd() call, or call puts() and waitfor() directly; for yet others, you will have to use sysread() instead of waitfor() and parse server responses yourself.

This makes more sense when combined with the Net::Telnet#cmd method's documentation, which says that the method:

sends a string to the host, and reads in all received data until is sees the prompt or other matched sequence.

You're not specifying a custom Prompt or Match option, so #cmd is waiting for something from the server that matches the default Net::Telnet prompt (/[$%#>] \z/n) to indicate the end of the message. If the message doesn't end with that kind of prompt, then it'll be waiting forever.

Possible solutions

Match the server's prompt

If the server does send some kind of prompt to indicate it's finished sending data and you should type the next command, you can pass a regular expression that matches it to the Net::Telnet initialiser. For example, if the server prompted you with command:, you could use:

connection = Net::Telnet.new(
  "Prompt" => /command: \z/,
  # …
)

Match the end of the response

If there's no prompt, but the response you're waiting for ends with a specific character sequence, you could explicitly specify the Match option when you call #cmd. For example, if your response was a single JSON array it would end with ], so you might be able to use this:

connection.cmd("String" => "{weather}", "Match" => "]") { |c| print c }

Give up on Net::Telnet and use a TCPSocket

If there's no prompt and no known ending, you could try to use the Net::Telnet object's underlying TCPSocket to read the data without using #cmd:

connection.puts("{weather}")
connection.sock.readline

At this point, there might not be much benefit to using Net::Telnet over a plain TCPSocket.

Upvotes: 3

The F
The F

Reputation: 3724

You are setting the timeout to one second and do not specify what str is. You can try increasing the timeout value or even setting it to false. Believieng it is the result from .cmd, try this:

connection = Net::Telnet.new(
  "Host" => host, "Port" => 4800, 
  "Telnetmode" => false, "Timeout" => false)

puts "Weather request...\n"

str = connection.cmd("{weather}"){ |c| print c }
parsed = JSON.parse(str)

puts "#{parsed}\n\n"

Upvotes: 1

Related Questions