web-dev-qa-db-fra.com

Comment éviter l'erreur de délai d'attente de commande lâche?

Je travaille avec la commande slack (le code python fonctionne derrière cela), cela fonctionne bien, mais cela donne une erreur

This slash command experienced a problem: 'Timeout was reached' (error detail provided only to team owning command).

Comment éviter cela?

22
Vikas Saini

Selon le Slack documentation de la commande slash , vous devez répondre dans les 3000 ms (trois secondes). Si votre commande prend plus de temps, vous obtenez l'erreur Timeout was reached. Votre code ne s'arrêtera évidemment pas, mais l'utilisateur n'obtiendra aucune réponse à sa commande.

Trois secondes, c'est bien pour une chose rapide où votre commande a un accès instantané aux données, mais peut ne pas être assez longue si vous appelez des API externes ou faites quelque chose de compliqué. Si vous avez besoin de prendre plus de temps, alors voyez Réponses différées et réponses multiples section de la documentation:

  1. Valider la demande est correct.
  2. Renvoyez immédiatement une réponse 200, Peut-être quelque chose comme {'text': 'ok, got that'}
  3. Allez et effectuez l'action que vous voulez faire.
  4. Dans la demande d'origine, vous obtenez un paramètre response_url Unique. Faites une demande de POST à cette URL avec votre message de suivi:
    • Content-type Doit être application/json
    • Avec le corps en tant que message codé JSON: {'text': 'all done :)'}
    • vous pouvez renvoyer des réponses éphémères ou dans le canal et ajouter des pièces jointes de la même manière que l'approche immédiate

Selon les documents, "vous pouvez répondre aux commandes d'un utilisateur jusqu'à 5 fois dans les 30 minutes suivant l'appel de l'utilisateur".

36
rcoup

Après avoir résolu ce problème moi-même et avoir hébergé mon application Flask sur Heroku, j'ai trouvé que la solution la plus simple était d'utiliser le filetage. J'ai suivi l'exemple d'ici: https: // blog .miguelgrinberg.com/post/the-flask-mega-tutorial-part-xi-email-support

from threading import Thread

def backgroundworker(somedata,response_url):

    # your task

    payload = {"text":"your task is complete",
                "username": "bot"}

    requests.post(response_url,data=json.dumps(payload))    

@app.route('/appmethodaddress',methods=['POST','GET'])
def receptionist():

    response_url = request.form.get("response_url")

    somedata = {}

    thr = Thread(target=backgroundworker, args=[somedata,response_url])
    thr.start()

    return jsonify(message= "working on your request")  

Tout le travail lent et lourd est effectué par la fonction backgroundworker(). Ma commande slack pointe vers https://myappaddress.com/appmethodaddress Où la fonction receptionist() prend le response_url Du message Slack reçu et le transmet avec toutes les autres données facultatives à la backgroundworker(). Comme le processus est maintenant divisé, il renvoie simplement le message "working on your request" À votre canal Slack presque instantanément et une fois terminé, backgroundworker() envoie le deuxième message "your task is complete".

8
dom

Moi aussi, je faisais souvent face à cette erreur:

"Darn - cette commande slash n'a pas fonctionné (message d'erreur: Timeout was reached). Gérez la commande à slash-command "

J'écrivais un Slack slash-command "bot" sur AWS Lambda qui devait parfois effectuer des opérations lentes (invoquer d'autres API externes, etc.). La fonction Lambda prendrait plus de 3 secondes dans certains cas, provoquant le Timeout was reached erreur de Slack.

J'ai trouvé ici l'excellente réponse de @ rcoup et je l'ai appliquée dans le contexte d'AWS Lambda. L'erreur n'apparaît plus.

Je l'ai fait avec deux fonctions Lambda distinctes. L'un est un "répartiteur" ou "réceptionniste" qui salue la commande Slash entrante avec un "200 OK" et renvoie le simple type de message "Ok, got that" à l'utilisateur. L'autre est la fonction Lambda "de travail" réelle qui démarre l'opération de longue durée de manière asynchrone et publie le résultat de cette opération sur le Slack response_url plus tard.

Voici la fonction Lambda du répartiteur/réceptionniste:

def lambda_handler(event, context):
    req_body = event['body']

    try:
        retval = {}

        # the param_map contains the 'response_url' that the worker will need to post back to later
        param_map = _formparams_to_dict(req_body)
        # command_list is a sequence of strings in the slash command such as "slashcommand weather pune"
        command_list = param_map['text'].split('+')

        # publish SNS message to delegate the actual work to worker lambda function
        message = {
            "param_map": param_map,
            "command_list": command_list
        }

        sns_response = sns_client.publish(
            TopicArn=MY_SNS_TOPIC_ARN,
            Message=json.dumps({'default': json.dumps(message)}),
            MessageStructure='json'
        )

        retval['text'] = "Ok, working on your slash command ..."
    except Exception as e:
        retval['text'] = '[ERROR] {}'.format(str(e))

    return retval


def _formparams_to_dict(req_body):
    """ Converts the incoming form_params from Slack into a dictionary. """
    retval = {}
    for val in req_body.split('&'):
        k, v = val.split('=')
        retval[k] = v
    return retval

Comme vous pouvez le voir ci-dessus, je n'ai pas appelé la fonction Lambda de travail directement depuis le répartiteur (bien que cela soit possible). J'ai choisi de tiliser AWS SNS pour publier un message que le travailleur reçoit et traite .

Basé sur cette réponse StackOverflow , c'est la meilleure approche car elle est non bloquante (asynchrone) et évolutive. De plus, il était plus facile d'utiliser SNS pour découpler les deux fonctions dans le contexte d'AWS Lambda, l'invocation directe est plus délicate pour ce cas d'utilisation.

Enfin, voici comment je consomme l'événement SNS dans ma fonction Lambda de travail:

def lambda_handler(event, context):
    message = json.loads(event['Records'][0]['Sns']['Message'])
    param_map = message['param_map']
    response_url = param_map['response_url']

    command_list = message['command_list']
    main_command = command_list[0].lower()

    # process the command as you need to and finally post results to `response_url`
8
nonbeing