Message handlers aren't invoked in aiogram bot on webhooks

Question:

I’m launching an aiogram bot on webhooks locally using ngrok tunnel, but instead of proper responses, the bot returns 200 OK with almost nothing in response body, thus a user doesn’t see anything in a chat.

Bot’s main.py driver looks like this:

from aiogram import executor

from loader import dp, bot, cache
from handlers import register_start_handlers, register_main_handlers
from settings import Settings, get_settings

config: Settings = get_settings()


async def on_startup(dispatcher):
    await bot.set_webhook(config.webhook.address)

register_start_handlers(dp)
register_main_handlers(dp)

async def on_shutdown(dispatcher):
    await bot.delete_webhook()
    await dispatcher.storage.close()
    await dispatcher.storage.wait_closed()
    await cache.close()
    await cache.wait_closed()


if __name__ == "__main__":
    setup_logging()
    executor.start_webhook(
        dispatcher=dp,
        webhook_path=config.webhook.WEBHOOK_PATH,
        on_startup=on_startup,
        on_shutdown=on_shutdown,
        skip_updates=True,
        host=config.webhook.BOT_WEBAPP_HOST,
        port=config.webhook.BOT_WEBAPP_PORT,
    )

The message handlers in handlers module are registered through functions like so:

from aiogram import Dispatcher
from aiogram.types import Message
from aiogram.dispatcher.webhook import SendMessage

async def begin_interaction(message: Message):
    return SendMessage(message.from_user.id, "Message text")

def register_start_handlers(dp: Dispatcher):
    dp.register_message_handler(begin_interaction, commands=["start"])

And the settings come from the .env file in the project root, retrieved like this:

from pydantic import validator, BaseSettings

BASE_DIR = pathlib.Path(__file__).parent


class EnvSettings(BaseSettings):
    class Config(BaseSettings.Config):
        env_file = "../.env"

...

class WebhookSettings(EnvSettings):
    WEBHOOK_HOST: str
    WEBHOOK_PATH: str
    BOT_WEBAPP_HOST: str
    BOT_WEBAPP_PORT: int

    @property
    def address(self) -> str:
        return f"{self.WEBHOOK_HOST}{self.WEBHOOK_PATH}"


class Settings:
    ...
    webhook: WebhookSettings = WebhookSettings()

The bot connects to several microservices, all of which are launched using docker-compose, so the environment variables look something like this:

WEBHOOK_HOST='https://some.subdomain.ngrok.io'
WEBHOOK_PATH='/'

BOT_WEBAPP_HOST=0.0.0.0
BOT_WEBAPP_PORT=3001

I had to use 0.0.0.0 (or the docker network local IP which worked the same way) as HOST because localhost recommended by the official docs caused the app to fail with the following error:
OSError: [Errno 99] error while attempting to bind on address ('::1', 3001, 0, 0): cannot assign requested address

I assume that this doesn’t matter much, as the example apps (again, like the official one) work fine both with localhost and 0.0.0.0 (also using the same ngrok setup).

Now, to the problem. With the above setup, the application launches as normal, but does not return expected responses to the POST requests from Telegram (checked this with ngrok’s inspect page). Instead, it just returns 200 OK to any request with only ok in the body and no data that Telegram could turn into a message to respond with. Simple experiments showed that message handlers are registered, but not called, which is strange: the same bot structure works perfectly fine on simpler test bots. And this project itself works just fine when using long polling, so the overall structure seems to be more or less funtional.

I’ve tried to write a minimal reproducible example to replicate the error, but didn’t succeed (all simpler programs I came up with work fine with this setup), so I’m just hoping to get some speculations on what could be the problem here. I’ve been trying to change IP addresses and ports in .env, but that never worked. Launching in docker is not the problem either because when I try to launch outside of a container exposing neccessary ports in docker-compose file, everything goes pretty much the same.

Asked By: acalabash

||

Answers:

As it turns out, the problem was in the structure of the bot.
I used aiogram’s built-in FSM tooling which assigns a state value to a dialogue with a specific user and lets you filter messages based on that:

@message_handler(state=Manager.choose_action)
async def f(message: Message, state: FSMContext):
    ...

Seemingly, during testing I shut down the docker containers once, but they persisted the bot’s state data, and the next time I launched the bot service, the message handler associated with the state was catching all the messages and returning nothing because they did not have the data that the handler expected.

So, it was basically my fault, but this was a tough error to catch. If you get in a situation like mine, try to cancel your states (like with a /cancel command) or wipe out the persisted bot data, this might help

Answered By: acalabash
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.