diff --git a/aiogram/dispatcher/__init__.py b/aiogram/dispatcher/__init__.py index 1053dda1..0230484d 100644 --- a/aiogram/dispatcher/__init__.py +++ b/aiogram/dispatcher/__init__.py @@ -715,6 +715,21 @@ class Dispatcher: return FSMContext(storage=self.storage, chat=chat, user=user) def async_task(self, func): + """ + Execute handler as task and return None. + Use that decorator for slow handlers (with timeouts) + + .. code-block:: python3 + + @dp.message_handler(commands=['command']) + @dp.async_task + async def cmd_with_timeout(message: types.Message): + await asyncio.sleep(120) + return SendMessage(message.chat.id, 'KABOOM').reply(message) + + :param func: + :return: + """ def process_response(task): response = task.result() self.loop.create_task(response.execute_response(self.bot)) diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index 860b0cc9..64f4b566 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -2,7 +2,6 @@ import asyncio import asyncio.tasks import datetime import functools -import time import typing from typing import Union, Dict, Optional @@ -12,6 +11,8 @@ from .. import types from ..bot import api from ..bot.base import Integer, String, Boolean, Float from ..utils import json +from ..utils.deprecated import warn_deprecated as warn +from ..utils.exceptions import TimeoutWarning from ..utils.payload import prepare_arg DEFAULT_WEB_PATH = '/webhook' @@ -41,10 +42,6 @@ class WebhookRequestHandler(web.View): """ - def __init__(self, request): - self._start_time = time.time() - super(WebhookRequestHandler, self).__init__(request) - def get_dispatcher(self): """ Get Dispatcher instance from environment @@ -85,9 +82,19 @@ class WebhookRequestHandler(web.View): return web.Response(text='ok') async def process_update(self, update): + """ + Need respond in less than 60 seconds in to webhook. + + So... If you respond greater than 55 seconds webhook automatically respond 'ok' + and execute callback response via simple HTTP request. + + :param update: + :return: + """ dispatcher = self.get_dispatcher() loop = dispatcher.loop + # Analog of `asyncio.wait_for` but without cancelling task waiter = loop.create_future() timeout_handle = loop.call_later(RESPONSE_TIMEOUT, asyncio.tasks._release_waiter, waiter) cb = functools.partial(asyncio.tasks._release_waiter, waiter) @@ -107,11 +114,22 @@ class WebhookRequestHandler(web.View): return fut.result() else: fut.remove_done_callback(cb) - fut.add_done_callback(self.response_task) + fut.add_done_callback(self.respond_via_request) finally: timeout_handle.cancel() - def response_task(self, task): + def respond_via_request(self, task): + """ + Handle response after 55 second. + + :param task: + :return: + """ + warn(f"Detected slow response into webhook. " + f"(Greater than {RESPONSE_TIMEOUT} seconds)\n" + f"Recommended to use 'async_task' decorator from Dispatcher for handler with long timeouts.", + TimeoutWarning) + dispatcher = self.get_dispatcher() loop = dispatcher.loop @@ -121,6 +139,12 @@ class WebhookRequestHandler(web.View): asyncio.ensure_future(response.execute_response(self.get_dispatcher().bot), loop=loop) def get_response(self, results): + """ + Get response object from results. + + :param results: list + :return: + """ if results is None: return None for result in results: diff --git a/aiogram/utils/exceptions.py b/aiogram/utils/exceptions.py index 0bf3a030..edbf5bcd 100644 --- a/aiogram/utils/exceptions.py +++ b/aiogram/utils/exceptions.py @@ -13,6 +13,14 @@ class TelegramAPIError(Exception): super(TelegramAPIError, self).__init__(_clean_message(message)) +class AIOGramWarning(Warning): + pass + + +class TimeoutWarning(AIOGramWarning): + pass + + class ValidationError(TelegramAPIError): pass