Шина между Веб-воркерами и основным потоком. Ускоряем работу JavaScript

Photo by Indira Tjokorda on Unsplash

Введение

В современном мире веб-разработки часто возникают ситуации, когда необходимо эффективно управлять большим объемом данных и производить сложные вычисления. Однако выполнение таких задач в основном потоке UI может привести к замедлению работы приложения и ухудшению пользовательского опыта. Вот здесь на помощь приходят веб-воркеры.

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

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

Для прочтения этой статьи предполагается, что вы уже имеете опыт использования typescript и postMessage, т.к. я не буду вдаваться в подробности этих технологий.

Определение терминов и концепций

Основной поток в контексте веб-разработки — это поток, в котором выполняется большая часть JavaScript-кода, включая обработку событий, обновление пользовательского интерфейса и так далее. Он отвечает за выполнение всего кода, который влияет на то, что пользователь видит в браузере.

Веб-воркер — это скрипт, который браузер может выполнить в фоновом потоке, отдельно от скрипта, который выполняется на веб-странице, в фоновом потоке. Это позволяет выполнение сложных вычислений без блокировки основного потока.

Шина (bus) в данном контексте представляет собой механизм, который обеспечивает обмен данными и командами между различными частями системы, такими как веб-воркеры и основной поток.

Сервис — это модуль или компонент, который предоставляет определенную функциональность в системе. В контексте шины, сервис может быть представлен в виде веб-воркера, который выполняет определенные вычисления или обрабатывает данные.

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

Сервис пустышка (Mock Service) — это прокси-класс, который используется в основном потоке для вызова методов веб-воркера. Он облегчает взаимодействие с веб-воркером, делая его прозрачным для основного потока.

ReturnType — это перечисление, определяющее режим работы сервиса (работает ли он с Promise или с Observable объектами).

Архитектура и работа шины

Шина в данном контексте представляет собой систему, которая обеспечивает взаимодействие между основным потоком и веб-воркерами. Вот как это работает:

1. Регистрация и инициализация веб-воркеров:

Веб-воркеры регистрируются в шине с помощью специальных методов. Эти воркеры могут быть инициализированы в момент первого вызова любого метода из любого сервиса этого воркера.

2. Создание фабрики сервисов:

Фабрика сервисов создается на основе зарегистрированного веб-воркера. Она позволяет создавать экземпляры сервисов с нужными параметрами и настройками.

3. Использование сервисов пустышек:

С помощью фабрики сервисов создаются сервисы пустышки, которые используются в основном потоке. Они делают взаимодействие с веб-воркером прозрачным, поскольку предоставляют тот же API, что и реальный сервис в веб-воркере.

4. Отправка сообщений в веб-воркер:

Когда метод на сервисе пустышки вызван, создается команда на отправку сообщения в веб-воркер. Эта команда содержит информацию о методе, аргументах и других параметрах.

5. Получение результатов из веб-воркера:

Веб-воркер обрабатывает полученное сообщение и отправляет результат обратно. В зависимости от режима работы сервиса (ReturnType), результат может быть представлен как Promise или Observable.

6. Управление ресурсами:

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

Схема работы шины:

На схеме слева изображен основной поток, справа — веб-воркеры. Реальный экземпляр сервиса расположен в веб-воркере, а в основном потоке находится его мок. Когда мы вызываем метод, мок сервис перенаправляет этот вызов в часть шины в основном потоке, которая, в свою очередь, перенаправляет его в часть шины в веб-воркере согласно настройкам регистрации воркера в шине. Затем вызов передается реальному экземпляру сервиса. Ответ от сервиса направляется в основной поток по той же логике. На уровне библиотеки используется технология postMessage для передачи сообщений, но вы можете реализовать свою логику и передать ее в шину.

Примеры и практическое применение

Давайте рассмотрим некоторые примеры, чтобы лучше понять, как работает данная система в реальных условиях. Это позволит нам увидеть, как можно использовать архитектуру и инструменты в разных сценариях.

Классы библиотеки:

// Класс для регистрации веб-воркеров и создания фабрики сервисов пустышек class MainThreadBus {   // Регистрация веб-воркеров   registerBusWorkers(transports: ITransport[]) { /* ... */ }      // Создание фабрики сервисов пустышек   createFactoryService(transport: ITransport) { /* ... */ } }  // Класс для регистрации реального сервиса в шине на стороне веб-воркера class BusWorker {   getService!: ServiceGetter;   transport!: ITransport;   // Подключение к шине   static connectToBus(transport: ITransport, getService: ServiceGetter, initHandler?: InitEventHandler) { /* ... */ } }  // Транспортный уровень, использующий postMessage export class ObjectCopyTransport implements ITransport {   constructor(private readonly ctx: Worker) { /* ... */ }      // Обработчик сообщений   protected messageHandler(event: MessageEvent<SendCommand>): void { /* ... */ }      // Отправка сообщения   sendMsg(msg: unknown): void { this.ctx.postMessage(msg); } }

Предположим, у нас есть UserService, который долго возвращает комментарии пользователя. Вынесем их получение в веб-воркер, используя эту шину. Код на стороне основного потока:

import { MainThreadBus, ObjectCopyTransport } from 'web-worker-bus'; // Создание веб-воркера const worker = new Worker(new URL('./Services/UserWorker', import.meta.url)); const userTransport = new ObjectCopyTransport(worker); // Регистрация воркера MainThreadBus.instance.registerBusWorkers([userTransport]); // Создание фабрики, привязанной к воркеру export const userWorkerFactory = MainThreadBus.instance.createFactoryService(userTransport);

Создание фабрики, привязанной к воркеру UserWorker.

Код в веб-воркере:

import { BusWorker, ObjectCopyTransport, ServiceGetter } from 'web-worker-bus'; import { container } from './UserWorkerContainer';  /* eslint-disable-next-line no-restricted-globals */ const worker = self as unknown as Worker;  const serviceGetter: ServiceGetter = (serviceName) => {   // Возвращаем экземпляр сервиса UserService, используя любой контейнер или создавая экземпляр класса напрямую   return container[serviceName as keyof typeof container]; }; // Подключение веб-воркера к шине BusWorker.connectToBus(new ObjectCopyTransport(worker), serviceGetter);

Реализация для сервиса, который использует Observable:

// Сервис для получения комментариев пользователя export class UserServiceWithObservable {   public getUserComments(): Observable<UserComments[]> { /* ... */ } }

Создание сервиса пустышки в основном потоке, который связан с сервисом в веб-воркере:

const userService = userWorkerFactory<UserService>("UserService", ReturnType.rxjsObservable);

Теперь мы можем использовать сервис, как будто бы его экземпляр находится в основном потоке.

Готовые примеры вместе с популярными фреймворками можете увидеть, перейдя по ссылкам ниже:

Обратите внимание, для Angular пришлось создать NgObjectCopyTransport — транспортный уровень, который оборачивает обработку полученных сообщений от веб-воркеров в NgZone для корректной работы рендера Angular.

Основные преимущества

  1. Инкапсуляция логики: Все взаимодействие с веб-воркерами скрыто за абстракцией, делая код более чистым и удобным для чтения и поддержки.

  2. Переносимость: Ваша система позволяет легко переносить тяжелые вычислительные задачи в веб-воркеры, не меняя остальной части кода. Это улучшает производительность и делает приложение более отзывчивым.

  3. Гибкость: Благодаря фабрике сервисов пустышек и возможности создавать различные транспортные уровни, данная библиотека может быть использована в различных сценариях и с разными фреймворками.

  4. Поддержка Observable и Promise: Встроенная поддержка наблюдаемых и обещаний облегчает интеграцию с современными приложениями и практиками программирования.

Применение

  1. Улучшение производительности: В случае, если ваше веб-приложение включает в себя сложные вычисления или обработку больших объемов данных, перенос этих задач в веб-воркеры может существенно улучшить отклик и общую производительность.

  2. Работа с большими данными: При работе с большими объемами данных, такими как анализ или визуализация, использование веб-воркеров с вашей библиотекой поможет обрабатывать данные эффективнее и быстрее.

  3. Реальное время и потоковая обработка: Ваша библиотека позволяет легко интегрироваться с потоковой обработкой и реальным временем, такими как наблюдаемые веб-сокеты или другие потоковые источники данных.

  4. Масштабируемость: Система удобна для масштабирования и может быть легко расширена для поддержки дополнительных сервисов и воркеров.

  5. Платформенная независимость: Поддержка различных транспортных уровней и интеграция с популярными фреймворками делает библиотеку применимой в различных платформах и средах.

Ограничения первой версии шины

Хотя библиотека предлагает множество преимуществ, есть и некоторые ограничения, которые следует учитывать:

  1. Не поддерживаются методы, созданные через symbol: Это может влиять на некоторые особенности и паттерны проектирования, которые вы можете использовать в своем коде.

  2. Транспортный уровень использует копирование сообщения при передаче данных между потоками: Это может увеличить время выполнения кода, в зависимости от объема передаваемых данных. Необходимо учитывать это при работе с большими объемами данных.

  3. Работает исключительно с асинхронным API: Все взаимодействия с библиотекой должны быть асинхронными. Это не должно быть большой проблемой для современных веб-приложений, но все же стоит иметь в виду.

  4. Можно использовать в рамках одного сервиса только Promise или только RxJs Observable объектами: Это ограничивает гибкость в выборе подходов в разных частях одного и того же сервиса, но обеспечивает последовательность и согласованность в использовании.

Эти ограничения не являются критическими, но они могут влиять на то, как вы будете использовать библиотеку в своем проекте. Они также могут быть адресованы в будущих версиях библиотеки.

Заключение

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

Главные преимущества этого подхода включают в себя:

  • Улучшение производительности путем распределения нагрузки на разные потоки.

  • Возможность создания более чистого и модульного кода.

  • Повышение масштабируемости и гибкости в разработке.

Однако также существуют и определенные ограничения, которые следует учитывать при работе с библиотекой.

Этот подход может быть особенно полезен для разработки сложных и высоконагруженных веб-приложений, где необходимо эффективно распределять нагрузку и управлять асинхронными операциями.

Если вы заинтересованы в дальнейшем изучении этого подхода или хотите попробовать его в своем проекте, вы можете обратиться к следующим ресурсам:

Этот подход представляет собой интересное и перспективное направление в разработке веб-приложений и может стать важным инструментом в арсенале современного веб-разработчика.

Если вы заинтересованы в этом проекте, ваш вклад будет очень ценен. Вы можете помочь улучшить библиотеку, исправить ошибки, добавить новые функции или даже написать документацию. Любые предложения и пул-реквесты приветствуются! 🙂

А если у вас возникнут вопросы или что-то окажется непонятным, не стесняйтесь обращаться. Может быть, есть какой-то пункт, который хотели бы разобрать подробнее? Пишите в комментариях. Ваше мнение и интерес к деталям очень важны для меня, и я постараюсь предоставить всю необходимую информацию.

Спасибо за внимание к статье!


ссылка на оригинал статьи https://habr.com/ru/articles/752526/

Как в OWASP ZAP создать авторизационный контекст и задействовать его в автоматизации

Привет, Хабр!

С вами инженерный отдел по динамическому анализу Swordfish Security. Продолжаем разбирать полезные кейсы, и сегодня мы рассмотрим, как в DAST-сканере OWASP ZAP настроить автоматическую авторизацию в приложении и переиспользовать ее в дальнейших сканированиях.

Содержание:

  1. Введение

  2. Создание контекста

  3. Автоматизация с помощью API

  4. Автоматизация с помощью Automation Framework

  5. Вместо вывода

Введение

OWASP ZAP (Zed Attack Proxy) — один из самых популярных инструментов для проверки безопасности веб-приложений. У него широкий диапазон возможностей для обнаружения уязвимостей. Однако, прежде чем начать сканирование, необходимо правильно настроить инструмент, в том числе и авторизацию, чтобы тестировать приложение с одной или нескольких учетных записей.

В DAST-инструментах при настройке сканирований можно встретить различные термины. Авторизация обычно выделяется отдельно и может называться, например, «Профиль авторизации». В ZAP’е есть более широкое понятие для настройки — «Контекст». Это набор информации о приложении, который может быть использован для проведения тестирования. Контекст включает в себя URL-адреса, используемые в продукте, технологиях и параметрах, учетные данные и настройки по поддержанию сессии и прочую информацию, которая может пригодиться при анализе безопасности.

В OWASP ZAP можно создать несколько контекстов, все они будут иметь уникальный набор настроек. Для каждого приложения генерируется отдельный контекст. Также можно создать один набор для основного сайта и другой, например, для административной панели. Контексты позволяют фокусироваться на определенных частях приложения и проводить тестирование только для выбранного набора данных.

Контекст сохраняется в формате XML-файла. Его можно импортировать в GUI-версию инструмента или же использовать для старта автоматизированного сканирования, например в Docker-контейнере. Рассмотрим оба варианта.

Создание контекста

ZAP поддерживает несколько режимов аутентификации: ручной, с помощью формы, HTTP, отправки JSON-данных и с помощью настраиваемого скрипта. Недавно у нас выходила статья про поддержку сессии в OWASP ZAP на основе заголовков. А сейчас давайте разберемся, как настроить контекст с аутентификацией с помощью формы.

Вот вам пошаговый пример на базе формы из GUI ZAP’а:

0. Подключить ZAP как веб-прокси в браузере и открыть сканируемый сайт.
1.1. Поймать в ZAP запрос к первой странице сайта.
1.2. Найти в ZAP аутентификационный запрос (обычно это POST с параметрами username=&password=).
2.1. Добавить сайт из дерева (оно создается ZAP’ом автоматически) в контекст (правый клик на сайт → Include in Context).
2.2. Найти в дереве аутентификационный запрос и сделать правый клик → flag as context → form based authentication.

 Рис. 1. Помечаем запрос как аутентификационный

Рис. 1. Помечаем запрос как аутентификационный

2.3. Найти ответ сервера, а в нем — строку, которая бы говорила о состоянии логина/логаута. Выделить ее и сделать правый клик → Flag as context → login/logout condition. Для заведомо уязвимого приложения WebGoat была выбрана строка «Location: …./login», так как при попытке открыть страницу в неаутентифицированном состоянии происходит перенаправление на страницу логина.

 Рис. 2. Добавляем индикатор логаута

Рис. 2. Добавляем индикатор логаута

Также можно настроить, чтобы ZAP периодически делал запрос на какую-либо аутентифицированную страницу и проверял ответ — сохраняется сессия или нет.

 Рис. 3. Пример из другого контекста

Рис. 3. Пример из другого контекста

3.1. В настройках контекста (раздел Users) нужно добавить нового пользователя и его пароль. Впоследствии эти данные будут применяться для аутентификации и проверки авторизации.

 Рис. 4. Добавление учетной записи

Рис. 4. Добавление учетной записи

3.2. Чтобы проверить настройки аутентификации, нужно дважды кликнуть на контексте и открыть раздел Authentication. Здесь можно заметить, что пользователь и пароль заданы шаблонами, в которые будут подставляться данные из раздела Users:

 Рис. 5. Автоматическая подстановка имени пользователя и пароля

Рис. 5. Автоматическая подстановка имени пользователя и пароля

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

 Рис. 6. Управление сессией

Рис. 6. Управление сессией

Рекомендуется начать с автоматического управления сессией (auto-detect). Если этот вариант не сработает, можно настроить поддержку с помощью cookie, заголовка, HTTP-аутентификации или указать скрипт для управления сессией.

Таким образом мы добавили приложение в контекст и настроили аутентификацию и поддержку сессии. Можно заполнить и другие параметры, важные для сканирования, например, указать используемые технологии в соответствующей вкладке.

После настройки контекста можно запускать перебор сайта пауком и активное сканирование прямо в интерфейсе. Но сейчас нас больше всего интересуют возможности автоматизации сканирований с применением подготовленного контекста. И для начала мы экспортируем его:

 Рис. 7. Экспорт контекста

Рис. 7. Экспорт контекста

При экспорте создается файл с расширением context, но, если открыть документ после экспорта, станет понятно, что он составлен в XML-формате. В дальнейшем этот файл может быть импортирован обратно в ZAP, использован для запуска сканирования через API либо передан в качестве параметра в скрипт автоматизации ZAP.

Автоматизация с помощью API

Для работы с API ZAP’а необходимо запустить его в режиме сервиса следующим образом:

$ ./zap.sh -daemon -host 0.0.0.0 -port 8080 -config api.disablekey=true -config api.addrs.addr.name=.* -config api.addrs.addr.regex=true -newsession 20_07-api_session

Данные, собранные в рамках работы с API, будут сохранены в файл сессии, который потом можно переиспользовать.

ВАЖНО: команда, приведенная выше, содержит небезопасные параметры api.disablekey=true и api.addrs.addr.name=.*, которые открывают доступ к API любых хостов без ключа. В неизолированном окружении так делать не стоит!

В дальнейшем можно посылать команды, например, при помощи CURL следующим образом:

Импортируем нужный нам контекст:

curl "http://address:8080/JSON/context/action/importContext/?contextFile=webgoat.context"

Запускаем обход сайта:

scanId=$(curl "http://address:8080/JSON/spider/action/scanAsUser/?contextId=1&userId=643&url=http%3A%2F%2Fapp_address%3A8090%2FWebGoat%2Fwelcome.mvc&maxChildren=&recurse=&subtreeOnly=" | jq '.["scanAsUser"]')

Запускаем активное сканирование:

ascanId=$(curl "http://address:8080/JSON/ascan/action/scanAsUser/?url=http%3A%2F%2Fapp_address%3A8090%2FWebGoat%2Fwelcome.mvc&contextId=1&userId=643&recurse=&scanPolicyName=&method=&postData=" | jq '.["scanAsUser"]')

После завершения тестирования можно создать отдельный файл с уязвимостями конкретного уровня критичности, в данном случае среднего:

curl "http://address/HTML/alert/view/alerts/?baseurl=&start=&count=&riskId=2" > medium.html

А также посмотреть общие результаты анализа:

curl "http://address:8080/JSON/alert/view/alertsSummary/?baseurl="

И, конечно, сгенерировать отчет:

curl "http://address:8080/JSON/reports/action/generate/?title=report&template=traditional-json&theme=&description=&contexts=http%3A%2F%2Fapp_address%3A8090&sites=&sections=&includedConfidences=&includedRisks=&reportFileName=&reportFileNamePattern=&reportDir=&display="

Подобные команды можно собрать в шаг сканирования приложения в том же Gitlab CI/CD, чтобы автоматически получать готовый отчет.

OWASP ZAP предоставляет обширный перечень API ручек, в том числе для детальной настройки сессии. Контекст приложения также может быть сгенерирован через API. Но первичное создание удобнее проводить через интерфейс, к тому же там всё экспортируется файлом, который легко переиспользовать.

Автоматизация с помощью Automation Framework

Второй вариант — применение аддона Automation Framework. Он присутствует в ZAP по умолчанию и позволяет автоматизировать основные задачи, выполняемые инструментом. Для этого в качестве входного параметра для скрипта запуска ZAP zap.sh подключается YAML-файл с конфигурацией нужных задач. Сейчас в документации ZAP’а этот вариант автоматизации указан как предпочтительный.

Вся настройка осуществляется внутри YAML-файла, запуск происходит одной командой:

./zap.sh -cmd -autorun config.yaml

Создать YAML-файл можно либо ключом -autogenmin или -autogenmax к скрипту zap.sh, который идет в дистрибутиве ZAP, либо через UI — в нижней панели добавляется закладка Automation, конфигурируется и далее экспортируется в документ.

 Рис. 8. Automation Framework

Рис. 8. Automation Framework

Этот файл позволяет описать контексты, то есть сканируемые приложения, их настройки аутентификации и пользователей, а также задачи, которые будут выполняться для этих контекстов.

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

 Рис. 9. Создание нового плана

Рис. 9. Создание нового плана

Под задачами понимаются запуски аддонов, позволяющие выполнять различные действия, такие как построение структуры приложения (как простое, так и при помощи AJAX-паука), пассивное и активное сканирование, фильтрация найденных уязвимостей, создание отчетов. Предполагается, что к Automation Framework (который сам является аддоном) можно подключать дополнительные аддоны под разные требования.

Каждая такая задача имеет свои параметры и может быть настроена в самом YAML-файле конфигурации.

 Рис. 10. Настройка задачи паука

Рис. 10. Настройка задачи паука

Также для задач можно выставить критерий успешности, например, считать обход сайта удачным, если было найдено более 100 URL.

После настройки запустим Automation Framework. Если сканирование прошло успешно, можно экспортировать YAML-файл настроек и в дальнейшем использовать его для автоматизации. Узнать, насколько успешно прошло сканирование, получится по Output вкладке, в ~/.ZAP/zap.log или на панели AF, где будет частично сообщаться о результатах выполнения задач.

Далее этот файл конфигурации можно импортировать в ZAP либо использовать совместно с основным скриптом запуска ZAP zap.sh для выполнения указанных в файле задач.

Вот как может выглядеть подобный шаг в пайплайне:

Пример .gitlab-ci.yml для запуска AF на основе плана

run-zap:      # создаем задачу
tags:
- docker    # задаем тэг раннера, на котором хотим запустить задачу
  image: owasp/zap2docker-stable   # образ из докерхаба или другого репозитория
  before_script:   # то, что выполнится до основного скрипта
    - mkdir -p /zap/wrk/reports    # создаем папку reports для отчета в пути /zap/wrk (этот путь уже изначально есть в контейнере)
  script:    # основной скрипт
    - zap.sh -cmd -addonupdate    # обновляем аддоны (рекомендуется)     - zap.sh -cmd -autorun $CI_PROJECT_DIR/webgoat_config.yaml   # запускаем ZAP AF на базе плана, который лежит в основном репозитории   after_script:  # команды после основного скрипта
    - cp /zap/wrk/reports/webgoat_CI.pdf .    # копируем отчет из контейнера на раннер
    #- cp /home/zap/.ZAP/zap.log .   # копируем лог, если нужен для выявления проблем
  artifacts:
paths:
      - webgoat_CI.pdf  # указываем, какой отчет нам нужен
      #- zap.log  # указываем лог

Вместо вывода

В результате мы получаем отчет по заданному шаблону либо можем вытащить данные в JSON-формате для их дальнейшей обработки. Есть еще третий вариант — настроить интеграцию OWASP ZAP с платформой-оркестратором AppSec.Hub и получать уже готовые дефекты 😉

Полезные ссылки:

Контексты в OWASP ZAP

OWASP ZAP API

OWASP ZAP Automation Framework


ссылка на оригинал статьи https://habr.com/ru/companies/swordfish_security/articles/752528/

C++: чем CRTP лучше Шаблонного Метода?

Введение

Паттерн Шаблонный Метод (Template Method), описанный в книге по паттернам проектирования за авторством “банды четырех” (GoF), не связан с шаблонами (templates) C++ и является поведенческим шаблоном. Curiously Recurring Template Pattern (CRTP или “странно повторяющийся шаблон”) является усовершенствованием паттерна Шаблонный Метод и представляет собой идиому C++, в которой класс X наследуется от реализации шаблонного класса, используя сам X в качестве шаблонного аргумента. Название этой идиоме было дано Джимом Коплиеном (Jim Coplien), который наблюдал ее в самых первых образцах шаблонного кода C++. Эта методика позволяет достигнуть эффекта, аналогичного использованию виртуальных функций, без накладных расходов (и некоторой гибкости) динамического полиморфизма. CRTP можно использовать вместо Шаблонного Метода при условии, что вам не нужен динамический полиморфизм во время выполнения. Этот паттерн широко используется в библиотеках Windows ATL и WTL.

Шаблонный Метод

Давайте сначала рассмотрим классический паттерн Шаблонный Метод. В своей работе Шаблонный Метод полагается на полиморфизм и, как вы могли догадаться из названия, шаблонный метод. В нашем примере абстрактный базовый класс AbstractTextout имеет 11 перегрузок функции Print и одну чисто виртуальную функцию Process, которая будет реализована только в производных классах. Среди возможных примеров полезных производных классов можно выделить логирование, вывод на консоль и вывод отладочной информации. В этом руководстве мы ограничимся реализацией класса для вывода отладочной информации.

class AbstractTextout { public:     void Print(const wchar_t* fmt);     // ... плюс еще 10 других перегруженных Print с разным количеством аргументов Box protected:     virtual void Process(const std::wstring& str) = 0; };

Ниже вы можете наблюдать код одной из функций Print. Аргумент Box отвечает за преобразование POD (plain old data) в строку. Отличие от остальных перегрузок функции Print заключается в том, что они просто набивают больше аргументов Box в vs. Я не буду вдаваться в подробности реализации класса Box, так как цель этой статьи заключается в другом. Вы можете посмотреть ее в полном коде примера, ссылки на который приведены в конце статьи.

void AbstractTextout::Print(const wchar_t* fmt, Box D1) {     std::wstring wsfmtstr = fmt;      std::vector<std::wstring> vs;     vs.push_back( D1.ToString() );      std::wstring str = StrReplace( wsfmtstr, vs );      Process(str); // реализуется только в производном классе. }

Вот как производный класс DebugPrint реализует функцию Process в DebugPrint.cpp:

void Process(const std::wstring& str) { #ifdef _DEBUG     OutputDebugStringW( str.c_str() ); #endif }

А так мы взаимодействуем с классом DebugPrint:

#include "DebugPrint.h"  void main() {     DebugPrint dp;      dp.Print(L"Product:{0}, Qty:{1}, Price is ${2}\n", L"Shampoo", 1200, 2.65);     // выводит "Product:Shampoo, Qty:1200, Price is $2.650000" }

Для класса с виртуальными функциями создается специальная виртуальная таблица (vtbl). И конечно же наличие виртуальной таблицы подразумевает накладные расходы, связанные с определением правильной функции для вызова. Curiously Recurring Template Pattern же использует статический полиморфизм, который устраняет необходимость в этих накладных расходах. В следующем разделе мы разберемся, за счет чего это достигается.

Curiously Recurring Template Pattern

AbstractTextout теперь является шаблонным классом, что означает, что весь код, определенный в cpp, должен быть перемещен в заголовочный файл. Прежде чем вызывать Process, код сначала приводит (cast) себя к производному типу.

template <typename Derived>  class AbstractTextout { public:     void Print(const wchar_t* fmt, Box D1 )     {         std::wstring wsfmtstr = fmt;          std::vector<std::wstring> vs;         vs.push_back( D1.ToString() );          std::wstring str = StrReplace( wsfmtstr, vs );          static_cast<Derived*>(this)->Process(str);     } }

DebugPrint останется неизменным, за исключением того, что теперь сам является шаблонным типом в своем базовом классе AbstractTextout, и мы должны сделать функцию Process не виртуальной и переместить ее в заголовочный файл:

class DebugPrint : public AbstractTextout<DebugPrint> { public:     void Process(const std::wstring& str)     { #ifdef _DEBUG         OutputDebugStringW( str.c_str() ); #endif     } };

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

#include "DebugPrint.h"  void main() {     DebugPrint dp;      dp.Print(L"Product:{0}, Qty:{1}, Price is ${2}\n", L"Shampoo", 1200, 2.65);     // выводит "Product:Shampoo, Qty:1200, Price is $2.650000" }

Существует предвзятое мнение, что с C++ даже простую программу писать долго. Современный С++ и набор его библиотек легко могут опровергнуть это. Приглашаю вас на вебинар, где за 40 минут практической части мы создадим настоящий сетевой сервис на языке C++ с использованием библиотеки Boost.Asio.

Полезные ссылки 

Лицензия

Эта статья вместе со всем исходным кодом и файлами находится под лицензией Code Project Open License (CPOL)

Код примеров


ссылка на оригинал статьи https://habr.com/ru/companies/otus/articles/752530/

Как я сделал приложение для себя, а оно понравилось многим

Привет, меня зовут Артём и я Frontend Team Lead в Surf. Я очень люблю две вещи: геймификацию и графики. Сейчас я работаю с людьми больше, чем с кодом, поэтому читаю много тематических статей. Вот так, тихим вечером, в одной из них я натолкнулся на идею объединения Moving Motivators и лепестковой диаграммы.

Moving Motivators — это игровой метод работы с командой из практик Management 3.0, почитать можно здесь

А статья, где я нашёл идею, здесь

Во время чтения в голове возникла картинка — красивое обзорное сравнение мотивации команды, с графиком, всяческими подсказками и аналитикой. Я подумал, что мне, как тимлиду, было бы крайне полезно иметь такой инструмент, чтобы понимать, что вдохновляет и огорчает, что важно, а что нет — для команды в среднем и каждого человека персонально. Быстро погуглив, я не нашёл ничего похожего. Что ж, решил я, все описано, надо просто взять и сделать.

Правда на старте возникла проблема — отсутствие планирования, импульсивность решений и неуправляемая тяга к экспериментам привели к тому, что я бился лицом о грабли и переделывал приложение четыре раза (не надо так)

Сначала я начал делать на привычном стеке (CRA, который к этому времени уже морально устарел, плюс Ant Design и MobX) — и застрял довольно быстро, уже не помню на чём, пытаясь сразу сделать хорошо. Бросил и какое-то время не трогал. А потом, другим тихим вечером (вообще в тихие вечера часто голова варит лучше всего) мою голову пронзила мысль: «раз уж это геймификация, то почему бы не сделать всё на GameMaker?» (не осилил я Unity и потому предпочитаю GM). Я снёс все наработки и начал с чистого листа.

Второй подход достаточно долго шёл хорошо и гладко, но в какой-то момент я застрял на банальном действии — кажется, не мог добиться идеального тайминга анимаций. Расстроенный, я снова забросил разработку примерно на месяц.

Когда остыл и осознал свою ошибку, я ещё два раза переписывал с нуля, на этот раз используя привычный React-стек и не экспериментируя с технологиями. 

Но не с методологиями. В третьем подходе зачем-то решил заодно внедрить Feature Sliced Design, не имея опыта в построении такой архитектуры с нуля. Оказалось, для такого проекта этот метод не подходит и я больше решал проблемы «а как правильно разбить по слоям», вместо того, чтобы заниматься самим приложением. К счастью, эта неудача уже не привела к очередному переворачиванию стола и длительному перерыву в разработке — когда осознал проблему, я тут же создал новую папку под проект.

В последней попытке решил, что буду делать максимально просто, максимально быстро и максимально работоспособно. Взял связку Typescript + Next.js + RSuite + Apache ECharts без всяких стейт-менеджеров и прочего усложнения и начал писать «как буква ляжет». Потом, конечно, пришлось это всё рефакторить и разносить, но мне такой подход принёс давно забытое удовольствие — скорость реализации фич, исправления ошибок и внедрения новых идей (которые, естественно, возникали на ходу) были невероятны, а на выходе красота по полочкам.

По наполнению проблем особых не было — контент был готов ещё к первой итерации: дизайн экранов и примерное понимание флоу, картинки, сгенерированные с помощью Stable Diffustion, адаптированные названия карточек и куча данных по их описанию.

Хотя, нет. С названиями и описанием были проблемы — некоторые имена карточек было очень сложно переводить на русский, не теряя при этом частиц смысла. Пришлось вчитываться в определения оригинальных названий в словарях и искать максимально подходящие замены.

Например, Acceptance, которое помимо очевидного «принятия» подразумевает под собой ещё «принятие ответственности». Или неожиданно многогранное Honor. Или Relatedness, которое все равно потеряло часть своего смысла, превратившись в «связь». Потерянные значения я попытался закрыть блоком «Также известно как…»

Частично мне в этом помогал ChatGPT, но справился он не со всеми задачами, которые я перед ним ставил. Так что приходилось многое дорабатывать самому. В итоге результат мало напоминает оригинальные карточки — вышла не экранизация, а фильм «по мотивам произведения», да ещё и адаптированный. Этакая «Мотивационная братва». Но так как на тот момент приложение создавалось без прицела на широкую аудиторию, мне нравился результат.

После того как я решил «всё, готово!», было два бета-теста (на жене и на коллегах), которые помогли закрыть неочевидные для меня проблемы, добавить несколько приятных мелочей и принесли массу удовольствия от того, что многим приложение понравилось.

Прямо сейчас я одной рукой пишу этот текст, а второй заканчиваю ту самую страницу из моего воображения, из-за которой я взялся реализовать этот проект — интерфейс для обработки результатов команд. Точнее, страница уже готова, но стало очевидно, что без инструкции пользоваться ей немного сложнее, чем базовой функциональностью. Поэтому, прежде чем я выложу новый билд, мне необходимо написать статью или записать видео с объяснением, как этим всем пользоваться. Чтобы не быть голословным — держите скрин.

Когда-то давно, на заре своей карьеры, я работал дизайнером. Как вы понимаете, мир стал чуточку лучше, когда я перестал этим заниматься. Но в пет-проектах выбор не богатый и приходится всё делать самому настолько, насколько умею.

Из стратегических же планов пока что только банальщина — бэкенд, регистрация и хранение данных на сервере. Может быть, когда наберётся большое количество данных и обратной связи, родится ещё что-нибудь. А пока продолжаю писать как буква ляжет. Попробовать web-версию приложения → можно здесь

Больше интересного контента для разработчиков → в телеграм-канале Surf Web Team 

П.с там публикуем кейсы, лучшие практики, новости и вакансии Surf, а также проводим прямые эфиры. 


ссылка на оригинал статьи https://habr.com/ru/companies/surfstudio/articles/752534/

Open Source решение на React для быстрого создания панели управления в проекте

Мы занимаемся разработкой на аутсорсе. Часто стартуем новые проекты. Не работаем с готовыми CMS – всё на фреймворках с нуля, обычно PHP, node.js у нас на бекенде. В таких проектах постоянно требуется создавать панель управления. 

В 90% случаев это довольно однотипные таблички и формы. Процесс их создания превращается в рутину. В остальных 10% встречаются и сложные задачи, которые требуется реализовать в панели управления: особенные интерфейсы, логику. 

Мы искали инструмент, чтобы клепать однотипные админки, но при необходимости создавать и кастомные решения. У нас были такие требования:

  1. Мы хотим решение, которое быстро создает CRUD (create, read, update, delete) и требует минимальных усилий. 

  2. Мы хотим иметь возможность создавать какой-то сложный интерфейс, если этого потребует задача.

  3. Мы делаем классные, красивые проекты, поэтому хотим визуально приятное решение. 

  4. Мы хотим, чтобы решение не зависело от языка на back-end, так как, например, мы начинали с PHP, Laravel, но со временем в стэке появились node.js, Go.

Короче говоря, хотели быстро, красиво и кастомно. 

Изучение существующих решений

Конечно, сначала мы посмотрели в сторону готовых решений, которые нам советовали. Они хорошие, но:

  • созданы специально для каких-то фреймворков / языков типа laravel, node.js

  • умеют только генерировать CRUD, жестко заданной структуры, где ты не сможешь ничего своего внедрить или создать кастомно.

  • нельзя стилизовать

Вот, что мы рассматривали:

Панели управления для Laravel

Для node.js

https://adminjs.co Красивое промо, а demo сильно отстает

Самостоятельные панели управления

  • https://strapi.io/ —  очень крутая, но для других целей. Это, скорее, конструктор сущностей с интерфейсом и API

  • https://pocketbase.io/—  Аналогично, это конструктор сущностей с интерфейсом и API

  • https://directus.io/—  Это конструктор бэкенда.
    https://filamentphp.com/ чисто под php, нельзя кастомизровать стили, нельзя создавать свои интерфейсы. Возможно создавать только таблицы и формы по шаблону, а мы помним, что хотим гибкость, независимость от языка и возможность создавать свои интерфейсы и кастомизировать их

  • https://flatlogic.com —  Это тоже, скорее, конструктор бэкенда.

Прямые конкуренты

  • https://github.com/refinedev/refine 

  • https://marmelab.com/react-admin/ наверное, лучшее решение, что сейчас есть на рынке, давно развиваются, им наше уважение. К минусам отнесли мы следующие моменты: довольно старый проект, и где-то технологии уже устарели, несимпатичный интерфейс, старые UI библиотеки. Огромная документация и так просто создать CRUD и вникнуть в работу без погружения не получится.

Библиотеки

Так же изучали множество UI библиотек, которые представляют просто просто UI, но всю логику для генерации CRUD и авторизации в админке приходится писать самому.

Примеры:

https://www.lightningdesignsystem.com/

https://developer.microsoft.com/en-us/fluentui#/controls 

https://design.fusionfabric.cloud/foundations

— их огромное количестов дизайн систем.

Мы нашли самое схожее решение в нужном нам сегменте, и им стало https://marmelab.com/react-admin/

Оно обладает огромным функционалом. Но для нас это, отчасти, и проблема, так как онбординг занимает большое время. Ну и очень посредственный внешний вид. И тем не менее, это был наш основной конкурент.

Первые попытки создания собственного продукта

Мы начали разработку в апреле 2022 года. Сперва бэклог формировался из задач, которые решали на одном коммерческом проекте.

На момент тестового релиза в июне 2023 года трудозатраты на проект под названием Admiral составили 700 часов. На сегодняшний день мы уже обкатали это решение примерно на 10 коммерческих проектах – полет отличный.

Изначально у нас был только один разработчик, который делал коммерческий проект и фултайм работал над этим решением. На данный момент почти все фронты в нашей компании контрибьютят в проект. Мы стараемся, чтобы все отлично владели проектом. Но посмотрим как пойдет, какое будет финансирование. Возможно, в дальнейшем мы выделим команду, которая будет заниматься только этим проектом.

Вообще, мы трижды пытались создать что-то свое. Каждый раз забивали, потому что понимали, что где-то архитектурно ошибались.

Для своей первой админки мы взяли набор готовых компонентов — https://tabler.io/ . Сама админ-панель представляла собой смесь css, js, jquery и html, разбитых на компоненты blade. Например, были готовые layout, страницы формы и т.п.

Так выглядела типичная страница с формой для редактирования сущности

Но, конечно, можно было делать и более кастомные страницы.

Со временем мы все чаще стали использовать подход SPA для наших сайтов, такой же подход хотелось внедрить и в админ-панель, так как работать с blade шаблонами было неудобно. Особенно, когда задача выходила за рамки готовых компонентов. Мы обратили внимание на laravel nova. Нам понравился их подход, и мы стали адаптировать его под наши проекты.

Для начала взяли тот же tabler, но уже те компоненты которые были написаны на react. Получалось довольно неплохо, но все же в tabler на тот момент было недостаточно компонентов. 

Стали искать альтерантиву и она нашлась — ant.design . Взяв его компоненты, мы приступили к созданию админ-панели своей мечты. Постепенно появлялось все больше компонентов, прокачивался наш бэк. 

Вот как теперь это стало выглядеть:

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

Но…и этот подход оказался не идеальным. И вот почему:

  1. Админ панель была сильно связана с php. Нам хотелось иметь возможность использовать ее для проектов на других языках.

  2. Для разработки в том числе и фронта требовалось поднимать всю инфраструктуру: фронт админки, бэкенд, базу данных.

  3. Сложность разработки кастомных страниц, так как требовалась очень плотная работа бэкенд и фронтенд разработчиков. Структура json, которая приходила от бэкенда, была крайне сложная, возникали проблемы с ее модификацией.

Попытка №2

Решением всех этих проблем и следующим этапом стало выделение админки в полностью отдельное приложение. Теперь админка – такое же SPA-приложение, которое работает по REST api, с четким контрактом.

Теперь админ панель не привязана к языку, на бэкенде – мы использовали и php и golang. Простой типичный CRUD все так же просто создавать, этим занимаются бэкенд разработчики. Кастомные страницы создает фронтенд, для этого ему теперь не нужно поднимать еще и бэкенд. Благодаря простой api, ее легко “замокать”, и добавлять новые требования. 

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

На данный момент такой подход показал себя крайне гибким. А для упрощения рутинных задач мы используем команды кодогенераторы.

Наше время

Сегодня мы хотим поделиться с вами готовым решением на React от разработчиков для разработчиков. Недавно мы его релизнули в open source. Оно позволяет быстро создавать красивую панель управления в проекте и CRUD в ней, но при желании вы можете сделать и абсолютно кастомные интерфейсы. 

Для кого:

  • для тех, кому нужно часто создавать в проектах панель управления.

  • для тех, кому требуется кастомное решение и не подходят коробочные, но нет денег/времени на разработку с 0.

  • для тех, у кого нет времени разбираться с существующими решениями, которые требуют много времени и усилий на изучение.

Фишки:

📀 Используются готовые компоненты React.

⚙️ Не важно, на чем написан back-end, от него нужен только REST API.

🛡 Написана на TypeScript, есть встроенная типизация.

👨‍💻 Адаптивный дизайн: Интерфейс библиотеки масштабируется под любой размер экрана. Это удобно при использовании на мобильных устройствах.

🌍 Локализация: мы поддерживаем разные языки.

👨‍🎨 Интуитивно понятный интерфейс, с которым легко работать.

🎨 Различные темы оформления: вы можете изменить цветовую схему интерфейса в соответствии с вашими потребностями.

Если уже интересно, смотрите по ссылке https://github.com/dev-family/admiral. Будем рады звездочке на GitHub и рекомендации тем, у кого похожие боли. Ну и, конечно, обратной связи.


ссылка на оригинал статьи https://habr.com/ru/articles/752542/