tail on the browser

283 views
Skip to first unread message

marcos rebelo

unread,
Apr 20, 2012, 10:50:47 AM4/20/12
to mojol...@googlegroups.com
Hi all

I'm getting crazy with the next script. The script is a sort of tail for a file on the server, when I start the server it works ok but when I try to reload the page doesn't work any more.

What am I doing wrong?

#!/usr/bin/env perl
use Mojolicious::Lite;

get '/' => sub {
  my $self = shift;
  $self->render('index');
};

my $loop = Mojo::IOLoop->singleton;
my $FILE_PATH = __FILE__; # Just for example now

websocket '/tail' => sub {
    my $self = shift;
    
    open(my $fh, '-|', 'tail', '-f', $FILE_PATH) or die $!;
    
    $self->on(finish => sub { 
        close $fh; 
        $fh = undef;
    } );
        
    my $sub;
    $sub = sub {
        return if not defined $fh;
        my $line = readline($fh);
        $self->send_message( $line );
        print "SEND: $line";
        $loop->timer(0, $sub);
    };
    
    $sub->();
};

app->start;
__DATA__

@@ index.html.ep
<!DOCTYPE html>
<html>
    <head>
        <title>tail</title>
        <style>
            .class-test {
                width:10000px;
            }
        </style>
        <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
        <script>
            "use strict"
            
            var create_line_div = function( text ) {
                var div = $("<div></div>");
                div.text(text);
                return div;
            };
            
            var websocket;
            var createWebSocket = function () { 
                websocket = new WebSocket("<%= $self->req->url->to_abs->scheme( $self->req->is_secure ? 'wss' : 'ws' )->path( '/tail' ) %>"); 
                websocket.onmessage = function(evt) { $('.class-test').append( $( create_line_div( evt.data ) ) ) }; 
            };
            
            $( createWebSocket() );
        </script>
    </head>
    <body>
        Welcome to Tail!
        <div class='class-test'>
        </div>
    </body>
</html>


Best Regards
Marcos Rebelo

--
Marcos Rebelo
http://www.oleber.com/
Webmaster of http://perl5notebook.oleber.com

Gabriel Vieira

unread,
Apr 20, 2012, 11:12:15 AM4/20/12
to mojol...@googlegroups.com
CGI? 

--
You received this message because you are subscribed to the Google Groups "Mojolicious" group.
To post to this group, send email to mojol...@googlegroups.com.
To unsubscribe from this group, send email to mojolicious...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/mojolicious?hl=en.



--
Gabriel Vieira

marcos rebelo

unread,
Apr 20, 2012, 11:37:05 AM4/20/12
to mojol...@googlegroups.com
morbo script.pl

Tobias Oetiker

unread,
Apr 20, 2012, 11:38:56 AM4/20/12
to mojol...@googlegroups.com
Hi Marcos,

I have appended code I wrote for the a event-stream client which
might give you some inspiration ... note that the design is
different as the data source (tail -f) only runs once and not for
every client connecting ... in your design every connection gets
its own tail ....

package Signage2;
use Mojo::Base 'Mojolicious';

# This method will run once at server start
sub startup {
my $self = shift;
# Router
my $r = $self->routes;
my $log = $self->log;
$r->get('/' => 'index');
my $stream = $self->launch_arec;


my $loop = Mojo::IOLoop->singleton;

$r->get('/sound' => sub {
my $self = shift;
# Change content type
my $out_stream = $loop->stream($self->tx->connection);
$self->res->headers->content_type('text/event-stream');
# stop mojo from complaining about incomplete page
$self->render_later;
my $read_cb = $stream->on(read => sub {
my ($stream, $chunk) = @_;
while ($chunk =~ m{^(.+)[\r\n]+}g){
$self->write("event:log\ndata:".$1."\n\n");
}
});
my $close = sub {
my $stream = shift;
$self->write("event:log\ndata:Closed\n\n") if $self;
$log->info("closed client connection");
$self->finish;
};
$stream->once(close => $close);
$stream->once(error => $close);

my $unsubscribe = sub {
$stream->unsubscribe(read=>$read_cb);
$log->info("browser gone");
};

# when the connnection is dead, stop writing to it
$out_stream->once(timeout => $unsubscribe);
$out_stream->once(close => $unsubscribe);

});
}

sub launch_tail {
my $self = shift;
# Increase inactivity timeout for connection a bit
my $log = $self->log;
my $pid = open(my $fh,"-|", "tail","-f","$FILE_PATH") ||
die "can't fork: $!";
# Create stream


my $loop = Mojo::IOLoop->singleton;

my $stream = Mojo::IOLoop::Stream->new($fh);
# register with the io loop
my $id = $loop->stream($stream);
$stream->on(error => sub {
my ($stream, $err) = @_;
$loop->remove($id);
$log->error("Stream Error: $err");
});

$stream->on(close => sub {
my $stream = shift;
$log->info("closing connection to tail");
waitpid($pid,0);
$loop->remove($id);
});
$stream->start;
return $stream;
}

1;


Today marcos rebelo wrote:

--
Tobi Oetiker, OETIKER+PARTNER AG, Aarweg 15 CH-4600 Olten, Switzerland
http://it.oetiker.ch to...@oetiker.ch ++41 62 775 9902 / sb: -9900

Ed W

unread,
Apr 20, 2012, 1:13:18 PM4/20/12
to mojol...@googlegroups.com
Hi


What am I doing wrong?

I think if you add some print statements you will find that you block on the close $fh ?



    open(my $fh, '-|', 'tail', '-f', $FILE_PATH) or die $!;
    
    $self->on(finish => sub { 
        close $fh; 
        $fh = undef;
    } );

I haven't tested, but I think the close will wait for tail to finish and block?


Here is my attempt at a loop - note there are some extreme subtleties here and I would love for Sebastian to comment on the "correct" way to handle callbacks.  What you should watch for is that some of the handles are weakened, and also the various shutdown functions throw ugly warning if you re-enter them.  So you need to design your closedown so that you can kill either the tail which kills the web connection, or kill the web connection which kills the tail, all without creating a loop:

---

get '/system/logs' => 'system/logs';

# EventSource for log messages
get '/system/logs-es' => sub {
  my $self = shift;
  $self->render_later;


  # Increase inactivity timeout for connection a bit
  Mojo::IOLoop->stream( $self->tx->connection )->timeout(300);

  # Change content type

  $self->res->headers->content_type('text/event-stream');
  $self->res->headers->header( 'X-Accel-Buffering' => 'no' );

  my $pid = open( my $fh, "-|", "tail -F -n 0 /var/log/messages" );
  defined($pid) || die "can't fork: $!";

  # Create stream

  my $stream = Mojo::IOLoop::Stream->new($fh);
  $stream->timeout(0);
  $stream->on(
    read => sub {
      my ( $stream, $chunk ) = @_;
      $chunk = $self->txt_to_html($chunk);
      $self->write("event:log\ndata:$chunk\n\n");
    }
  );
  $stream->on(

    close => sub {
      my $stream = shift;
      say "Close";    # DEBUG
      $self->finish("event:log\ndata:Log Closed<br><br>\n\n") if ( $self && $self->tx && !$self->tx->is_finished );
    }
  );
  $stream->on(
    error => sub {
      my ( $stream, $err ) = @_;
      say "MY Error: $err";    # DEBUG
      $self->finish("event:log\ndata:Error...Closing<br><br>\n\n") if ( $self && $self->tx && !$self->tx->is_finished );
    }
  );

  # Start listening to $fh
  Mojo::IOLoop->singleton->stream($stream);

  # Unsubscribe from "message" event again once we are done
  $self->on(
    finish => sub {
      my $self = shift;
      say "Finishing";         # DEBUG
#    Mojo::IOLoop->singleton->drop($fh);
#    $stream->close;
      # Kill child
      kill( "TERM", $pid ) if $pid;
      undef $pid;
    }
  );
};



@@ system/logs.html.ep
<div class="page-header">
  <h1>System Logs</h1>
</div>
<p>Times in GMT/UTC</p>
<div class="row-fluid">
  <div class="span12">
    <pre id="syslog">
    %== $self->txt_to_html( Mojo::Asset::File->new( path => '/var/log/messages' )->slurp );
    </pre>


    % content_for javascript_footer => begin
    <script>
      var events = new EventSource('<%= url_for '/system/log-es' %>');

      // Subscribe to "log" event
      events.addEventListener('log', function(event) {
        $("#syslog").append(event.data);
      }, false);
    </script>
    %end
  </div>
</div>

Reply all
Reply to author
Forward
0 new messages