Я люблю Vue и Vite - какой замечательный опыт разработки. Но я люблю Python за его Tornado, SQLAlchemy и многое другое. Итак, вот верхушка айсберга - это демонстрирует взаимосвязь между вопиющим злоупотреблением инструментами и плохой инженерией - где линтинг, форматирование, тестирование, документация и все остальное? Если меня спросят, я могу набрать pylint, axblack, pytest, sphinx, но отдыхать не буду; рпкс для меня.

Для начала создадим Makefile - его шаги просты, если у вас нет Make:

  • создать виртуальную среду под названием venv
  • активировать и установить nodeenv и tornado
  • создать виртуальную среду узла под названием nenv

Makefile

setup:
 if which python3 && [ ! -d venv ] ; then python3 -m venv venv ; fi
   source venv/bin/activate \
   && python -m pip install -q -U pip \
   && pip install nodeenv tornado \
   && if [ ! -d nenv ] ; then nodeenv nenv; fi

Затем вызовите функцию настройки вашего Makefile:

make setup

Создайте пакет Python с именем chat и добавьте к нему модули с именами main.py и websocket.py. В пакет добавьте каталог с именем static, в котором будут размещаться наши статические веб-файлы.

В bash вы можете запустить:

mkdir chat
touch chat/__init__.py
touch chat/main.py
touch chat/websocket.py
mkdir chat/static

Main будет содержать нашу tornado конфигурацию и websocket определять наш обработчик веб-сокетов. Tornado создает экземпляры классов RequestHandler для обработки каждого запроса. Мы будем использовать два обработчика: Websocket и StaticFileHandler.

websocket.py

""" our websocket handler """
import logging
from tornado.websocket import WebSocketHandler
log = logging.getLogger(__name__)
class Websocket(WebSocketHandler):
    """ a websocket handler that broadcasts to all clients """
    clients = []

    def check_origin(self, origin):
        """ in development allow ws from anywhere """
        if self.settings.get('debug', False):
            return True
        return super().check_origin(origin)

    def open(self, *args, **kwargs):
        """ we connected """
        log.info('WebSocket opened')
        self.clients.append(self)

    def on_close(self):
        """ we're done """
        if self in self.clients:
            self.clients.remove(self)
        log.info('WebSocket closed')

    def on_message(self, message):
        """ we've said something, tell everyone """
        for client in self.clients:
            client.write_message(message)

Здесь вы найдете документацию по торнадо для веб-сокетов.

Наша специализация добавляет свойство класса clients, к которому каждый запрос добавляет и удаляет из себя. Это позволяет нам вести трансляцию при получении сообщения. Мы проверяем настройку отладки, чтобы узнать, разрешаем ли мы запросы из других источников. Мы собираемся получить доступ к этому веб-сокету с сервера Vite через порт 3000. Без метода check_origin торнадо вернет ошибку 403.

main.py

""" our entry point """
import logging
import tornado.ioloop
from tornado.web import Application
from tornado.options import define, options, parse_command_line
from .websocket import Websocket

log = logging.getLogger(__name__)

define('debug', type=bool, default=False, help='auto reload')
define('port', type=int, default=8080, help='port to listen on')


def make_app():
    """ make an application """
    return Application(
        [
            (r'/ws', Websocket),
            (
                r'/(.*)',
                tornado.web.StaticFileHandler,
                {'path': 'chat/static', 'default_filename': 'index.html'},
            ),
        ],
        debug=options.debug,
    )


def main():
    """ parse command line, make and start """
    tornado.options.parse_command_line()
    app = make_app()
    app.listen(options.port)
    log.info('listening on port: %s', options.port)
    if options.debug:
        log.warning('running in debug mode')
    tornado.ioloop.IOLoop.current().start()


if __name__ == '__main__':
    main()

Tornado предоставляет парсер аргументов, и с его помощью мы определяем две опции: порт и отладка. Затем мы создаем приложение, определяя маршруты и настройки. Маршруты определяют путь, параметры обработчика и инициализации. У нашего Websocket нет опций, но StaticFileHandler должен знать путь к файлам и имя файла по умолчанию, если имя файла не присутствует в запросе. Используя нашу опцию отладки, мы можем активировать горячую перезагрузку торнадо, и мы сделаем это в командной строке, указав --debug=true.

Tornado - это асинхронный фреймворк, поэтому мы запускаем его ioloop. Теперь он написан поверх пакета asyncio стандартной библиотеки с несколькими отличными дополнениями.

Итак, теперь у нас есть приложение торнадо, которое мы можем запустить из командной строки, активировав нашу виртуальную среду и вызвав наш основной модуль:

. venv/bin/activate
python -m chat.main --debug=true

Vite

Сначала нам нужно активировать наш nodeenv, а затем вызвать Vite для создания приложения.

. nenv/bin/activate
npm init @vitejs/app client

Вам будет предложено установить Vite и спросить, какое приложение вы хотите. Мы выберем обычный Vue. Затем он проинструктирует вас перейти в каталог клиента и запустить npm install и npm run dev. Сделай так. Теперь на порту 3000 вы должны найти свое приложение Vite Vue3. Добавим клиент Websocket:

websocket.js

import {
    reactive
} from 'vue'

export default {
    install: (app, options) => {
        const ws = new WebSocket("ws://localhost:8080/ws");
        ws.transcript = reactive([])
        ws.onmessage = function (evt) {
            ws.transcript.push(evt.data)
        };
        app.config.globalProperties.$ws = ws
    }
}

Мы создаем WebSocket обратно в наше приложение торнадо. У нас также есть атрибут стенограммы reactive. Мы устанавливаем себя как плагин, предоставляя глобальный доступ к $ ws в любом компоненте vue. Каждое сообщение websocket будет добавлено в наш атрибут transcript. Хотя здесь это не видно, у Websocket есть метод send, который принимает строковый параметр сообщения. Мы будем использовать это в методе HelloWorld.vue say.

Затем в _17 _...

main.js

import {
    createApp
} from 'vue'
import App from './App.vue'
import ws from './websocket.js'

const app = createApp(App)
app.use(ws)
app.mount('#app')

Мы добавляем наш плагин в приложение с app.use(ws). В остальном это так, как было в шаблоне проекта.

Затем мы меняем components/HelloWorld.vue, чтобы использовать наш плагин:

HelloWorld.vue

<template>
<div class="page">
    <div class="transcript">
        <div class="line" v-for="line in $ws.transcript">
            {{ line }}
        </div>
    </div>
    <form @submit.prevent="say">
        <input v-model="name" placeholder="your name" size="10">
        <input v-model="something" 
               placeholder="say something" size="40">
        <input type="submit" value="Say">
    </form>
</div>
</template>

Наш шаблон представляет собой простую колонку с расшифровкой и записью. Наша стенограмма - это итерация реактивного атрибута $ ws.transcript. Наша запись - это ряд полей ввода для имени, чего-то и кнопки отправки. В нашей форме есть обработчик событий отправки, который предотвращает поведение по умолчанию - например, он не делает запрос и не перезагружает страницу.

<script>
export default {
    data() {
        return {
            name: "anon",
            something: ""
        }
    },
    methods: {
        say() {
            this.$ws.send(`${this.name} said: ${this.something}`)
            this.something = ""
        }
    }
}
</script>

Да, я знаю, что не использовал композицию api. Это все еще читается, и по мере того, как я привыкаю к ​​api композиции, я перейду к ней. Это выходит из моих пальцев само по себе ... У нас есть два реактива и метод. Метод вызывается событием формы, мы форматируем строку и отправляем ее через веб-сокет. Затем мы сбрасываем ввод something.

<style scoped>
.page { display: flex; flex-direction: column; } 
.form { display: flex; flex-direction: row; } 
.transcript { display: flex; flex-direction: column; }
</style>

CSS предоставляет столбцы и строки с использованием flex.

Кроме того, я заменил логотип изображением, чтобы мои ошибки или небрежность не ассоциировались с Vite, Vue или Tornado.

И вот результат:

Вы должны увидеть это на:

http://localhost:3000

Затем мы создадим наше приложение и получим к нему доступ через порт 8080. Во-первых, нам нужно отредактировать client/package.json, чтобы сообщить Vite, куда поместить созданные файлы.

"build": "vite build --outDir=../chat/static/ --emptyOutDir",

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

. nenv/bin/activate
cd client
npm run build

Теперь вы должны указать в браузере:

http://localhost:8080

Торнадо обслуживает и клиента, и веб-узел. Уточняйте размер клиента! Для меня это 59к! (размер статического каталога).

Если вы все еще используете Vite на порту 3000, используя две страницы браузера, вы можете общаться между Vite и Tornado, разработчиком и создателем!

Помещение в контейнер докеров при многоступенчатой ​​сборке приведет к получению образа размером 48 МБ!

Это только начало. Отсюда мы находим контейнеры докеров, доступ к базе данных через SQLAlchemy, Redis для поддержки многопроцессного взаимодействия, удаленные вызовы процедур для предоставления представлений, кеширование для поиска и многое другое, что стало возможным благодаря простому запуску тестируемых функций ioloop.

Проект доступен по адресу: https://github.com/blueshed/chat

Следующая статья доступна по адресу: Python Realtime Chat Engineered.

Больше контента на plainenglish.io