perlslack-apimojoliciousmojolicious-lite

Delayed response to slash command with Mojolicious in Perl


I am trying to create a slack application in Perl with mojolicious and I am having the following use case:

Slack sends a request to my API from a slash command and needs a response in a 3 seconds timeframe. However, Slack also gives me the opportunity to send up to 5 more responses in a 30 minute timeframe but still needs an initial response in 3 seconds (it just sends a "late_response_url" in the initial call back so that I could POST something to that url later on). In my case I would like to send an initial response to slack to inform the user that the operation is "running" and after a while send the actual outcome of my slow function to Slack.

Currently, I can do this by spawning a second process using fork() and using one process to respond imidiately as Slack dictates and the second to do the rest of the work and respond later on.

I am trying to do this with Mojolicious' subprocesses to avoid using fork(). However I can't find a way to get this to work....

a sample code of what I am already doing with fork is like this:

sub withpath
{
     my $c = shift;

     my $user   = $c->param('user_name');

     my $response_body = {
                response_type => "ephemeral",
                text          => "Running for $user:",
                attachments   => [
                     { text => 'analyze' },
                 ],
             };
     my $pid = fork();
     if($pid != 0){
         $c->render( json => $response_body );
     }else{
         $output = do_time_consuming_things()
         $response_body = {
             response_type => "in-channel",
             text          => "Result for $user:",
             attachments   => [
                { text => $output },
             ],
         };

         my $ua = Mojo::UserAgent->new;
         my $tx = $ua->post(
               $response_url,
               { Accept => '*/*' },
               json => $response_body,
               );
        if( my $res = $tx->success )
        {
                print "\n success \n";
        }
        else
        {
                my $err = $tx->error;
                print "$err->{code} response: $err->{message}\n" if $err->{code};
                print "Connection error: $err->{message}\n";
        }
    }
}

So the problem is that no matter how I tried I couldn't replicate the exact same code with Mojolicious' subproccesses. Any ideas?

Thanks in advance!


Solution

  • Actually I just found a solution to my problem!

    So here is my solution:

    my $c = shift; #receive request
    
    my $user   = $c->param('user_name'); #get parameters
    my $response_url = $c->param('response_url');
    my $text = $c->param('text');
    
    my $response_body = { #create the imidiate response that Slack is waiting for
                response_type => "ephemeral",
                text          => "Running for $user:",
                attachments   => [
                    { text => 'analyze' },
                ],
            };
    
    my $subprocess = Mojo::IOLoop::Subprocess->new; #create the subprocesses
    $subprocess->run(
    sub {do_time_consuming_things($user,$response_url,$text)}, #this callback is the
    #actuall subprocess that will run in background and contains the POST request
    #from my "fork" code (with the output) that should send a late response to Slack
    sub {# this is a dummy subprocess doing nothing as this is needed by Mojo.
        my ($subprocess, $err, @results) = @_;
        say $err if $err;
        say "\n\nok\n\n";
    }
    );
    #and here is the actual imidiate response outside of the subprocesses in order
    #to avoid making the server wait for the subprocess to finish before responding!
    $c->render( json => $response_body ); 
    

    So I actually simply had to put my code of do_time_consuming_things in the first callback (in order for it to run as a subprocess) and use the second callback (that is actually linked to the parent process) as a dummy one and keep my "imidiate" response in the main body of the whole function instead of putting it inside one of the subprocesses. See code comments for more information!