web-dev-qa-db-ja.com

slackコマンドのタイムアウトエラーを回避する方法

私はスラックコマンドで作業しています(Pythonコードはこの背後で実行されています)、それはうまく動作しますが、これはエラーを与えます

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

これを避ける方法は?

22
Vikas Saini

Slack slash command documentation によると、3000ms(3秒)以内に応答する必要があります。コマンドに時間がかかると、Timeout was reachedエラーが発生します。コードの実行は明らかに停止しませんが、ユーザーはコマンドに対する応答を受け取りません。

コマンドがデータに瞬時にアクセスする場合は3秒で十分ですが、外部APIを呼び出す場合や複雑な操作を行う場合は長さが足りない場合があります。 do時間がかかる場合は、遅延応答と複数応答ドキュメントのセクション:

  1. 要求が正常であることを検証します。
  2. すぐに200応答を返します。おそらく{'text': 'ok, got that'}の行に沿って何か
  3. 行って、実行したい実際のアクションを実行します。
  4. 元のリクエストでは、一意のresponse_urlパラメーターが渡されます。フォローアップメッセージを使用して、そのURLにPOSTリクエストを行います。
    • Content-typeapplication/jsonである必要があります
    • JSONエンコードされたメッセージとしての本文:{'text': 'all done :)'}
    • 短命またはチャネル内の応答を返し、即時アプローチと同じように添付ファイルを追加できます

ドキュメントによると、「ユーザーの呼び出しから30分以内にユーザーコマンドに最大5回応答できます」。

36
rcoup

自分でこの問題に対処し、FlaskアプリをHerokuでホストした後、スレッドを使用するのが最も簡単なソリューションであることがわかりました。ここから例に従いました: 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")  

低速で重い作業はすべて、backgroundworker()関数によって実行されます。私のスラックコマンドはhttps://myappaddress.com/appmethodaddressを指し、receptionist()関数は受信したSlackメッセージのresponse_urlを受け取り、他のオプションデータと一緒にbackgroundworker()に渡します。プロセスが分割されると、すぐに"working on your request"メッセージがSlackチャネルに返され、完了時にbackgroundworker()が2番目のメッセージ"your task is complete"を送信します。

8
dom

私もこのエラーに頻繁に直面していました。

「くそ–そのスラッシュコマンドは機能しませんでした(エラーメッセージ:Timeout was reached)。スラッシュコマンドでコマンドを管理してください。」

私は AWS Lambdaのスラックスラッシュコマンド「ボット」 を書いていましたが、これは時々遅い操作(他の外部APIを呼び出すなど)を実行するために必要でした。 Lambda関数は、場合によってはSlackからTimeout was reachedエラーを引き起こす3秒以上かかります。

ここで@rcoupの優れた答えを見つけ、AWS Lambdaのコンテキストで適用しました。エラーは表示されなくなりました。

2つの別個のLambda関数を使用してこれを行いました。 1つは、「ディスパッチャ」または「受付係」で、着信Slackスラッシュコマンドに「200 OK」で挨拶し、単純な「Ok、got that」タイプのメッセージをユーザーに返します。もう1つは、ロングオペレーションを非同期的に開始し、そのオペレーションの結果を後でSlack response_urlにポストする実際の「ワーカー」Lambda関数です。

これは、ディスパッチャ/レセプショニストのLambda関数です。

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

上記からわかるように、ディスパッチャからワーカーLambda関数を直接呼び出しませんでした(これは可能ですが)。 AWS SNSを使用して、ワーカーが受信して処理するメッセージを発行する を選択しました。

このStackOverflowの答え に基づくと、これはノンブロッキング(非同期)でスケーラブルであるため、より良いアプローチです。また、AWS LambdaのコンテキストでSNSを使用して2つの機能を分離する方が簡単でした。このユースケースでは、直接呼び出しは難しいです。

最後に、ワーカーLambda FunctionでSNSイベントを使用する方法を示します。

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