Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slack PHP API - Avoid Timeout error

I am trying to use Slack Custom command and not pretty sure how to use delayed messages since the Yoda Speak External API takes more than 3 seconds to respond.

I have done the following:

  • Sent the slack command /Yoda in my case and received the reponse_url.
  • Used the following to post the following to the response URL.
$data_string = '{"response_type": "in_channel", "text":"Checking,please wait..."}' ;
$chs = curl_init();
curl_setopt($chs, CURLOPT_URL, $response_url);
curl_setopt($chs, CURLOPT_POST, true);
curl_setopt($chs, CURLOPT_POSTFIELDS, $data_string); 
curl_setopt($chs, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($chs, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($chs, CURLOPT_RETURNTRANSFER, true);
curl_setopt($chs, CURLOPT_POST, 1);
curl_setopt($chs, CURLOPT_HTTPHEADER, array('Content-Type:application/json'));
$results = curl_exec($chs);

enter image description here

  • Now, when I call the Yoda API, it gives the following error "Timeout was reached". I read about delayed responses but not sure how should I proceed from here.
$chsres = curl_init();
curl_setopt($chsres, CURLOPT_URL, "https://yoda.p.mashape.com/yoda?sentence=welcome+to+stack");
curl_setopt($chsres, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($chsres, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($chsres, CURLOPT_VERBOSE, true);
curl_setopt($chsres, CURLOPT_TIMEOUT, 45);
curl_setopt($chsres, CURLOPT_RETURNTRANSFER, true);
curl_setopt($chsres, CURLOPT_HTTPHEADER, array('Content-Type:application/json', "X-Mashape-Key:> deMeGoBfMvmshQSemozTqJEY9z0jp1eIhuAjsnx9cQAQsHUifD"));
$resultchsres = curl_exec($chsres);
echo $resultchsres;

Can someone please let me know how to get rid of the timeout error using delayed responses?

UPDATED CODE:

$response_url = $_POST['response_url'];
$text = $_POST['text'];

$term = str_replace(' ', '+', $text);

//https://paypal.slack.com/services/B0VQMHX8W#service_setup
//initial respond with 200OK for timeout
ignore_user_abort(true);
set_time_limit(0);
ob_start();
echo('{"response_type": "in_channel", "text": "Checking, please wait..."}');
header($_SERVER["SERVER_PROTOCOL"] . " 200 OK");
header("Content-Type: application/json");
header('Content-Length: '.ob_get_length());
ob_end_flush();
ob_flush();
flush();


    $chsres = curl_init();
    curl_setopt_array($chsres, array(
        CURLOPT_URL => "https://yoda.p.mashape.com/yoda?sentence=$term",
        CURLOPT_SSL_VERIFYPEER => FALSE,
        CURLOPT_SSL_VERIFYHOST => FALSE,
        CURLOPT_VERBOSE => true,
        CURLOPT_RETURNTRANSFER => FALSE,
        CURLOPT_HTTPHEADER => array('Content-Type:application/json', "X-Mashape-Key: deMeGoBfMvmshQSemozTqJEY9z0jp1eIhuAjsnx9cQAQsHUifD"),
        CURLOPT_RETURNTRANSFER => true
    ));
    $yodaresponse = curl_exec($chsres);

    $curl = curl_init();
    curl_setopt_array($curl, array(
        CURLOPT_URL => $response_url,
        CURLOPT_POST => 1,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POSTFIELDS => $yodaresponse
    ));

    $resp = curl_exec($curl);
    var_dump($resp);
    curl_close($curl);

I still get the same error "Darn – that slash command didn't work (error message: Timeout was reached). Manage the command at slash-command"

like image 473
Vimalnath Avatar asked Mar 24 '16 08:03

Vimalnath


3 Answers

You're doing all the right things, just need to change the order.

  1. Respond to the original request with a 200 OK response immediately. See this answer for details, but essentially:

    ignore_user_abort(true);
    ob_start();
    echo('{"response_type": "in_channel", "text": "Checking, please wait..."}');
    header($_SERVER["SERVER_PROTOCOL"] . " 200 OK");
    header("Content-Type: application/json");
    header('Content-Length: '.ob_get_length());
    ob_end_flush();
    ob_flush();
    flush();
    
  2. Then make the Yoda API request using curl, as you're doing

  3. Once you have the Yoda results, send them to Slack at $response_url using curl, as you're doing.
like image 160
rcoup Avatar answered Oct 18 '22 10:10

rcoup


If you're using FPM then this is what you want - http://php.net/manual/en/function.fastcgi-finish-request.php

Your code would then look like this...

<?php
$response_url = $_POST["response_url"];
$term = rawurlencode($_POST["text"]);
error_log("POST: " . print_r($_POST, 1));

$response = ["response_type"=>"in_channel", "text"=>"Checking, please wait..."];
echo json_encode($response);
header("Content-Type: application/json");
fastcgi_finish_request();

$ch = curl_init();
...
like image 2
Dan Avatar answered Oct 18 '22 10:10

Dan


Another approach that will work is to use a curl request with a short timeout to spawn a second PHP script. Since my provider has put some restrictions on my PHP environment (e.g. no process spawning) this has been the only approach that has worked for me.

The first script will terminate shortly after and send an HTTP OK back to Slack. The second script will continue running, handle the time consuming processing (e.g. calling external APIs) and finally send the result as delayed response to the response_url.

1st script

This is the curl request in your first script:

<?php>
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "second.php?redirect_url=$redirect_url");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT_MS, 400);     //just some very short timeout        
    curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
    curl_exec($ch);
    curl_close($ch);

    /* send short response back to user, e.g. "Processing your request..." */
?>

The length of the timeouts is arbitrary, however in my tests a very short timeout (e.g. 10ms) did not work.

You will also need to implement a way to transfer input data between the two scripts as illustrated with passing the request_url as URL parameter.

Finally for slash commands Slack requires you to send a short response back to the user.

2nd script

This is how your 2nd script looks like:

<?php
    ignore_user_abort(true);          //very important!
    usleep (500000);                  //to ensure 2nd script responds after 1st
    /* call external API */
    /* send response back to Slack using response_url */
?>

The statement ignore_user_abort(true); is mandatory to ensure your 2nd script keeps running after the curl timeout.

The usleep with 0.5 secs is to ensure that the 2nd script responds after the first, but not mandatory for this solution to work.

The example is based on one answer of the "Continue PHP execution after sending HTTP response" question.

like image 2
Erik Kalkoken Avatar answered Oct 18 '22 09:10

Erik Kalkoken