Эта статья написана для ламеров

Я как обычно учился кодить, и вдруг заметил что телеграм выпустил веб апи и теперь там есть фронт...

На тот момент я уже владел React, Flask и Django, и решил повысить планку с помощью FastAPI.

Веря в свои силы, я установил FastAPI и aiogram. Очевидно, мне следовало бы использовать нативные методы Telegram API, но aiogram предоставляет множество удобных функций, которые ускоряют разработку.

Проблема

Django и Flask используют шаблонизаторы (я использовал Jinja2), что означает, что код, выполняемый в браузере (HTML, JS, CSS, TS и т. д.), отправляется непосредственно с сервера. Таким образом, у такого приложения нет отдельного сервера для фронтэнда.

однако Fast-api и React, нуждаются в отдельных серверах ...

Я понимаю, что найдутся люди, которые скажут: "Зачем использовать отдельный сервер для React, если можно добавить реактивность с помощью Knockout.js и создать веб-приложение на одном сервере?" - Да, это возможно, но мне кажется, что лучше использовать нормальные технологии, а не костыли на костылях, чтобы к моменту завершения проекта у вас был ценный опыт в резюме, а не повод для пенсии))

Я сталкивался с корпоративными проектами, написанными с использованием устаревшего Knockout,js и Python 2, все на одном монолите. Схема работает, к тому же можно нанимать джунов и платить им меньше, ведь всех обучают Django на курсах).

Решение

Вот у нас есть два сервера - FastAPI и React. Далее нам нужно их связать с Telegram API. Рассмотрим конкретный пример такой связи: разумеется, FastAPI и Telegram взаимодействуют через webhook. Чтобы их свести вместе, мы отправляем запрос Telegram на сервер, в котором указываем публичный адрес, полученный от ngrok, и токен бота из файла .env. В моем случае, я добавил адрес ngrok в .env, и FastAPI автоматически устанавливает связь с Telegram при запуске. Основная проблема заключается в том, что ngrok, даже в платной версии, поддерживает только один адрес, а у меня фронтэнд работает на отдельном порту. Однако здесь на помощь приходит localhost.run. Она имеет схожий функционал с ngrok, но настраивается чутка сложнее.

Вот план которого я бы придерживался

  1. Запускаем localhost.run на порту 3000 и добавляем адрес в окружение бэка

  2. запускаем React на порту 3000

  3. Запускаем ngrok на порту 8000 и добавляем адрес в окружение

  4. Запускаем Fast-api локально на порту 8000

План простой и надежный, как швейцарские часы.

1. localhost.run

Заходим на https://admin.localhost.run/ регаемся\логинимся.

Делаем shh ключ ориг. инструкция на английсском. Ключ состоит из 2х частей публичной и приватной. заходим в терминал и пишем

ssh-keygen -t rsa -b 2048 -C "<comment>"

*в комментарии рекомендуют указывать емейл

это получаем как ответ
это получаем как ответ

выбираем название ключа. и запоминаем путь. если не вводить имя то ключи будут сгенерированы с дефолтными именами id_rsa и id_rsa.pub

жмем ентер , получаем сообщение что ключ готов.
жмем ентер , получаем сообщение что ключ готов.

идем в директорию ключа, жмем ls -1 чтобы убедиться что пришли в нужное место и ключи готовы и ждут нас.

ls -1
как я понял  в папку .ssh можно только через консоль попасть
как я понял в папку .ssh можно только через консоль попасть

id_rsa и id_rsa.pub это приватный и публичный ключи соответсвенно. Открываем id_rsa.pub с помощью vim или nano. Если нет vim то стаvim vim))

vim id_rsa.pub

копируем все что внутри(начиная с shh и до конца текста)
дальше выходим из вима для этого жмем “:” и печатаем q! и жмем ентыр.

идем в браузере на https://admin.localhost.run/#/keys вводим там этот ключ

жмем сабмит
жмем сабмит

дальше идем в консоль

ssh -R 80:localhost:3000 localhost.run

Что означет эта команда можнопочтитать в докуменции сервиса

Получаем наш белый адрес на порту 3000

на скрине порт 3003 а не 3000
на скрине порт 3003 а не 3000

добавляем полученный адрес в .env бэка

теперь настало время REACT

2. REACT

Итак чтобы чтобы обходиться без танцев, я сделяль виззард скрипт. Отмечу что он дает выбрать js/jsx, yarn/npm, и создает докер и докер компоуз с прокинутыми портами, а весь код вынесен в Volume поэтому проект не требует постоянной пересборки.

Итак, создаем create_react_compose_app.sh или скачиваем с github.

Делаем его исполняемым , на мак это делается вот так:

chmod +x create_react_compose_app.sh

Запустим скрипт:

bash create_react_copmpose_app.sh
дальше выбираем основные параметры, все сделано так чтобы можно было прощелкать и использовать дефолтные параметры. имя проекта это pwd + web re , npm , js
дальше выбираем основные параметры, все сделано так чтобы можно было прощелкать и использовать дефолтные параметры. имя проекта это pwd + web re , npm , js
проверяем докерфаил
проверяем докерфаил

Запускаем докер, после вводим в консоль

docker-compose up

И вот приложение открыто на порту 3000, теперь браузере идем по адресу который получили в localhost.run

и вот мы онлайн
и вот мы онлайн

Идем в

идем в
идем в

Открываем index.html и добавляем туда библиотеку телеграм

<script src="https://telegram.org/js/telegram-web-app.js"></script>
прямо в head
прямо в head

И заменим

<p>  Edit <code>src/App.js</code> and save to reload.</p>
  <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>

На

<p> Hellow Habr. </p>

В src/App.js

проверяем по адресу который дал localhost.run

все работает, повезло :)
все работает, повезло :)

3. Ngrok

идем в браузере на https://ngrok.com/ Регаемся/логинимся

скачиваем версию под свою ос
скачиваем версию под свою ос

идем в https://dashboard.ngrok.com/get-started/setup

делам все по инструкции:

  • разархивировать zip

  • подставить ключ в конфиг, удобно что в документации сразу даются персональные команды с подставленным ключом

    не надо ничего подставлять это готовая команда. оставлю тут ссылку на документацию ngrok
    не надо ничего подставлять это готовая команда. оставлю тут ссылку на документацию ngrok
  • запускаем ngrok на порту 8000

    ngrok http 8000
и получаем второй белый апи адресс
и получаем второй белый апи адресс

4. Fast-API

заходим в исходную папку

заходим по стрелке
заходим по стрелке

создадим папку Fast-API используя команду

mkdir Fast-API

перейдём в нее

cd Fast-API

создадим main.py и README.md, .env c помощью уже известной команды touch. Легко запомнить про неё есть песня у Daft Punk - Touch

создаем фаил и кладем туда адрес из localhost.run , ngrok , токенбота ...

touch .env
вот пример синтаксиса для .env файла
вот пример синтаксиса для .env файла

Создаем venv

Да, нормальные пацаны юзают Poetry, но этот текст исключительно для ламеров, если готовы по уму делать сразу то вперед, тут будет через venv.

python3 -m venv venv 

активируем его

source venv/bin/activate

обновляем

pip install --upgrade pip

ставим uvicorn

pip install uvicorn

ставим Fast-api

pip install fastapi

ставим aiogram

pip install aiogram

ставим python-dotenv, это один из тех пакетов которые в pip называются не так как импортируются

pip install python-dotenv

при помощи уже известной команды touch создаем README.md

touch README.md

в README.md прописываем команду старта

uvicorn main:app --reload

открываем main.py

прописываем импорты и загружаем переменные из .env

from dotenv import load_dotenv
from fastapi import FastAPI

load_dotenv() # загружем .env в оперативку

TOKEN = os.environ["TELEGRAM_TOKEN"] # получаем занчения переменных
BACK_URL = os.environ["BACK_URL"]
REACT_URL = os.environ["REACT_URL"]

WEBHOOK_PATH = f"/bot/{TOKEN}" # формируем системные переменные из переменных окружения
WEBHOOK_URL = BACK_URL + WEBHOOK_PATH

создаем приложение и прописываем проверочную функцию

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello habr"}

запускаем сервер uvicorn с приложением app = FastAPI командой из README.md

 uvicorn main:app --reload

Переходим на BACK_URL в браузере и проверяем, что все работает. Убедимся, что мы правильно установили и импортировали файл .env, и также правильно запустили FastAPI.

у меня все работает, повезло ))
у меня все работает, повезло ))

импортируем aiogram и создаем функции обработчики сообщений И так далее

@app.on_event("startup") #обработчик запуска приложения, засылает вебхук в телеграм
async def on_startup():
    webhook_info = await bot.get_webhook_info()
    if webhook_info.url != WEBHOOK_URL:
        await bot.set_webhook(
            url=WEBHOOK_URL
        )


@app.post(WEBHOOK_PATH) #обработчик событий телгарма 
async def bot_webhook(update: dict):
    telegram_update = types.Update(**update)
    Dispatcher.set_current(dp)
    Bot.set_current(bot)
    await dp.process_update(telegram_update)

добавляем обработчик команды старт в телеграм

@dp.message_handler(commands="start")
async def new_message(message: types.Message):
    text = 'REACT'
    keyboard = types.InlineKeyboardMarkup()
    keyboard.add(types.InlineKeyboardButton('Launch react', web_app=WebAppInfo(url=REACT_URL)))
    await bot.send_message(message.chat.id, text, reply_markup=keyboard)


@app.on_event("shutdown") #и обработчик закрытия сесии 
async def on_shutdown():
    await bot.get_session()
    await bot.session.close()
    logging.info("Bot stopped")

добавлю favicon.iso и вот полный код

import logging
import os

from aiogram import types, Dispatcher, Bot
from aiogram.types import WebAppInfo
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.responses import FileResponse

load_dotenv()

TOKEN = os.environ["TELEGRAM_TOKEN"]
BACK_URL = os.environ["BACK_URL"]
REACT_URL = os.environ["REACT_URL"]

WEBHOOK_PATH = f"/bot/{TOKEN}"
WEBHOOK_URL = BACK_URL + WEBHOOK_PATH

bot = Bot(token=TOKEN)
dp = Dispatcher(bot)

app = FastAPI()

favicon_path = 'favicon.ico'


@app.get('/favicon.ico', include_in_schema=False)
async def favicon():
    return FileResponse(favicon_path)


@app.get("/")
async def root():
    return {"message": "Hello habr"}


@app.on_event("startup")
async def on_startup():
    webhook_info = await bot.get_webhook_info()
    if webhook_info.url != WEBHOOK_URL:
        await bot.set_webhook(
            url=WEBHOOK_URL
        )


@app.post(WEBHOOK_PATH)
async def bot_webhook(update: dict):
    telegram_update = types.Update(**update)
    Dispatcher.set_current(dp)
    Bot.set_current(bot)
    await dp.process_update(telegram_update)


@app.on_event("shutdown")
async def on_shutdown():
    await bot.get_session()
    await bot.session.close()
    logging.info("Bot stopped")


@dp.message_handler(commands="start")
async def new_message(message: types.Message):
    text = 'REACT'
    keyboard = types.InlineKeyboardMarkup()
    keyboard.add(types.InlineKeyboardButton('Launch react', web_app=WebAppInfo(url=REACT_URL)))
    await bot.send_message(message.chat.id, text, reply_markup=keyboard)


@app.post(WEBHOOK_PATH)
async def bot_webhook(update: dict):
    telegram_update = types.Update(**update)
    Dispatcher.set_current(dp)
    Bot.set_current(bot)
    await dp.process_update(telegram_update)

сохраняем, запускаем, проверяем

у меня воркает общем)
у меня воркает общем)

В данной статье я описал настройку веб-приложения в контексте #Telegram и #Web_Apps for Bots. При желании, можно также включить использование FAST-API в контейнере с помощью #poetry, но это уже не для новичков. Кроме того, мы также коснулись вопросов #ssh ключей, #react, #python, #fast-api, #docker, #docker-compose, #ngrok, #localhost.run, #venv, #uvicorn и использовали #shell-script.

Комментарии (5)


  1. Expany
    24.06.2023 20:42

    *англиЙской

    *ls -l

    Дальше читать не стал


    1. palyaros02
      24.06.2023 20:42

      ключь ещё.


      1. dopusteam
        24.06.2023 20:42

        • англисском

        • Hellow (Хотя может в англисском так и должно быть)

        • Передем

        • Легко запомнить про не есть песня у Daft Punk - Touch

        • также верное запустили FastAPI

        • добавляем обработчик команды старь

          А ещё практически полное отсутствие пунктуации


        1. dan_daniels Автор
          24.06.2023 20:42

          спасибо, исправил


    1. dan_daniels Автор
      24.06.2023 20:42

      ls -1 это верная команда