11

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"

9
  • 1
    Haven't tried delayed responses yet, but the documentation states you should reply with a HTTP 200 (within the max of 3000ms), perhaps you can then reply to the same response url later. Commented Mar 24, 2016 at 8:39
  • @Ciccio that's the part I am not understanding I already sent "checking, please wait" which is a equivalent 200 ok response. I don't understand what you mean by "you can then reply to the same response url later", should I use the same response_url again? Commented Mar 24, 2016 at 8:42
  • 2
    @Vimalnath You sending "checking, please wait" is not equivalent to a 200 OK response to slack. You need to respond to the request that Slack is sending to your server with 200 OK Commented Mar 25, 2016 at 9:21
  • 1
    My understanding is that you need to complete and close the initial http request from slack to your bot with HTTP code 200. Then you bot can continue to run and process the data and later send his result back to the response_url. Just sending back a message while still keeping the initial http request from Slack open will not work. Commented Mar 25, 2016 at 14:36
  • @WilhelmKlopp so just header("HTTP/1.1 200 OK"); code will do? Commented Mar 26, 2016 at 11:52

5 Answers 5

8

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.
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for your answer, can you check my updated code in the question? I still get same error , not sure if I missed anything!
I'd suggest using the command-line curl to post to your slash command URL (eg. curl -H "Content-Type: application/json" --data @body.json http://localhost:8000/slash, maybe use requestb.in for the $response_url?), you should be able to see whether the 200 comes back to curl immediately.
Looks like a sounds approach, but unfortunately does not seam to work. I tested it and it still runs into a timeout. Seams like Slack keeps the request open despite the flush(). @rcoup Did you test your approach successfully?
@ErikKalkoken does this one work as an alternate? I did test it, but it's on my home computer so I can't double-check until tomorrow. What PHP/Server are you running?
@croup unfortunately I get the same result. My scripts run on altervista.org with PHP 5.4.40 and apache.
|
4

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.

3 Comments

How this will work with POST request? Assume I want to send post request and don't want to wait for response then?
@Hassaan this works with both GET and POST requests. The principle is the same. you set a very small timeout and make sure the script you called is not aborted.
This is the only solution that worked for me without spawning another process.
2

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(); ... 

1 Comment

Worked for me as well (fixed an out-of-order delivery issue I was seeing with the top ranked answer.
1

From what I can see in the documentation, you're doing things mostly correctly. Just by echoing anything out, you're already passing a 200 OK message, so no need to do it explicitly. You should check to make sure this isn't a server problem though; is the URL being posted to valid? Not getting mangled by a rewrite rule along the way?

I've made some changes to your code below, including some debugging that will go to your error log (i.e. Apache's error log, by default.) Give it a try, and at the very least you'll have some more debugging details.

<?php $response_url = $_POST["response_url"]; $term = rawurlencode($_POST["text"]); error_log("POST: " . print_r($_POST, 1)); ob_end_clean(); ob_start(); $response = ["response_type"=>"in_channel", "text"=>"Checking, please wait..."]; echo json_encode($response); header("Content-Type: application/json"); header("Content-Length: " . ob_get_size()); ob_end_flush(); flush(); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => "https://yoda.p.mashape.com/yoda?sentence=$term", CURLOPT_HTTPHEADER => ["X-Mashape-Key: deMeGoBfMvmshQSemozTqJEY9z0jp1eIhuAjsnx9cQAQsHUifD"], CURLOPT_RETURNTRANSFER => true ]); $yodaresponse = curl_exec($ch); curl_close($ch); error_log("Yoda response: $yodaresponse"); $yodajson = json_encode([ "response_type"=>"in_channel", "text"=>$yodaresponse ]); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $response_url, CURLOPT_POST => 1, CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => $yodajson ]); $resp = curl_exec($ch); curl_close($ch); error_log("API response: $resp"); 

Comments

-1

Posting an answer as I don't have enough reputation to post comments...

I've had the same problem, and then I realized that Slack treats requests and responses differently. Specifically, HTTP request and response differ in their first line.

HTTP request example:

GET /hello.htm HTTP/1.1 User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT) Host: www.tutorialspoint.com Accept-Language: en-us Accept-Encoding: gzip, deflate Connection: Keep-Alive 

HTTP response example:

HTTP/1.1 200 OK Date: Mon, 27 Jul 2009 12:28:53 GMT Server: Apache/2.2.14 (Win32) Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT Content-Length: 11 Content-Type: text/xml Connection: Closed hello there 

If you can access raw bytes to be sent in PHP (never used PHP, so not familiar), just make it look like a response, rather than a request. Otherwise, send a response immediately, then do the work that you need, and send a request with the new message. This can be done in number of ways, one of which was outlined by @miken32, I opted out for invoking a background process in python.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.