Я работаю с командой slack (за этим работает код python), он работает нормально, но это дает ошибку
This slash command experienced a problem: 'Timeout was reached' (error detail provided only to team owning command).
Как этого избежать?
Я работаю с командой slack (за этим работает код python), он работает нормально, но это дает ошибку
This slash command experienced a problem: 'Timeout was reached' (error detail provided only to team owning command).
Как этого избежать?
Создайте поток, чтобы выполнить большую часть работы и вернуть ответ на слабину. Вы также можете вернуть ответ, когда поток завершится.
В соответствии с документацией Slack косой черты вам нужно ответить в течение 3000 мс (три секунды). Если ваша команда занимает больше времени, вы получаете ошибку Timeout was reached
. Очевидно, что ваш код не остановится, но пользователь не получит ответа на свою команду.
Три секунды отлично подходят для быстрой работы, когда ваша команда имеет мгновенный доступ к данным, но может быть недостаточно длинной, если вы выходите на внешние API или делаете что-то сложное. Если вам потребуется больше времени, см. Раздел "Отложенные ответы и несколько ответов" в документации:
200
, возможно, что-то вроде строк {'text': 'ok, got that'}
response_url
. Сделайте запрос POST
на этот URL с последующим сообщением:
Content-type
должен быть application/json
{'text': 'all done :)'}
В соответствии с документами "вы можете отвечать на команды пользователя до 5 раз в течение 30 минут после вызова пользователя".
Я тоже часто сталкивался с этой ошибкой:
"Darn - эта команда косой черты не работает (сообщение об ошибке:
Timeout was reached
). Управляйте командой с помощью косой черты"
Я писал Slack slash-command "bot" на AWS Lambda, который иногда требовал выполнения медленных операций (вызывая другие внешние API и т.д.). Функция Lambda в некоторых случаях занимает более 3 секунд, вызывая ошибку Timeout was reached
от Slack.
Я нашел @rcoup отличный ответ здесь и применил его в контексте AWS Lambda. Ошибка больше не появляется.
Я сделал это с двумя отдельными лямбда-функциями. Один из них - "диспетчер" или "регистратор", который приветствует входящую команду Slack slash с "200 OK" и возвращает пользователю сообщение "Ok, got that". Другая - действительная "рабочая" функция лямбда, которая запускает длинную операцию асинхронно и позже отправляет результат этой операции в Slack response_url
.
Это функция 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
Как видно из вышеизложенного, я не вызывал рабочую Лямбду-функцию непосредственно из диспетчера (хотя это возможно). Я выбрал использовать AWS SNS для публикации сообщения, которое получает и обрабатывает работник.
Основываясь на fooobar.com/questions/48851/..., это лучший подход, поскольку он не блокирует (асинхронно) и масштабируется. Также было проще использовать SNS для развязки двух функций в контексте AWS Lambda, прямой вызов для этого прецедента более сложный.
Наконец, вот как я использую событие SNS в моей рабочей Lambda Function:
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`
После решения этой проблемы сам и имея мое приложение 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()
. Моя команда slack указывает на https://myappaddress.com/appmethodaddress
, где функция receptionist()
принимает response_url
полученного сообщения Slack и передает его вместе с любыми другими необязательными данными в backgroundworker()
. По мере разделения процесса он просто возвращает сообщение "working on your request"
на ваш канал Slack практически сразу, а после завершения backgroundworker()
отправляет второе сообщение "your task is complete"
.