Как избежать ошибки времени ожидания слабой команды?
Я работаю с командой Slack (код Python работает за этим), она работает нормально, но это дает ошибку
This slash command experienced a problem: 'Timeout was reached' (error detail provided only to team owning command).
Как этого избежать?
4 ответа
Согласно документации команды Slack slash, вы должны ответить в течение 3000 мс (три секунды). Если ваша команда занимает больше времени, то вы получитеTimeout was reached
ошибка. Ваш код, очевидно, не перестанет работать, но пользователь не получит никакого ответа на свою команду.
Три секунды - это хорошо для быстрой вещи, когда ваша команда имеет мгновенный доступ к данным, но может оказаться недостаточно длинной, если вы вызываете внешние API или делаете что-то сложное. Если вам нужно больше времени, см. Раздел " Задержанные ответы и множественные ответы " в документации:
- Подтвердить запрос в порядке.
- Вернуть
200
ответ немедленно, может быть, что-то вроде{'text': 'ok, got that'}
- Иди и выполни фактическое действие, которое вы хотите сделать.
- В исходном запросе вы получите уникальный
response_url
параметр. ДелатьPOST
запросите этот URL с вашим последующим сообщением:Content-type
должно бытьapplication/json
- С телом в виде JSON-кодированного сообщения:
{'text': 'all done :)'}
- вы можете возвращать эфемерные или внутриканальные ответы, а также добавлять вложения так же, как и при непосредственном подходе
Согласно документам, "вы можете отвечать на пользовательские команды до 5 раз в течение 30 минут после его вызова".
После того, как я сам решил эту проблему и разместил свое приложение 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()
функция принимает response_url
полученного сообщения Slack и передает его вместе с любыми другими необязательными данными backgroundworker()
, Поскольку процесс теперь разделен, он просто возвращает "working on your request"
сообщение на ваш канал Slack практически сразу и по завершении backgroundworker()
отправляет второе сообщение "your task is complete"
,
Я тоже часто сталкивался с этой ошибкой:
"Черт, эта команда косой черты не сработала (сообщение об ошибке:
Timeout was reached
). Управлять командой в slash-command
Я писал Slack-команду "бот" на AWS Lambda, которая иногда требовалась для выполнения медленных операций (вызова других внешних API и т. Д.). Функция лямбда может занять более 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 для публикации сообщения, которое работник получает и обрабатывает.
Основываясь на этом ответе Stackru, это лучший подход, так как он неблокируемый (асинхронный) и масштабируемый. Также было проще использовать SNS для разъединения двух функций в контексте AWS Lambda, прямой вызов более сложен для этого варианта использования.
Наконец, вот как я использую событие 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`
Создайте поток, чтобы выполнить основную часть работы и вернуть ответ на слабину. Вы также можете вернуть ответ, когда поток завершится.