Dancer2 app: streaming content blocking server and client. How to avoid blocking?

TL; DR

I translates the content using keywords Dancer2 delayed

, flush

, content

, done

and blocks the server during the streaming, until I call done

. How can I avoid this?

More details

I wrote a Dancer2 app that calls an external command (shell / Perl script) when the user clicks on a specific button. I want the output of this command to be displayed in the browser in a section <pre>

, and I want to update this section as new lines arrive. Similar tail -f

, but in a browser. My external command runs for a few seconds, maybe up to 10 minutes, so I run it asynchronously.

My first approach was to completely disconnect the program from the web server with double-fork, exec, ssid and close / reopen the STDIN / OUT / ERR program so that the command output goes to a temporary log file. Then I set up the AJAX calls (once per second) so that the Dancer2 application reads new lines from the log file and returns them to the client until the PID of the external command disappears. This worked like a charm until the moment my "outside team" issued commands ssh

to contact some other server and return this output. ssh

does not work as expected when run without a terminal, rather ssh

than outputting any output. Think about the ssh $host "ls -l"

one that hasn't given a way out.

So, I switched to the Dancer2 engine delayed

as shown in the code below. I took the CSV example from Dancer2 docs as a template. Again, it works like a charm , but as long as the command is run and new lines appear in the browser, the server is blocked. When I click any other link on the webpage, I only see the hour glass until the command ends. It looks like the server is uniprocessor and one-liner.

index.tt

<script>
    function start_command( event ) {

        $('#out_win').empty();
        var last_response_len = false;
        $.ajax({
            url: '/ajax/start',
            xhrFields:  {
                onprogress: function(evt){
                    /* make "this_response" only contain the new lines: */
                    var this_response;
                    var response = evt.currentTarget.response;
                    if ( last_response_len === false ) {
                        this_response = response;
                        last_response_len = response.length;
                    } else {
                        this_response = response.substring(last_response_len);
                        last_response_len = response.length;
                    }

                    /* add those new lines to <pre> and scroll down */
                    var pre = $('#out_win');
                    pre.append(this_response); 
                    pre.scrollTop(pre.prop('scrollHeight'));
                }
            },
            success: function(result, textStatus, jqXHR) {
                alert("Done streaming, result="+result);
            },
            error: function( jqXHR, textStatus, errorThrown ) {
                alert("error; status=" + textStatus);
            },
        });

        event.preventDefault();
    }
</script>

<div class="container">

    <div>   <%# Links %>
        <a href="javascript:start_command();">Start external command</a></br>
        <a href="/other">Show some other page</a>
    <div>

    <div>   <%# output window %>
        <pre class="pre-scrollable" id="out_win"></pre>
    </div>

</div>

      

Streaming.pm

package Streaming;
use Dancer2;

################################################################
#
################################################################
get '/' => sub {
    template 'index';
};

################################################################
#
################################################################
get '/ajax/start' => sub {
    delayed {
        flush;    # streaming content

        # "stream" something. Actually I start an external program here
        # with open(..., "$pgm |") and stream its output line my line.
        foreach my $line ( 1 .. 10 ) {
            content "This is line $line\n";
            sleep(1);
        }
        done;    # close user connection
    }
    on_error => sub {
        my ($error) = @_;
        warning 'Failed to stream to user: ' . request->remote_address;
    };
};

true;

      

I use

  • Dancer2 0.204002 set via apt install libdancer2-perl

    on
  • Ubuntu 17.04
  • there is no other web server, that is, I am using the server that comes with Dancer2 starting with plackup -a bin/app.psgi

  • jQuery 2.2.4
  • Bootstrap 3.3.7
  • Perl 5.18 +

My questions:

  • I am doing something completely wrong wrt. keyworddelayed

  • Or am I using the wrong web server for this task? I would like to stick with the web server that comes with Dancer2 because it is so easy to use and I don’t need any bandwidth like google does. We will have 1-3 users per day and in most cases not at the same time, and there will be no blocking web application.
+3


source to share





All Articles