Question

Here in the rails 3.x app,I am using net::ssh and running some commands to remote pc.I would like to display the live logs to the user's browser.like,If two commands are running in net::ssh to execute i.e echo "Hello",echo "Bye" is passed then "Hello" should be displayed in the browser immediately finishing after its execution.Here is the code I am using for ssh connection and running commands in ruby on rails application

Net::SSH.start( @servers['local'] , @machine_name, :password => @machine_pwd, :timeout => 30) do |ssh|      
  ssh.open_channel do |channel|
    channel.request_pty         
    channel.exec("echo 'ssh started'")                                   
    channel.exec("ruby -v") 
    channel.exec("rails -v") 
    channel.exec("echo 'ssh Finished'") 


    channel.on_close do |ch|
      puts "****shell terminated****"
    end

    channel.on_eof do |ch|
      puts "****remote end is done sending data****"            
    end
    channel.on_extended_data do |ch, type, data|
      puts "****got stderr****: #{data.inspect}"
    end
    channel.on_data do |channel, data|
      puts "****ondata****: #{data} \n"                    
      puts "****channel****: #{channel} \n"          
      $logs << data # this data to be sent immediately to browser in each go                                  
    end

  ssh.loop      
  end
end  

Here on_data is sending data in every command execution,I need this data to be sent to the browser immediately.Is there any way to do this.So that I can achieve live logs in front end browser.Thanks!

Was it helpful?

Solution

To achieve 'sending data to the browser immediately' you need to use one of three strategies:

  • Short Polling
  • Streaming
  • Long polling

(in the following examples I assume that $logs is of type Queue

Short Polling

Short polling is the classic way to get pending information from the server. It means that once every X seconds the client asks the server 'do you have new information?', the server answers either 'no', in which case the client goes back to sleep for another X seconds, or the server answers 'yes, here is some new information...', which the client can then render, before sleeping for another X seconds.

The major upside for this strategy is that it is supported by all browsers and all ruby versions (simple AJAX calls).

The downside of this strategy is that you are going to have a delay of up to X seconds before seeing new data. If X is too short - you are going to suffer from a lot of network overhead (all the requests with empty responses).

Example implementation:

def get_logs
  available_logs = []
  while(line = $logs.pop(true) rescue nil) available_logs << line

  body available_logs.join($/)
end

Streaming

Streaming strategy is when your client opens a request to the server, the server starts sending a response to the client, but when the available information ends, it doesn't close the connection, but leaves it open. Then it continues to stream information on the open socket as the information comes.

In Rails 4.0 there is an ActionController::Live which implements this strategy. For Rails 3.2 you can try looking at this answer: https://stackoverflow.com/a/4320399/1120015

Example implementation:

def get_logs
  class LogConsumer
    def each
      while $channel.active?
        yield $logs.pop + $/
      end
    end
  end

  self.response_body = LogConsumer.new
end

The main difference between this solution and the other two is that the client implementation is not as straight forward - the client can't just wait for the response to return and then render it (like the default jQuery usage). For sample implementation, a lot of places point to Ajax Patterns, which apparently is unavailable at the moment :-(.

Another option I've seen is using the portal plugin*:

portal.open("/get_logs", {
  inbound: function(data) {
    render_log_lines(data);
  }
});

* I have no experience using this plugin, so this should be taken as a direction to research, rather than as a working example...

Long Polling

Long polling means that your AJAX client request the server for the next log line. If the server has one (or more) to provide, it returns it to the client, the client renders the line(s), and requests again. If the server does not have a line to provide, it does not return an empty response, but rather hangs to the request, and waits until a line is available. As soon as a new line is available, it is returned to the client. The client renders the line, then immediately requests the server again.

When you choose this strategy, you must make sure that there is no timeout set in either the web-server or the client, nor in any other points in the middle (load balancers, etc.)

Example implementation:

def get_logs
  available_logs = []
  while(line = $logs.pop(true) rescue nil) available_logs << line

  if available_logs.empty?
    available_logs << $logs.pop
  end

  body available_logs.join($/)
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top