Читать в телеге. Когда-то там были посты не только от меня.
USB-C
Когда пытался понять отличия Thunderbolt 3 от USB 3.X Gen Y Type C, то узнал, что за доставку питания отвечает стандарт USB Power Delivery. И этот стандарт настолько хорош, что можно заряжать телефон от ноутбучной зарядки, если оба этот стандарт поддерживают. Телефон с разъемом USB-C, очевидно, это делает, а на адаптере зарядника от ноута должен быть вариант с 5V/2A. В нормальной технике будет еще защита от неправильных режимов, так что беспокоиться не о чем. Я проверил с рабочим ноутом и телефоном — действительно, работает.
А само отличие Thunderbolt 3 от USB-C в основном заключается в скорости передачи данных и лицензировании (и, как следствие, стоимости). Еще не всякий разъем/кабель USB поддерживает туннелирование сигнала с монитора. Решить это должен USB4, который полностью совместим с Thunderbolt 3 и имеет только один тип разъема, USB-С.
Вообще классно, что через один провод можно передавать любые данные, в т.ч. графику и сеть, а еще и питание. Можно всю периферию и питание цеплять к док-станции, а “тушку” менять — переключаться между рабочим и домашним компом, например. Или вообще телефон воткнуть и работать за ним, как за компом.
Удаление фона
Нашел сайтик, на котором можно без регистрации, смс и прочей фигни удалить фон с изображения, особенно если есть повышенная криворукость, как у меня. Не очень сложный фон удаляется автоматически, а мелкие детали можно подредактировать самостоятельно.
Мне пригодилось для создания эмоджи-реакций в слаке (потому что имеющийся набор очень скуден, особенно после миллиардов стикеров в телеге).
Scala JS
Портировать существующий проект с обработкой заявок на Scala JS оказалось довольно легко (ну, с учетом того, что там нет зависимостей): берем туториал, выполняем немного пунктов из него, меняем немного IO — заменяем чтение из файла на чтение из textarea, аналогично меняем main, добавляем простенькую html-ку и готово. Единственное, обо что споткнулся на первой итерации — нужно было сделать явное преобразование asInstanceOf[TextArea]
после document.getElementById
, но это логично и мелочевка.
На простых примерах работало отлично, на основном примере — подвисло аж на 45 секунд. Браузер трижды предлагал убить скрипт. Размер сборки, кстати — 1.2Мб. Вроде логично: собирал-то я в неоптимизированном варианте, через sbt fastLinkJS
. Для оптимизированной сборки нужно выполнить sbt fullLinkJS
. Результат оптимизации не очень впечатлил — 35 секунд, но зато размер сборки всего 253Кб.
Для сравнения — сборка и запуск с нуля обычной версии (без JS) через sbt clean
+ sbt run
выполнится за 26 секунд, из которых чуть меньше 3 секунд будет само выполнение. Если просто запускать jar, то общее время будет 4 секунды. Тоже многовато, но с учетом числа копирований ради иммутабельности — приемлемо. Может, когда будет настроение, попробую оптимизировать.
В официальных доках пишут, что производительность Scala JS проседает в 1-3 раза по сравнению с обычным кодом. Т.е. мои 11+ раз выглядят весьма тухло.
Я вдумчиво посмотрел в свой код аж пять минут и решил поменять List
на Vector
, потому что у меня есть операции добавления в конец списка, хотя выбор между двумя этими СД довольно трудный (на всякий случай: скаловский Vector
— это хитрый trie, а не динамический массив, как можно подумать по названию). В обычном коде стало только хуже — время возросло до 5 секунд, т.е. стало больше на треть. А вот на фронте снизилось до 6 секунд. Все равно долго, но гораздо лучше, чем 35. И, получается, медленнее обычного кода всего в полтора раза, что укладывается в диапазон 1-3 из официальных бенчмарков. Ускорение спишу на разницу работы с памятью в JVM и JS, подробности оставлю как упражнение читателю :)
Что в итоге получилось — можно посмотреть на гитхабе, рекомендую сразу на первый дифф смотреть.
P.S. по последним постам может показаться, что я мазохист и люблю копаться во фронте, но нет — просто так получилось, что хотелось попробовать, а все руки не доходили (яжбэкендер).
Term vs match query
Term query ищет документы по точному совпадению запроса с токенами в индексе, а match query — по совпадению токенизированого запроса. Т.е. к значению в запросе применяются те же преобразования, что и при индексации, или даже какой-то другой анализатор. Дополнительная плюшка match query еще в том, что он, как и term query, универсален, и позволяет искать не только по текстовым полям, но и по keyword/числовым/датам и т.п.
Возникает вопрос: а зачем тогда вообще нужен term query? Для текстового поля логично использовать тот же анализатор (разве что кроме каких-то очень экзотических случаев или дебага). Для прочих типов полей — разница будет крошечной: потратится только немного времени на определение анализатора, который надо применить (чтобы выяснить, что его нет). Даже разработчики говорят — используйте match для единообразия. И в старом гайде пишут примерно то же самое. Я нашел только один аргумент — скоринг: в term query игнорируется IDF. Но это подходит под “экзотический случай”.
Альтернативный вариант — использовать для всего, кроме текста terms query. Это ясно дает понять, что ищем в индексе “как есть”, намекая на то, что это поле в индексе тоже хранится “как есть”.
OWASP CheatSheet
Случайно наткнулся на сборник шпаргалок по OWASP.
Если кто вдруг не знает, OWASP (Open Web Application Security Project) — это онлайн-сообщество, которое рожает бесплатные советы про то, как безопасно писать преобразователи sql в json веб-приложения. Самое известное и старое творчество — OWASP TOP 10.
Возможно, в каких-то компаниях от круд-формошлепов старших разработчиков будут требовать знания этих рекомендаций. Правда есть нюанс — большая часть проблем, описанных в них, покрывается нормальными библиотеками/фреймворками и правилом “не реализуй безопасность самостоятельно”. А если и накосячить в этом — проверять вас скорее всего будет такой же разработчик, как и вы, потому что большинству компаний насрать на ИБ в целом, а на безопасный процесс разработки — тем более. Даже если не насрать — то должен быть как минимум специальный человек, ответственный за это, который в том числе отвечает за такие вещи, как построение модели нарушителя, обучение специалистов, организацию проверки кода на соответствие требованиям ИБ.
А сами шпаргалки среднего качества и довольно разнородны. Но пригодятся для того, чтобы проверить себя на совсем очевидные ляпы или начать погружаться в тему.
GitHub Actions
Решил попробовать на прошлой неделе, после закрытия долга с задачей про заявки.
Думаю, найду сейчас какой-нибудь туториал, пройду его и буду корпеть над составлением yaml для корректной работы.
Оказалось, что для того, чтобы подключить прогон тестов нужно просто зайти на вкладку с Actions, нажать одну кнопку для подключения
потом при желании подредактировать файлик (я заменил java 1.8 на 11) и жахнуть коммит.
Т.е. три клика и все. Я в шоке.
What a great time to be alive!
Hexdiff
Простенькая утилита для сравнения двух файлов в hex.
hexdiff 1.bin 2.bin
Разницу подсвечивает не очень хорошо, но удобно посмотреть, где она начинается. По крайней мере удобнее, чем открыть 2 параллельных hex-редактора или делать diff
по двум выводам hexdump
. Мне это пригодилось, когда нужно было понять, почему один из двух “одинаковых” URL’ов работал, а другой нет: проверил наличие невидимых символов, которые там действительно оказались.
Обнаружил случайно: смотрел на diff
, который из полезного смог только сказать, что две строки разные. Подумал — как можно сделать бинарный diff
? Ну, наверно, diff -b
или какой-нибудь hexdiff
. И на вторую команду система предложила sudo apt install hexdiff
. Люблю такой UX :)
Миграция approle между двумя Vault-хранилищами
Секреты смигрировать много ума не надо — это практически key-value хранилище. А вот с ролями приложений (которые представляют собой пару role_id
+ secret_id
и по сути мало отличаются от логина-пароля) сложнее. Нельзя просто так взять и записать
vault write /auth/approle/role/ROLE_NAME role_id=SOME_UUID secret_id=WANTED_UUID
— параметр secret_id
будет проигнорирован. Т.е.
vault read /auth/approle/role/ROLE_NAME/role-id
вернет нужный role_id
, а вот
vault list /auth/approle/role/ROLE_NAME/secret-id
вернет ошибку, т.к. secret_id
не был создан. Причем нужный secret_id
не получится прописать, только сгенерировать новый через
vault write /auth/approle/role/ROLE_NAME/secret-id
Но есть способ это обойти — создать custom-secret-id
.
write /auth/approle/role/ROLE_NAME/custom-secret-id secret_id=WANTED_UUID
Для приложения все будет работать “как раньше”, роль/секрет ему можно не менять.
Вообще говоря, это не очень хороший подход, правильнее и безопаснее будет сгенерировать новый secret_id
и прокидывать его приложению вместо этой чехарды. Но прокидывание роли контролирует обычно CI/CD (иначе зачем нужна вообще эта безопасность), а там две роли сразу (старую и новую) для всех фиче-веток не прокинешь. Дешевле на время переходного периода мигрировать старую роль приложения, а после окончания миграции — удалить ее.
Основы LLVM
Хорошая статья, где доступно объяснены основы LLVM.
ConditionalOnMissingBean
Неприятным сюрпризом оказалось поведение аннотации @ConditionalOnMissingBean
. Казалось бы, название говорит о том, что бин добавляется в контекст только тогда, когда нет бина с таким же классом (или с классом, который указан в параметре аннотации).
Т.е. если есть
@Configuration
class MainConfiguration
@ConditionalOnMissingBean(MainConfiguration::class)
@Configuration
class TestConfiguration
то TestConfiguration
загрузится только в случае, если в контексте нет MainConfiguration
. Однако если переименовать TestConfiguration
→ ConfigurationTest
, то будут загружены обе конфигурации. WTF, переименование класса ломает функциональность?
На самом деле, все очень просто: загрузка бинов в контекст по умолчанию производится в лексикографическом порядке, а @ConditionalOnMissingBean
смотрит только на контекст, загруженный на данный момент, и во втором случае бин MainConfiguration
действительно пока еще не создан при загрузке ConfigurationTest
. Конечно, документация явно говорит об этом и предлагает использовать @ConditionalOnMissingBean
только для автоконфигураций. Решить эту проблему можно явной аннотацией @Order
или профилями, но сделать это можно только для контролируемых классов. И ради тестов такое делать — перебор. Но ни через какой @Conditional
это сделать не получится, т.к. он не влияет на порядок загрузки бинов.