Как с помощью Python создать приложение для расшифровки речи в реальном времени
Пошаговое руководство с использованием AssemblyAI и Streamlit
Научить ИИ разговаривать шёпотом — непростая задача даже сегодня. Но мы покажем, насколько простыми стали распознавание и транскрипция речи, по крайней мере, на поверхности. Интересно? Тогда добро пожаловать под кат.
Материал подготовлен к старту курса по Fullstack-разработке на Python.
Введение
Приложение расшифровки речи в режиме реального времени автоматически преобразует текст в речь. Этот текст почти мгновенно отображается на экране, а использовать подобные приложения можно для самых разных целей, включая расшифровку лекций, конференций и встреч. Здесь есть ряд преимуществ:
можно сразу записывать идеи и беседы. Это очень полезная функция для людей, которые работают в быстро меняющейся среде, и для людей с большим количеством идей;
развивать навыки общения, ведь теперь вы увидите, как говорите вы сами и как говорят другие.
Такими приложениями могут пользоваться люди с нарушением слуха или те, кто учит английский. Приложение расшифровывает аудио в реальном времени, а пользователь видит текст на экране параллельно произношению слов. К тексту можно применить обработку естественного языка.
Мы научимся создавать приложение для динамического преобразования речи в текст и сделаем это с помощью API AssemblyAI (серверная часть) и Streamlit (клиентская часть).
Вот видеоверсия статьи:
Обзор приложения
Для приложения понадобятся следующие библиотеки для Python:
streamlit — веб-фреймворком воспользуемся для размещения всех виджетов ввода и вывода;
websocket — позволяет приложению взаимодействовать с API AssemblyAI;
asyncio — позволяет выполнять всевозможный речевой ввод и вывод асинхронно;
base64 — кодирует и декодирует аудиосигнал перед его отправкой в API AssemblyAI;
json — считывает речевой вывод, сгенерированный через API AssemblyAI (например, расшифрованный текст);
pyaudio — обрабатывает речевой ввод через библиотеку PortAudio;
os и pathlib — используются для перехода по различным папкам проекта и работы с файлами.
Настройка рабочей среды
Чтобы воссоздать приложение для динамической расшифровки речи на вашем компьютере, мы создадим среду conda под названием transcription:
conda create -n transcription python=3.9
Возможно, высветится запрос на установку зависимостей Python-библиотеки. Если так, нажмите клавишу Y, чтобы подтвердить действие и продолжить.
После создания среды conda активировать её можно так:
conda activate transcription
Делать это нужно каждый раз в начале написания кода, а по завершении работы с кодом из среды нужно выйти:
conda deactivateЗагрузка GitHub-репозитория
Загрузим с GitHub весь репозиторий приложения динамической расшифровки речи:
Можно установить обязательные библиотеки, которыми пользуется приложение:
pip install -r requirements.txtПолучение ключа от API AssemblyAI
Получить доступ к API в AssemblyAI крайне просто. Для начала зарегистрируйтесь на AssemblyAI, это бесплатно. Зайдите в учётную запись. На панели справа вы увидите API-ключ:
Как получить API-ключ в AssemblyAI
Теперь, когда вы скопировали API-ключ в память, необходимо добавить его в файл secrets.toml из папки.streamlit. Путь к файлу выглядит так: .streamlit/secrets.toml. Его содержание должно быть таким:
api_key = ‘xxxxx’
Вместо xxxxx вставьте свой ключ от API. Получить этот ключ мы сможем с помощью строки кода st.secrets.
Запуск приложения
Прежде чем запускать приложение, давайте рассмотрим содержимое рабочей директории (то, что открывается по команде tree в Bash):
Содержимое папки realtime-transcription
Теперь мы готовы запустить своё приложение:
streamlit run streamlit_app.py
Этот код позволит открыть приложение в новом окне браузера:
Строки 10-13 — начальное состоянии сессии приложения.
Строки 15-22 — ввод для приём пользовательского ввода параметров аудио представлены виджетом text_input.
Строки 24-31 — для открытия потока аудиоданных через pyaudio используются входные параметры аудио из блока кода выше.
Строки 33-46 — определяют 3 пользовательские функции (например, start_listening, stop_listening и download_transcription), которые будут вызываться в коде (см. ниже).
Строка 49 — отображает название приложения через строку st.title.
Строки 51–62 — отображает информацию о приложении (раздел About) с помощью строки st.expander.
Строки 64–67 — создают 2 столбца строкой st.columns для размещения кнопок «Пуск» (Start) и «Стоп» (Stop). То есть они используют start_listening и stop_listening через параметр on_click виджета кнопки.
Строки 69–139 — здесь выполняется обработка речевого входа и выхода: аудиосигнал передаётся в API AssemblyAI, где расшифрованный текст выдаётся в формате JSON. Эта часть была изменена и адаптирована из блока кода, написанного Мисрой Турп и Джорджиосом Мириантусом.
Строки 141–144 — отображают кнопку загрузки расшифровки, а затем удаляют файл.
Весь код
import streamlit as st import websockets import asyncio import base64 import json import pyaudio import os from pathlib import Path
# Состояние сессии if ‘text’ not in st.session_state: st.session_state = ‘Listening…’ st.session_state = False
async def send(): while st.session_state: try: data = stream.read(FRAMES_PER_BUFFER) data = base64.b64encode(data).decode(“utf-8”) json_data = json.dumps({“audio_data”:str(data)}) r = await _ws.send(json_data)
except websockets.exceptions.ConnectionClosedError as e: print(e) assert e.code == 4008 break
except Exception as e: print(e) assert False, “Not a websocket 4008 error”
r = await asyncio.sleep(0.01)
# Принимаем транскрипцию (вывод) async def receive(): while st.session_state: try: result_str = await _ws.recv() result = json.loads(result_str)
if json.loads(result_str)==’FinalTranscript’: print(result) st.session_state = result st.write(st.session_state)
if Path(‘transcription.txt’).is_file(): st.markdown(‘### Download’) download_transcription() os.remove(‘transcription.txt’)
# Ссылки (этот код — адаптация кода по ссылкам ниже) # 1. https://github.com/misraturp/Real-time-transcription-from-microphone # 2. https://medium.com/towards-data-science/real-time-speech-recognition-python-assemblyai-13d35eeed226
Заключение
Поздравляем, вы создали приложение для динамического преобразования речи на Python с помощью API AssemblyAI. Как уже говорилось, у таких приложений есть несколько вариантов использования (диктовка статьи/сочинения или письма, развитие навыков общения преобразование речи для людей с нарушением слуха и т. д.).
Вы можете поступить как автор, то есть снова адаптировать код, уже для российских голосовых API. А мы поможем прокачать ваши навыки или с самого начала освоить профессию, актуальную в любое время:
Вёрстка помогает нам выстраивать содержимое веб-страниц по определённым правилам: например, строго в соответствии с согласованным макетом или в зависимости от пользовательского устройства. Сегодня сайты неплохо умеют подстраивать свой контент и под различные размеры экранов, но так было не всегда.
Существует несколько основных подходов к вёрстке, особенности которых мы рассмотрим в этой статье на простом наглядном примере:
Структура представленной разметки страницы очень простая: сверху располагается шапка, ниже — основное содержимое страницы, которое включает в себя контейнер для карточек и сами карточки.
Для демонстрации подходов к вёрстке мы будем вносить изменения только в CSS, в то время как представленная выше HTML-разметка будет оставаться неизменной.
А теперь давайте вернёмся к обсуждению самих подходов.
Фиксированная вёрстка
Самым простым и не требующим больших усилий решением можно считать фиксированную вёрстку. Фиксированной она называется потому, что содержимое страницы, свёрстанной таким образом, никак не подстраивается под размер экрана и отображается на всех устройствах одинаково.
Для фиксированной вёрстки ширина контейнера, в котором находится весь контент, указывается в абсолютных величинах (например, px). Помимо ширины контейнера не лишним будет указать внешние отступы, чтобы контент отображался всегда по центру экрана.
Стили, указанные после отступа, нужны для формирования сетки контента, которая будет иметь три колонки одинаковой ширины и отступы в 24px между колонками и строками. Сетка задаётся с помощью CSS Grid Layout, и если вы ещё не знакомы с возможностями этого способа раскладки, рекомендую ознакомиться с данной статьёй.
Этого уже будет достаточно, чтобы добиться ожидаемого отображения веб-сайта на обычном экране компьютера.
Однако, такая вёрстка совершенно не подходит для отображения на мобильных устройствах: из-за фиксированной ширины контейнера пользователю придётся приближать страницу и скроллить её влево-вправо, чтобы иметь возможность прочитать имеющийся на ней текст.
Отзывчивая вёрстка
В таком случае мы могли бы сделать наш интерфейс чуть более дружелюбным, если бы стали использовать отзывчивую (или резиновую) вёрстку. Её особенность состоит в том, что для задания параметров используются не абсолютные, а относительные величины (например, %), благодаря чему содержимое страницы может подстраиваться под ширину экрана.
Для нашего примера также необходимо указать максимальную ширину контейнера, чтобы на больших экранах контент отображался точно так же, как и в случае с фиксированной вёрсткой.
Ещё мы добавим внутренний отступ, чтобы на маленьком экране карточки не прилипали к его краям. Именно из-за этих внутренних отступов в 16px слева и справа ограничения по максимальной ширине увеличиваются с 900px до 932px.
Вот как поведёт себя резиновая вёрстка на средних и маленьких экранах. На планшете она отображается так же хорошо, как и на экране компьютера, но на мобильных устройствах ситуация только ухудшилась: карточки стали настолько узкими, что прочитать текст на них практически невозможно.
Адаптивная вёрстка
С ростом популярности смартфонов появилась и стала распространённой адаптивная вёрстка, которая позволила изменять стили контейнера в зависимости от ширины экрана. Этот подход может помочь нам избежать проблем, с которыми мы столкнулись выше, когда использовали резиновую вёрстку на маленьком экране.
С помощью медиа-запросов мы можем изменять сетку, по которой будет выстраиваться контент. Изначально стили указываются для минимальных размеров экрана (такой подход называется mobile-first), и если ширина экрана достигает какого-либо брейкпоинта, первоначальные стили заменяются новыми. Существует множество систем величин брейкпоинтов, но мы воспользуемся значениями, представленными здесь.
В нашем примере мы будем изменять максимальную ширину контейнера и сетку для расположения карточек: на маленьких экранах (шириной меньше 480px) колонка будет одна, на экранах средней величины (от 480px до 768px) — две, а на больших (шириной больше 768px) — три. Максимально возможная ширина контейнера по-прежнему 932px, она будет установлена на экранах, ширина которых равна этому значению или больше него.
Последним и самым современным подходом к вёрстке веб-сайтов является отзывчиво-адаптивная вёрстка, которая сочетает в себе положительные свойства двух рассмотренных выше подходов. Такая вёрстка подойдёт в том случае, когда приоритетная задача состоит в том, чтобы занять максимум от имеющегося пространства: например, если контент представлен не карточками, а изображениями, и нам не нужны поля слева и справа от контейнера.
При таком подходе вёрстка между брейкпоинтами ведёт себя абсолютно «резиново», но на брейкпоинтах могут меняться какие-то важные свойства отображения (в нашем случае — свойства сетки). Используя данный подход, вы можете быть уверены, что на экране абсолютно любого размера ваше приложение будет отображаться как нужно и им будет удобно пользоваться.
Последний рассмотренный подход к вёрстке считается самым надёжным и безопасным решением, однако, выбор типа вёрстки должен производиться индивидуально в зависимости от задач и структуры конкретного веб-приложения.
Код всех рассмотренных примеров вы можете посмотреть в песочнице.
В этой статье мы рассмотрели типы вёрстки, отличающиеся поведением на экранах разных размеров, но это лишь один взгляд на вёрстку. Существуют также иные подходы к выделению её типов, например, в зависимости от используемых HTML-тегов, но об этом мы поговорим уже в следующих статьях.
Заголовки должны быть большими, жирными и громкими, чтобы привлекать внимание пользователя за несколько секунд. Мы перевели статью о стилях заголовков, сегодня предлагаем изучить градиенты.
В заголовках обычно содержится текст, набранный крупным шрифтом, выделенный начертанием или цветом. К нему иногда прилагается описательный подзаголовок, изображения и кнопки призыва к действию.
Текст основного заголовка обозначается тегом h1, его стилизацией мы и займёмся.
Полезные подсказкиВыбирайте правильные цвета
Если работаете с брендбуком, сосредоточьтесь на фирменных цветах. Если инструкций нет, сначала подберите сочетающиеся цвета, потом приступайте к оформлению.
Делайте заголовок уникальным
Тест и стиль должны быть уникальными. На одной веб-странице нужен только один тег h1 — это ради SEO и производительности, особенно если у заголовка сложный стиль.
Выбирайте правильные шрифты
Между визуальным хаосом и хорошим заголовком — тонкая грань, которую пересекают размер, цвет, начертание и гарнитура шрифта. Поэтому иногда творческие порывы надо притормаживать.
CSS градиенты цвета текста
Градиент — цветовой переход между цветами, при котором они смешиваются. Есть три способа добавить цветовые градиенты к тексту заголовка CSS:
linear-gradient()
radial-gradient()
conic-gradient()
1. linear-gradient()
Функция обеспечивает переход цвета по прямой линии. Сделаем для старта заголовок в HTML:
Modern Frontend Monitoring and Product Analytics
Используйте семантический тег header, как показано, тег section с ID или проверенный с class=”header”. Главное, чтобы заголовок был заметным и узнаваемым.
Функция linear-gradient() позволяет добавлять специфические параметры, в том числе направление градиента и как минимум два значения цвета. Но можно добавлять больше — количество цветов не ограничено. Посмотрим на CSS:
Свойство background-clip гарантирует, что фон не выйдет за пределы элемента — в данном случае это текста. Свойство color установлено как transparent, так что фон видно прямо за заголовком. Результат кода: светлый цвет перетекает в тёмный слева направо:
Линейный градиент текста слева направо
Стилизовать заголовок с градиентом можно в других направлениях:
linear-gradient(45deg, #553c9a, #ee4b2b);Линейный градиент по диагонали
Начало перехода цвета можно задать, добавив процентное значение после первого цвета. Фиолетовый занимает 45% текста заголовка, а потом переходит в красный:
linear-gradient(to right, #553c9a 45%, #ee4b2b)Линейный градиент с процентами2. radial-gradient()
Радиальный градиент начинается в исходной точке, из которой расходятся цвета. По умолчанию переход цвета начинается из центра элемента.
Первый цвет перемещается от начальной точки 0% и проворачивается на 33% заголовка. В этой точке должен начаться цветовой переход, но начальная точка следующего цвета была 33%, поэтому цвет меняется сразу.
Бонус: повторяющиеся градиенты
Как можно понять, градиент цвета текста повторяется по всему заголовку. Добавим цветовые точки, получим градиентные узоры.
Даже если вы не собираетесь вызывать у пользователей головокружение, учитесь использовать градиенты.
Используйте оттенки одного цвета
Чтобы получить паттерн repeating-radial, используем такой синтаксис:
repeating-radial-gradient(circle closest-corner at 100px 100px, #553c9a, #ee4b2b 10%, #553c9a 20%);
Теперь поменяем красный на светло-фиолетовый:
repeating-radial-gradient(circle closest-corner at 100px 100px, #553c9a, #b393d3 10%, #553c9a 20%);
Итог:
Используйте не только оттенки одного цвета. Жёлтый хорошо сочетается с оранжевым, а зелёный — с синим.
Используйте правильные параметры
Помните четыре параметра для radial-gradient? Посмотрим ещё.
repeating-radial-gradient( ,, , );
Градиенту можно задать форму circle или ellipse. По умолчанию это ellipse с положением в центре.
Размер фигуры можно определить с помощью параметров:
closest-side — форму градиента определяет ближайшая к центру стороной элемента;
closest-corner — форму градиента определяет ближайший угол элемента;
farthest-side —форму градиента определяет сторона элемента, удалённая от центра;
farthest-corner — форму градиента определяет удалённый от центра угол.
Далее идёт позиция от corner или side. Это может быть процентное значение или длина.
Используйте минимум два цвета, но помните, что повторение первого в качестве третьего даёт плавный переход. Для ясности используем красный и фиолетовый:
Вот что получится при repeating-radial-gradient использовании значений closest-:
А теперь при использовании значений farthest-:
Между двумя первыми заголовками нет большой разницы, потому что градиент расходится из центра наружу. Тот же результат можем получить, используя linear-gradient. Лучше указать форму и определить точки цветовых переходов.
Видно, что свойство repeating-radial-gradient со значением closest-side или closest-corner выглядит ярче, но снижает читаемость, если использовать неподходящие цвета. Значения farthest-corner или farthest-side позволяют получить чёткий текст даже с контрастными цветами.
Фронтенд-новости №8. Вышел WordPress 6.0, найдена оптимальная длина строки, под вопросом
Дайджест новостей из мира фронтенд-разработки за последнюю неделю 23–29 мая.
К сожалению, на хабре нельзя устанавливать alt для картинок, поэтому …ДоступностьHTMLCSSJavaScriptReactNode.jsAngularVueИнструментыОбщее
Как выделяться с помощью типографики. Рассматривайте возможности типографики как дополнительную возможность привлечь внимание вашего интерфейса.
Оптимальная длина строки. Спойлер: 50-75 символов. Меньше 50 или больше 75 может ухудшить читабельность
Почему в вебе закругляют углы?
Как читать статьи на английском языке
В дайджесте много статей и видео на английском языке, чтобы это не стало препятствием: в Google Chrome есть функция перевода страницы с любого популярного языка, а видео можно перевести в Яндекс Браузере.
Для того чтобы создать сайт, который будет максимально привлекателен для посетителя, мало купить оригинальное доменное имя, разработать уникальный дизайн и вставить яркие картинки. Для сайта не менее важным элементом являются используемые шрифты, являющиеся частью дизайна. Если веб шрифты страницы, заголовков и текстовых блоков правильно подобраны, то прочтение основного текста будет более простым и комфортным, а это положительно скажется на общем впечатлении о сайте. В большинстве случаев именно текст является одним из главных способов коммуникации посетителя сайта и компании, поэтому важно понимать, что шрифты для инстаграма или для фотошопа далеко не всегда будут логично смотреться на информационном портале или в Интернет-магазине.
Виды шрифтов для сайта
Все веб шрифты страницы сайта делятся по начертанию на рукописные и печатные. Кириллические рукописные в большинстве случаев используются для того, чтобы оформить привлекательный заголовок, логотип, выделить в тексте отдельные фразы. Также такие шрифты подойдут для инстаграма и для фотошопа, когда необходимо персонализировать картинку. Для основных текстовых блоков такие шрифты не подходят, так как не особо удобны для чтения и восприятия, поэтому для основного текста используются печатные шрифты. Они делятся на три категории:
С засечками (в их названии часто присутствует serif) – они хорошо подходят для печатных изданий, а для сайта не совсем логично их использовать, так как они не всегда удобны к восприятию и рассеивают внимание посетителя. Поэтому такие шрифты востребованы для заголовков, подзаголовков и выделения отдельных блоков;
Рубленые или без засечек (обозначаются как sans serif) – нейтральные и универсальные шрифты, которые удобны к прочтению и восприятию. Многие сайты оформляются только при помощи таких шрифтов;
Декоративные – отличаются оригинальностью и яркостью, но текст, который ими написан, сложен для чтения, поэтому чаще всего такие шрифты применяются при создании заголовков и логотипов. Стоит сказать, что далеко не всегда можно скачать кириллицу отдельных шрифтов, из-за чего имеется ряд ограничений по их использованию.
Наиболее востребованные шрифты для сайтов
Количество популярных бесплатных css на просторах Интернета достаточно большое, равно как и количество шрифтов, которые доступны для бесплатного скачивания. Найти жирный (bold) шрифт не составляет труда, но необходимо знать о самых популярных шрифтах, использование которых практически всегда оправдано. Среди таких шрифтов (они все могут быть кириллическими рукописными) можно выделить:
Roboto – наиболее популярный шрифт, имеющий громадное количество вариаций, среди которых можно найти жирный (bold), light, black;
Open Sans – наиболее удобочитаемый всего читать, поэтому в Интернете его можно встретить почти на каждой интернет-странице;
Montserrat – простой, интересный и привлкательный для чтения, но при этом позволяющий «зацепиться» взгляду во время просмотра больших документов;
Roboto Condensed – имеет особенность – он узкий и несколько вытянутый, но при этом понятный и читабельный;
Source Sans Pro – имеет максимальный набор начертаний, причем разработан Adobe, поэтому отлично подойдет для Фотошопа и других графических приложений. Скачать кириллицу для него можно во всех вариантах;
Oswald – чаще всего применяется для заголовков, как для полноценных сайтов, так и для приложений;
Merriweather – имеет засечки и немного вытянутые и плавные буквы. При желании можно скачать вариант и без засечек;
Noto Sans – отличается упрощенным дизайном символов, но при этом очень хорошо читаем, благодаря чему отлично походит для текстовых материалов;
Yanone Kaffeesatz – имеет стиль типографской печати кофеен начала 20-го века. Немного старомоден, но выглядит дорого, благодаря чему отлично подходит для заголовков рекламного характера;
Caveat – имеет элегантные прописные литеры, которые узнать довольно просто. Хорошо подойдет для заголовков, но для текста.
Естественно, количество шрифтов, которые могут использоваться в популярных бесплатных css, исчисляется десятками тысяч, поэтому выбрать обычно довольно сложно. Особенно в тех случаях, если имеется желание выделиться. В такой ситуации оптимальным вариантом будет обращение к профессиональному дизайнеру, который подберет набор шрифтов с учетом дизайна сайта, его специфики и количества текстовой информации, размещенной на нем.
Особенности выбора шрифтов для сайта
Интересно то, что по статистике пользователи перед тем, как прочитать текстовые блоки на сайте, бегло просматривает заголовки, после чего принимают решение о дальнейшем прочтении, поэтому важно уделить особое внимание выбору шрифтов и их сочетанию. Использование традиционных шрифтов – Open Sans, Roboto, Montserrat – это беспроигрышный вариант, так как они простые, читабельные, привычные. Поэтому важно учесть то, какой контент необходимо оформить.
Отдельного внимания заслуживает тематика сайта и стилистика шрифтов, поэтому при возникновении сомнений относительно выбора стоит доверить этот процесс профессиональному дизайнеру. Среди общих рекомендаций по выбору шрифтов можно отметить:
Не стоит применять более трех шрифтов на странице, иначе текст никто не будет читать. Если необходимо выделить отдельные фрагменты, то имеет смысл использовать один и тот же шрифт, но в разных начертаниях;
Читабельность на первом месте – шрифт должен быть, прежде всего, читабельным, иначе страница будет только просматриваться и закрываться;
Шрифтовые пары должны сочетаться между собой. Если шрифты плохо сочетаются между собой, то стоит один из них сразу же менять;
Логичность обязательна – цвета букв должны быть подобраны с учетом фона, а размер быть таким, чтобы его было удобно читать.
Независимо от того, для какого сайта подбираются шрифты, необходимо экспериментировать – так можно подобрать оптимальное и при этом оригинальное сочетание, благодаря которому посетитель прочитает все от начал до конца, ведь именно это важно для многих.
Знакомство с профилировщиком производительности вашего браузера / Хабр
Эта статья — перевод оригинальной статьи Thomas Belin “Get to know your browser’s performance profiler”
Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.
Вступление
В какой-то момент своей карьеры вы, возможно, просматривали вкладку «Производительность» в инструментах разработки вашего любимого браузера. В конце концов вы попытались создать profile, но, вероятно, быстро разочаровались. Высокая плотность отображаемой информации делает ее немного подавляющей и несколько пугающей. Я был там, я понимаю тебя!
Хорошая новость: кривая обучения на самом деле не такая крутая!
Как только вы усвоите несколько концепций, он внезапно станет вашим самым ценным инструментом для устранения узких мест в производительности.
Эта статья даст вам несколько ключей к пониманию того, как работает профайлер и как правильно его использовать.
Давайте полностью забудем о console.log и console.time, сегодня мы погрузимся в профилировщик производительности!
Примечание: я не буду слишком углубляться в сложные сценарии, но в конечном итоге напишу дополнительную статью о продвинутых методах.
Модель данных
Первым шагом, который я предпринял, чтобы понять, как работает профилировщик, было чтение документации Mozilla об их новом профилировщике производительности (это отличный документ, прочтите его).
Первый вау-эффект, который у меня был, был, когда я увидел модель данных, которую использовал профайлер. Это на самом деле довольно просто
В документации Mozilla модель данных представлена следующим образом:
A A A | | | v v v B B B | v C
A, B и C — имена функций, а по оси X мы получаем время. По умолчанию профилировщик Firefox и Chrome настроен на создание снимка каждые 1 мс, что означает, что здесь каждый столбец представляет 1 мс.
В этом примере это означает, что стек со временем развивался таким образом.
в 0мс A вызывал B, а B все еще работал;
в 1 мс B вызывал C, а C все еще работал;
в 2 мс C закончил свою работу, и мы вернулись в B;
в 3 мс стек оказался пуст.
Из этого профилировщик может сделать следующие выводы:
А почти сразу вызвал B;
Мы пробыли ~1 мс в B, прежде чем вызвать C;
C потребовалось ~ 1 мс для выполнения;
B снова потребовалось еще ~ 1 мс после вызова C;
А закончил выполнение сразу после вызова B.
Имея в виду эту модель, мы можем создать некоторые данные:
A A A A A A A A A | | | | | | | | | V V V V V V V V V B B B B B B B B B | V C
B занимает немного времени до и после вызова C. Мы потратили ~1 мс на C и не потратили время на A:
A A A A A A A A A | | V V B B | V C
A занимает некоторое время перед вызовом B. B и C занимают ~1 мс.
Ограничения этой модели
Поскольку профилировщик берет только 1 выборку в мс, это означает, что вызов функции, который занимает менее 1 мс, имеет высокую вероятность не отображаться в сгенерированном профиле.
Сгенерированный профиль, скорее всего, будет выглядеть примерно так:
A A | | v v B D
В этом профиле не будет упоминаний о C или E.
Но мы здесь для отладки долгих задач, помните? Нет необходимости разбирать эти быстро выполняющиеся функции. Нам до них нет дела!
Собственное время (self time) против общего времени (total time)
Одно немного запутанное понятие в профилировщике — это собственное и общее время. Однако на самом деле это понятие довольно легко понять.
Их можно определить так:
собственное — время, проведенное в самой функции;
общее — время, проведенное в функции и всех дочерних функциях, которые она вызывает.
Чтобы прочувствовать это, вот конкретный пример:
function superExpensive() { for (let i = 0; i < 1e10; i++) {
console.log(i);
}
}
function main() {
superExpensiveComputation(); // < takes 1000ms
for (let i = 0; i < 1e6; i++) {
// ^ takes 5ms
console.log(i);
}
}
main будет иметь собственное время 5мс, но общее время 1005мс. superExpensiveComputation будет иметь общее и собственное время 1000мс
Общее время помогает выявить проблемные части кода, а собственное время позволяет сузить область поиска до функции, которая действительно требует вашего внимания.
Погружение в UI
Имея в виду эту модель, пользовательский интерфейс начинает обретать смысл. Понятия, которые мы видели ранее, начинают пригодиться для эффективного использования пользовательского интерфейса.
Здесь я сосредоточусь на профилировщике Firefox, но те же принципы применимы и к профилировщику Chrome.
Определение долгих функций верхнего уровня: дерево вызовов
Для начала возьмем очень простой пример кода. Представьте, что где-то есть кнопка, и при нажатии на нее мы запускаем функцию calculateNumber.
function generateNumber(nbIterations) { let number = 0; for (let i = 0; i < nbIterations; i++) {
number += Math.random();
}
return number;
} function computeNumber() {
console.log(generateNumber(1e9));
}
Вот что мы получим в нашем отчете профилировщика:
Поскольку профилировщик фактически профилирует все процессы Firefox, мы хотим убедиться, что мы просто проверяем текущее веб-приложение, над которым работаем.
Мы здесь веб-разработчики, нам не нужны внутренние трассировки стека браузера, давайте оставим только трассировки стека JS.
Мы ясно видим, что больше всего времени мы тратим на функцию generateNumber (здесь функция появилась в 488 выборках, что означает, что она выполнялась как минимум 488мс).
Дерево вызовов позволит вам быстро определить, какие функции верхнего уровня требуют времени. Это хороший обзор того, с чего начать копать, но он не поможет вам быстро определить вложенные функции, которые имеют большое собственное время работы.
Определение долгих вложенных функций: инвертирование стека вызовов
Теперь рассмотрим следующее:
function computeMultipleNumbers() { let number = 0; for (let i = 0; i < 10; i++) {
const fnName = `gen${Math.round(Math.random() * 100)}`; // We create a function with a random name
const fn = new Function(`function ${fnName}() {
return generateNumber(1e7);
} return ${fnName}`);
number += fn()();
}
result.innerText = number;
}
Особенность этой функции в том, что она генерирует именованные функции со случайными именами. Это означает, что теперь generateNumber будет вызываться из множества различных функций.
Посмотрим, как выглядит результат:
Здесь мы видим, что вызывается много функций, но все они имеют пустое собственное время. А это значит, что это не та функция, на которую мы на самом деле потратили время, они ждали завершения чего-то другого.
Теперь, если мы инвертируем стек.
Тут становится понятно, где мы собственно потратили время: в функции generateNumber 🙂
Инверсия фактически сортирует функцию с наибольшим собственным временем и сглаживает их в корне дерева. Это отличный способ идентифицировать трудоемкую функцию, и вы получаете ее стек вызовов прямо рядом с ней. При этом вы точно знаете, какая функция является проблемой и откуда она была вызвана.
Это дерево вызовов:
topLevel // self 0 first // self 0 second // self 0 third // self 10 fourth // self 7 fifth // self 8
Это инверсия стека вызовов:
third //self 10 second topLevel fifth // self 8 fourth second topLevel fourth // self 7 second topLevel
Таким образом, мы можем быстро определить, что мы потратили ~ 10мс на вызов third из topLevel > second
Заключение
В этой статье мы рассмотрели основные функции профайлера. Мы увидели, как использовать дерево вызовов и перевернутый стек вызовов для быстрого определения функций, требующих много времени, в вашем приложении.
Теперь эти трудоемкие функции не обязательно являются функциями, которые вам нужно оптимизировать. Проблема может заключаться в родительской функции или даже выше в дереве. Перевернутый стек вызовов дает вам хорошую отправную точку для изучения проблемной части вашего приложения.
Мы не рассмотрели здесь, что такое Flame Graph или Stack Chart, как профилировать асинхронный код или продвинутые методы, такие как маркеры. Это то, что я хотел бы осветить в следующей статье.
Архитектура фронтенда и какой она должна быть / Хабр
Все мы знаем про, или слышали про практики и паттерны проектирования SOLID, GRASP, MVC, MV** и даже применяем их с переменным успехом, стараясь нащупать эффективный подход к построению приложений. Но это лишь приводит к разнообразию реализаций наших приложений и частей функционала.
И поэтому я уже долгое время пытаюсь понять по каким правилам должно строиться фронтенд приложение чтобы оно удовлетворяло следующим критериям:
легкое расширение функционала приложения;
безболезненное внесение изменений в существующий функционал;
унифицированная структура приложения;
быстрый onboarding новых разработчиков на проект;
понятный и прозрачный код;
всегда понятно где в структуре файлов расположить ту или иную функциональность.
Какие у нас есть варианты?
«Организация файловой структуры это все что нам нужно»
Каждый лид или сеньор сами для себя выбирают варианты компоновки структуры приложения и выделения сущностей приложения. По итогу каждая система становится уникальной и неповторимой. И для того, чтобы разобраться в ней, нужны время и усилия, которые нужно будет тратить каждый раз при смене проекта. Плюс никто не отменял “бас фактор”.
Существует большое кол-во статей, описывающих «оптимальные», по мению авторов, варианты таких подходов. Пример.
Но это, в основном, про структуру файлов и частные случаи использования какого-то функционала. Такой подход только частично унифицирует структуру приложения, но этого мало для того, чтобы называться архитектурой. Может есть что-то лучше?
Domain Driven Design
Много умных дядек, таких как Мартин Фаулер и дядюшка Боб, написали много статей про него. На бэкенде в больших и сложных проектах он неплохо себя зарекомендовал. Но есть и много изъянов: туча абстракций, для простых действий нужно писать много кода, ну и разобраться, как готовить DDD та еще задача.
Есть примеры как готовить это на фронте, но, как видно, проблемы никуда не уходят и кол-во абстракций удручают. Простой onboarding тут невозможен, без прочтения “The Big Blue Book” и пары недель общения с ментором.
Есть переосмысленные подходы к архитектуре, которые больше похожи на правду и наверняка могут где-то успешно применены.
Основательная статья от Кхалила Стеммлера о возможной архитектуре клиентских приложений частично полагается на DDD подход, но при этом сильно его упрощает, освобождая нас от ненужных абстракций и смещая понятия в сторону фронт приложений.
Но бизнес логика в таких приложениях немного размывается и подход больше сфокусирован на функциональных слоях приложения, что отдаляет нас от требования к прозрачному коду и явной бизнес логики.
Джимми Богарт в своей статье пишет что DDD подход не совершенен и избыточен, и, как следствие, он предлагает переработанный подход vertical slices. И это отличный подход, о котором стоит почитать отдельно. Эта идея довольно простая и мы можем адаптировать ее к фронтенд приложениям.
Если DDD не удалось применить для наших нужд, то можно попробовать построить его на более общих правилах, которые предоставляет нам “Clear architecture”, ведь DDD основывается именно на них.
Clear architecture
Также есть попытки следовать всем постулатам чистой архитектуры и абстрагироваться от представления совсем. В этом случае мы сможем подменять view на любой фреймворк или вообще отказаться от его использования. Интересный подход и в некоторых случаях вполне обоснован и может оказаться отличным решением. Самый частый кейс, это использование одной и той же логики в браузере и на мобильном приложении. Подробнее об этом можно почитать тут.
Разработчики Flutter тоже столкнулись с проблемой сложности переиспользования логики между различными представлениями, и предложили подход – Business Logic Component (BLoC). Он позволяет снизить нагрузку на компоненты пользовательского интерфейса, отделив от них бизнес-логику.
Тут пример одной из реализаций BLoC в React.
Вроде неплохо, но все же есть много вопросов. И почти нет сообщества, которое бы могло помочь с возникающими вопросами.
FCD – Feature Sliced Design
И недавно для меня стало открытием методология FCD – Feature Sliced Design. На мой взгляд лучшем решением будет обратить внимание именно на эту методологию.
Ссылка на офф сайт.
Методология не привязана к конкретному стеку технологий и применима к большинству frontend-приложений. Документация содержит примеры реализации на JavaScript + React, но FSD успешно адаптируется и к другим комбинациям инструментов.
Для проектирования архитектуры методология предлагает следующие архитектурные абстракциями на основе которых строиться наше приложение.
Ниже приведу описание терминов из документации:Layers
Первый уровень абстрагирования – согласно скоупу влияния.
model – бизнес-логика модуля (store, effects/actions, hooks/contracts, …);
lib – вспомогательные библиотеки;
api – логика взаимодействия с API;
config – модуль конфигурации приложения и его окружения.
Ниже приведу пример описания фичи авторизации.
# Сегменты могут быть как файлами, так и директориями | ├── features/auth # Layer: Бизнес-фичи | | # Slice Group: Структурная группа “Авторизация пользователя” | ├── by-phone/ # Slice: Фича “Авторизация по телефону” | | ├── ui/ # Segment: UI-логика (компоненты) | | ├── lib/ # Segment: Инфраструктурная-логика (helpers/utils) | | ├── model/ # Segment: Бизнес-логика | | └── index.ts #
| | | ├── by-oauth/ # Slice: Фича “Авторизация по внешнему ресурсу” | …
Помимо унификации структуры, мы получаем наглядную бизнес логику, отличное описание слоев приложения с примерами на популярных ЯП. Также есть ответы на вопросы о расположении функционала и понятные правила уменьшения зависимостей в коде.
Эта методология только развивается и есть хорошее комьюнити, которое так же как и мы задается вопросами архитектуры фронтенда.
Заключение
У каждого из подходов есть свои плюсы и минусы. Учитывая что каждый проект имеет разный размер, сложность и специфику и цели, то что подойдет многим не факт что подойдет вам. Надеюсь что после прочтения статьи вы откроете для себя что то новое и сможете улучшить ваши собственные проекты.
Также если вам интересно в своем Telegram я время от времени выкладываю интересные находки по фронтенду. И всем чистой архитектуры.
Речь пойдёт про мощный инструмент, реализующий собой подход реактивного программирования и в разы упрощающий разработку — RxJS. В частности разберём один момент, про который нужно не забывать при использовании этой библиотеки, а именно — отписки.
Да, да, этот базовый момент может упускаться разработчиком, и это в свою очередь может привести к утечке памяти — об этом далее.
Кейс — отправка запросов на бэк и показ данных в реальном времениЗадача
Представим, что мы разрабатываем приложение, которое в реальном времени показывает нам курсы валют. Клиент часто обращается к бэку либо через обычные запросы, либо через веб-сокеты, и нам нужно каждый ответ от бэка отображать на стороне клиента.
Решение
Будем использовать RxJS в связке с Angular и нашу функцию, которая будет отдавать нам рандомный курс.
Функция будет нам отдавать через определённый промежуток времени курс, который будет сгенерирован рандомайзером:
// fake-currency.function.ts
import { interval } from ‘rxjs’; import { map } from ‘rxjs/operators’;
// Простой рандомайзер, который отдаёт случайное число в определённом диапазоне function getRandomByLimits(min: number, max: number) { return Math.round(Math.random() * (max – min) + min); }
Рандомные курсы валют у нас есть, теперь нам нужно реализовать компонент нашей страницы.
// my-component.component.ts
import { Component } from ‘@angular/core’; import { fakeCurrency } from ‘../fake-currency.function’;
@Component({ selector: ‘my-component’, templateUrl: ‘./my-component.component.html’, styleUrls: , }) export class MemoryLeakComponent { // свойство, которое мы будем использовать в шаблоне num: number;
Поздравляю!!! 🥳🥳🥳 У нас получилось вывести рандомный курс валюты на страницу!!!
Также поздравляю с получением первой утечки памяти!)
О какой утечке памяти идёт речь?
Дело в том, что если мы с этой страницы переместимся на другую, то работа функции fakeCurrency не остановится. Мы можем это проверить добавив логирование в подписку.
// my-component.component.ts
import { Component } from ‘@angular/core’; import { fakeCurrency } from ‘../fake-currency.function’;
@Component({ selector: ‘my-component’, templateUrl: ‘./my-component.component.html’, styleUrls: , }) export class MemoryLeakComponent { // свойство, которое мы будем использовать в шаблоне num: number;
Как мы видим — отображения курса на странице нет, но в консоль продолжают сыпаться логи. Если юзер продолжительное время будет переключаться между страницами, то такая утечка может вызвать дикие тормоза на его компьютере.
Как же нам избежать этого?
Конечно же отписаться!)
И способов отписки существует несколько.
Async Pipe
В данном случае этот способ наиболее предпочтительный. Немножко изменим наш код:
// my-component.component.ts
import { Component } from ‘@angular/core’; import { fakeCurrency } from ‘../fake-currency.function’; import { Observable } from ‘rxjs’; import { tap } from ‘rxjs/operators’;
@Component({ selector: ‘my-component’, templateUrl: ‘./my-component.component.html’, styleUrls: , }) export class MemoryLeakComponent { // свойство, которое мы будем использовать в шаблоне num$: Observable;
Если же нам значение в шаблоне не нужно, но при этом подписаться всё-таки нужно, то можно воспользоваться паттерном Destroy Subject и оператором отписки takeUntil:
// my-component.component.ts
import { Component } from ‘@angular/core’; import { fakeCurrency } from ‘../fake-currency.function’; import { takeUntil } from ‘rxjs/operators’;
@Component({ selector: ‘my-component’, templateUrl: ‘./my-component.component.html’, styleUrls: , }) export class MemoryLeakComponent implements OnDestroy { // свойство, которое мы будем использовать в шаблоне num: number; destroy$ = new Subject();
constructor() { // Случайный id const id = Math.floor(Math.random() * 100000); fakeCurrency(500) // Прокидываем оператор в поток и передаём ему другой поток .pipe(takeUntil(this.destroy$)) .subscribe((num) => { console.log(`ID: ${id}`, num); this.num = num; }); }
ngOnDestroy() { // завершаем поток // когда переданный поток завершается, то оператор takeUntil отписывается от текущего потока this.destroy$.next(); this.destroy$.complete(); } }Может есть решение красивее?
Конечно, есть!)
Не забываем, что Subject сущности — те же классы, от которых мы можем наследоваться. (Нагло беру пример из библиотеки taiga-ui)
Создаём сервис, который будет наследоваться от ReplaySubject и имплементировать OnDestroy интерфейс:
// destroy.service.ts import { Injectable, OnDestroy } from ‘@angular/core’; import { ReplaySubject } from ‘rxjs’;
Первый взгляд на CSS свойство object-view-box / Хабр
Эта статья — перевод оригинальной статьи Ahmad Shadeed “First Look At The CSS object-view-box Property”
Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.
Вступление
Я всегда хотел, чтобы CSS умел обрезать изображение и размещать его в любом нужном мне направлении. Это стало возможным благодаря использованию дополнительного элемента HTML в сочетании с различными свойствами CSS, которые я объясню позже.
В этой статье я познакомлю вас с новым CSS свойством object-view-box, предложенным Джейком Арчибальдом в начале этого года. Это позволяет нам обрезать или изменять размер HTML-элементов, таких как или
Проблема
В следующем примере у нас есть изображение, которое нужно обрезать. Обратите внимание, что нам нужна только определенная часть этого изображения.
В настоящее время мы можем решить это одним из следующих способов:
Использование и его обёртывание в дополнительный элемент.
Добавления изображения через background-image и изменение положения и размера.
Обертывание дополнительным элементом
Это распространенное решение этой проблемы, вот как это происходит:
Обернул изображение в другой элемент (в нашем случае
).
Добавил position: relative и overflow: hidden.
Добавил position: absolute для изображения и поиграл со значениями позиционирования и размера для достижения результата.
Это прекрасно работает, но что, если мы хотим применить вышеописанное к тегу ? Ну, это то, что касается object-view-box.
Представляем Object-View-Box
Я был заинтригован, когда увидел, что свойство object-view-box может быть добавлено в Chrome 104. Теперь оно доступно в Chrome canary.
Обращаясь к CSS спецификации:
Свойство object-view-box определяет «view box» над элементом, аналогичное атрибуту
Свойство принимает значение basic-shape-rect, оно может иметь значения inset() | rect() | . Для демонстрации в этой статье я сосредоточусь на использовании inset().
Вернемся к проблеме.
С помощью object-view-box мы сможем использовать inset для рисования прямоугольника с четырех сторон (сверху, справа, снизу, слева), а затем применить object-fit: cover, чтобы избежать искажений.
Как это вообще работает? Не волнуйтесь, я объясню все ниже.
Исходный размер изображения
Исходный размер — это ширина и высота изображения по умолчанию. Изображение, с которым я имею дело, имеет размер 1194 × 1194 пикселей.
img { aspect-ratio: 1; width: 300px; }
С помощью приведенного выше CSS размер отображаемого изображения будет 300 × 300 пикселей.
Наша цель — нарисовать прямоугольник на исходном изображении. Для этого мы будем использовать значение inset().
Применение значения inset
Значение inset() будет основано на исходной ширине и высоте изображения, в результате чего изображение будет обрезано. Это поможет нам в рисовании вложенного прямоугольника и управлении четырьмя краями, аналогично работе с полями или отступами.
Значение inset определяет прямоугольник. Мы можем управлять четырьмя ребрами точно так же, как мы имеем дело с полями или отступами. В следующем примере .card имеет inset в 20 пикселей со всех краев.
Вот и все. Разве это не круто? Возможность оформления изображения может быть очень полезна для адаптивного дизайна или даже для демонстрации различных частей изображения.
Увеличение или уменьшение масштаба
Мы можем использовать inset для увеличения или уменьшения изображения. Согласно моим тестам, переход или анимация не будут работать с object-view-box.
Мы также можем уменьшить масштаб с отрицательным значением вставки.
Представьте, как это будет полезно для возможности масштабирования изображения, не оборачивая его в дополнительный элемент.
Демо
Вот демо, которое вы можете протестировать в Chrome Canary уже сегодня. Обязательно включите флаг «Экспериментальные функции веб-платформы».
Ссылка на Демо
Заключение
Я так заинтригован другими потенциальными вариантами использования этой новой функции. Это был быстрый первый взгляд, и я вернусь позже с новыми исследованиями.
Про поддержку Certificate Transparency для национальных сертификатов / Хабр
Недавно мы рассказывали Хабру про поддержку в Яндекс Браузере тех сайтов, которые перешли на использование национальных TLS-сертификатов. Если вы пропустили, то рекомендуем прочитать пост, он содержит ответы на популярные вопросы.
Сегодня мы расскажем про следующий большой шаг в этом направлении — про обещанную поддержку публичных логов, созданных на базе открытого стандарта Certificate Transparency.
Краткая предыстория
Напомним, что с недавних пор российские организации и даже обычные вебмастеры всё чаще сталкиваются с невозможностью купить или продлить сертификат для сайта. Без действующего сертификата невозможно обеспечить безопасную передачу данных между сайтом и пользователями. Самоподписанные сертификаты — не выход по многим причинам: их нужно находить и устанавливать вручную, за ними нет никакого контроля, их используют злоумышленники для перехвата трафика.
Для решения проблемы был создан национальный удостоверяющий центр (НУЦ). Его сертификаты стали приниматься в Яндекс Браузере для сайтов из публичного белого списка. Это решение помогло не допустить ситуации, при которой пользователи лишились бы доступа к онлайн-сервисам или были бы вынуждены на свой страх и риск устанавливать сторонние корневые сертификаты на уровне всей системы.
На старте это решение работало так:
Владелец сайта запрашивает сертификат у НУЦ.
НУЦ проверяет права владельца и выпускает сертификат.
НУЦ добавляет домен сайта в публичный список по адресу gosuslugi.ru/tls.
Яндекс регулярно кэширует этот список на свои серверы, перепроверяет на ошибки, затем раздаёт всем копиям Яндекс Браузера.
Если сайт использует национальный сертификат, Яндекс Браузер проверяет, что его домен содержится в публичном списке, и только в этом случае устанавливает защищённое соединение. Если домена там нет, пользователь увидит стандартную ошибку про некорректный сертификат на сайте.
Быстрый для внедрения механизм. Но в долгосрочной перспективе непригодный. Причин несколько.
История знает немало случаев компрометации удостоверяющих центров. Например, в 2011-м злоумышленники взломали удостоверяющий центр Comodo, чтобы выпустить сертификаты для Gmail и других популярных сайтов. Чуть позже похожая проблема случилась у DigiNotar. Примерно тогда все в индустрии поняли, что время слепого доверия к подписям удостоверяющих центров прошло. Если сертификат был выпущен по ошибке, то у сообщества должен быть механизм узнать об этом. По этой причине белый список gosuslugi.ru/tls не подходит в качестве долгосрочного решения: в нём содержится лишь список доменов, но нет никакой информации обо всех выпущенных для них сертификатах. Это не прозрачно для сообщества.
Кроме того, у списка доменов есть и серьёзные технические ограничения. Любые изменения в списке нужно сначала скачать на наши серверы и проверить, затем каждая копия браузера должна скачать его у нас для локального хранения на устройстве. На всё это может уйти несколько дней, в течение которых сайт с новым сертификатом будет недоступен для пользователей. А ещё при существенном росте числа записей в списке возрастёт потребление трафика и памяти, что тоже может не обрадовать людей.
Мы не стали изобретать велосипед для этой задачи, а воспользовались уже принятым в индустрии открытым решением — стандартом Certificate Transparency.
Коротко про Certificate Transparency (CT)
Certificate Transparency предполагает создание публичных логов (CT-логов), в которых регистрируются выпускаемые сертификаты. Такие логи допускают только добавление записей. Если сертификат был выпущен по ошибке, то его можно только отозвать, но не удалить бесследно. Технологически это работает за счёт применения хэш-деревьев, как и в блокчейне. Верить этому не обязательно: CT-логи открыты для внешнего аудита. Это позволяет:
пользователям и браузерам — отслеживать выпуск всех сертификатов для домена и проверять их подлинность;
Когда удостоверяющий центр (УЦ) регистрирует новый сертификат в CT-логе, в ответ он получает подписанную метку (SCT) этого сертификата. Каждый лог предоставляет свою метку.
УЦ передаёт заказчику сертификат. Метки логов содержатся в сертификате.
Когда браузер устанавливает соединение с сайтом, доверие к сертификату определяется не только доверием к УЦ, который выписал сертификат, но и по меткам CT-логов. К примеру, браузер Chrome при установке HTTPS-соединения требует, чтобы у сертификата было как минимум две любые метки от доверенных CT-логов.
Вот детальная схема с сайта проекта Certificate Transparency:
Как видно из схемы, доступность CT-логов для внешнего аудита — это ключевое свойство решения. Можно не полагаться на хэш-деревья, мониторить любые изменения в логах и сопоставлять изменения из разных логов.
Про CT-логи для национальных сертификатов
Чтобы вся эта схема работала, нужны CT-логи, которые будут записывать национальные сертификаты, выдавать для них подписанные метки. Они должны быть открыты для мониторинга. Должны иметь стабильный аптайм, хотя бы 99%. Один из примеров полного списка требований к логам можно найти на гитхабе. Там же есть готовый код, который позволяет поднять свой лог любому. На текущий момент ни один уже существующий CT-лог не принимает национальные сертификаты. Их поддержка — это вопрос договорённостей между владельцами логов и удостоверяющим центром. Будем надеяться, что в будущем такие договорённости появятся.
Яндекс разрабатывает Браузер, поэтому мы заинтересованы в появлении CT-логов, которые помогут защитить наших пользователей от компрометации сертификатов. Разработчики любого браузера в этом заинтересованы, поэтому, к примеру, компании Google и Apple создали и поддерживают свои логи. Мы пошли этим же путём и создали свой лог, воспользовавшись уже существующим открытым решением. Наш лог уже запущен и работает, НУЦ уже вносит в него новые сертификаты.
Свои CT-логи также запустили VK и «Минцифры России». Технические адреса всех трёх логов с поддержкой национальных сертификатов можно найти по адресу https://browser-resources.s3.yandex.net/ctlog/ctlog.json. Этих данных уже достаточно для того, чтобы сообщество смогло читать логи, искать в них конкретные сайты и вести аудит всех изменений.
Со своей стороны мы также создали форму в Яндекс Вебмастере, которая упрощает получение информации обо всех выпущенных национальных сертификатах для конкретного сайта. Сейчас поиск идёт по CT-логу Яндекса, а также по белому списку доменов. В ближайшее время планируем начать искать и по другим CT-логам.
Про поддержку CT-логов в Яндекс Браузере
Мы уже добавили поддержку новых CT-логов в Яндекс Браузер для Windows, macOS, Linux, Android и iOS. Теперь мы прекращаем обновлять список доменов с gosuslugi.ru/tls, замораживаем состав этого списка на наших серверах. Примерно год он продолжит использоваться в Браузере для тех сертификатов, которые были выпущены по старой схеме (пока не истечёт их срок действия).
Для новых национальных сертификатов, выпущенных с поддержкой CT-логов, сейчас действует простое правило: Браузер принимает сертификат, только если для него есть подписанная метка CT-лога Яндекса. Это стартовое решение.
Дальше мы планируем запустить мониторинг сторонних CT-логов, чтобы убедиться в их работоспособности и стабильности. После этого начнём учитывать метки любых других логов национальных сертификатов. Мы открыты для поддержки новых CT-логов в Яндекс Браузере: достаточно соответствовать нашим техническим требованиям. При этом политика ужесточится: Браузер будет требовать как минимум две метки, причём одну из них — обязательно от лога Яндекса.
Кроме того, мы готовимся выложить в опенсорс ту часть нашего Браузера, которая отвечает за поддержку сайтов и CT-логов с национальными сертификатами. Это может быть полезно не только со стороны аудита, но и тем командам, которые захотят добавить такую поддержку себе.
На всякий случай скажу, что нововведение никак не влияет на логику работы всех остальных (не национальных) сертификатов и их CT-логов. Для них мы, как и прежде, полагаемся на политики и списки из проекта Chromium.
Заключение
Поддержка сайтов с национальными сертификатами поможет не допустить ситуации, когда пользователи лишатся доступа к онлайн-сервисам или будут вынуждены устанавливать сомнительные корневые сертификаты на уровне всей системы. Прозрачность и открытость этого решения для пользователей, владельцев сайтов и разработчиков браузеров должны обеспечиваться строгим соблюдением правил Certificate Transparency. Хочется верить, что в будущем мы увидим не только новые CT-логи, но и новые инструменты для их удобного мониторинга. В этом нам нужна помощь сообщества.
Идеальных решений не бывает, поэтому повторю свой призыв из предыдущего поста: если вы знаете, как сделать лучше, — приходите, советуйте. Хотите покритиковать решения — критикуйте и предлагайте улучшения. Спасибо.