Оптимизация раундтипов хорошая возможность для улучшения. Это очень важно для скорости запуска и бесшовных переподключений, но внедрение нулевого раундтипа приведет к сложностям в виде защиты от reply атак, что может быть критично
Сложности в виде защиты от reply атак возникают не из-за нулевого рандтрипа а из-за отсуствия долговременного хранения этого счетчика (которого вы передаете с каждым сообщением) на сервере и на клиенте. Вот у вашего протокола уже есть такая защита но внутри установленного соединения. А что происходит если у пользователя на некоторое время отключается интернет? Если соединение разрывавется (например из-за таймаута) то повторная попытка установить новое соединение с повторной отправкой сообщения как раз и приведет к replay багам. И решением этой проблемы как раз и будет долговременное хранение как на сервере так и на клиенте либо этого счетчика либо вообще контекст всего соединения
Оптимизация раундтипов хорошая возможность для улучшения. Это очень важно для скорости запуска и бесшовных переподключений, но внедрение нулевого раундтипа приведет к сложностям в виде защиты от reply атак, что может быть критично. К тому же я описывал механизм востановления сессии, который не успел реализовать, который должен значительно сократить время повторного подключения.
А почему бы просто не установить на сервере таймаут соединения в бесконечность (если там есть этот таймаут) а дальше на клиенте просто сохранять на диск контекст соединения и таким образом никогда не разрывать соединение? То есть даже если юзер выходит из приложения то при следующем запуске приложение просто загрузит контекст соединения из диска и просто продолжит отправлять и получать данные по этому уже установленному соединению.
который должен значительно сократить время повторного подключения
Таким образом решается задача возобновления сессий - раз соединение практически никогда не будет разрываться то нет такой необходимости пытаться оптимизировать установку нового соединения и время повторного подключения
А запекание ephemeral ключа приведет к проблемам с PFS, а в данном случае для меня безопасность приоритетнее скорости
Если мы запекаем в бинарнике приложения ephemeral ключ то на сервере нужно будет хранить приватную часть этого ключа не все время а только до выхода новой версии приложения (а если приложение активно развивается то выход минорной версии происходит обычно не реже чем раз в месяц). Это конечно не идеальный perfect-forward-security но уже лучше чем в протоколе TLS 1.2 когда часто использовалась схема с использованием RSA и публичного ключа из сертификата для дальнейшей передачи симметричного ключа шифрования - там действительно утечка приватного ключа сертификата позволит расшифровать все перехваченные ранее сообщения за все время действия сертификата (год и больше) и поэтому в TLS 1.3 полностью отказались от этой варианта и оставили только схемы c согласованием ключа через DHE или ECDHE (diffie-hellman и аналог на эллиптических кривых).
Вообще "идеального" PFS не сущестсвует так как это всегда trade-off между удобством для пользователя (отсутствие необходимости делать полноценный хендшейк на каждый чих) и безопасностью. Вот скажем ради безопасности вы решили что зашивать ephemeral ключ в бинарнике приложения (с необходимостью хранить приватную часть на сервере до выхода новой версии приложения) это небезопасно с точки зрения PFS поэтому вы решаете генерировать приватную часть и согласовывать новый ключ с каждым соединением. Но проблема в том что если вы решаете организовать общение клиента с сервером не через установку нового соединения на каждый запрос к серверу а через установку одного долгоживущего соединения (с обменом сообщений внутри этого соединения) то это уже в какой-то степени жертвование безопасностью и уход от PFS. А если вы решаете добавить механизм возобновления сессии (ну или же мое предложение в виде сохранения контекста соединения на диск) то это еще больший уход и пожертвование perfect-forward-security ради увеличения эффективности и удобства.
В общем для себя я решил что проблемы PFS несколько преувеличены и по сути являются сомнительной (на мой взгляд) попыткой сгладить потенциальный взлом сервера (а иначе как этот долговременный приватный ключ может утечь?) и я такой себе дальше думаю - если уж произошел такой взлом с утечкой приватного ключа то мне кажется что возможность расшифровать перехваченный ранее обмен сообщениями это будет не самая большая проблема в сравнении с потенциальным доступом (в случае взлома) ко всем данным пользователей (то есть ко всему что этот сервер может хранить или обрабатывать)
// 3. Подписываем свой эфемерный публичный ключ своим долгосрочным приватным ключом.
// Это доказательство для клиента, что мы — именно тот сервер, за кого себя выдаем.
Мне кажется или здесь зарыта дыра в вашем алгоритме? Что мешает "человеку посередине" перехватить это сообщение а потом выдать себя за сервер просто отправив эту копию? Приватного ключа у него нет но он ему и не нужен так как он просто отправит тот же эфемерный публичный ключ который был сформирован сервером и прикрепит ту же самую подпись и таким образом успешно выдаст себя за сервера. А чтобы предотвратить такую возможность нужно чтобы сервер подписал/зашифровал своим приватным ключом не свой эфемерный ключ а некое рандомное число которое клиент сгенерирует и пришлет серверу в своем первом сообщении (и дальше клиент получив ответ расшифрует число публичным ключом сервера и проверит совпадает ли оно с тем числом которое клиент отправил)
В Eppie каждое клиентское приложение это нода в p2p-сети. Каждый пользователь автоматически участвует в хранении и транспортировке данных.
Очень интересно узнать как вы собираетесь обеспечивать этот инвариант. Например что помешает пользователю форкнуть исходники этой ноды и изменить сорцы так чтобы можно было отправить письмо без участия в хранении и транспортировке? В децентрализированных приложениях основанных на криптовалютах такое же почтовое приложение будет работать таким образом что отправка письма потребует оплаты этой криптовалютой которую можно заработать как раз и обеспечивая работу этой сети. Получается устойчивая замкнутая экосистема. А как будет обеспечиваться устойчивость работы в вашей сети непонятно. Можно предположить что для отправки письма пользователю нужно будет пропустить через себя н мегабайт трафика и продержать данные н дней но только непонятно как вы собираетесь обеспечить это на уровне алгоритма консенсуса то есть чтобы даже измененные исходники ноды со стороны юзера не позволили ему обмануть эти правила (то есть чтобы другие ноды не принимали отправленное письмо)
Есть несколько мыслей по оптимизации вашей схемы. У вас получается что клиент сможет отправить первый байты полезных данных (например имя api endpoint-a) только после полного раундтрипа? А что насчет сервера? Если сервер вычислил ключ шифрования уже на первом upd-пакете от клиента то значит он имеет теоретическую возможность отправить зашифрованные данные тут же не дожидаясь запроса от клиента, правильно? То есть сервер генерирует "ephemeral server public key" записывает его в udp-пакет который предназначен клиенту и тут же внутри этого же udp пакета следом записывает уже зашифрованные данные и тогда клиент получив кей-шару от сервера вычисляет симметрический ключ шифрования и дальше сможет расшифровать уже полученные данные от сервера.
В общем это довольно интересная техника которая позволяет избежать лишнего сетевого раундтрипа в случае если серверу нужно безусловно отправить клиенту какие-то данные (например свою версию api-ручек чтобы клиент увидев что у него старая версия бинарника которая не совместима с ними попросил юзера обновить приложение).
И дальше появляются мысли как можно еще сильнее зарыться в оптимизацию сетевых раундтрипов - если имя api-ручки сервера не является таким уж и секретным то клиент может включить это имя (в незашифрованном виде) в тот свой первый udp-пакет и тогда сервер сможет выполнить полученный запрос и отправить уже зашифрованные данные клиенту уже в том первом udp-пакете который клиент получит от сервера. И кстати тут же напрашивается идея что можно сэкономить драгоценное место в upd-пакете (1472 байт) и закодировать имя api-ручки в номере udp-порта - то есть сервер будет слушать не один порт а например тысячу сетевых портов по одному на каждую api-ручку, правда для этого нужно чтобы это зашифрованное соединение не было привязано не только к ip-аддресу и порту клиента (что в принципе и так является хорошей практикой чтобы например избежать установки нового соединения при смене клиентом wi-fi сети на мобильную сеть и наоборот) но и к порту сервера.
Ну и наконец появляется такая идея что этот "ephemeral server public key" можно вообще включить в бинарник приложения и тогда клиент сможет вычислить ключ для симметричного шифрования вообще без общения с сервером и тогда он сможет в своем первом upd-пакете (включив сначала свой "ephemeral public key" ) отправить серверу в зашифрованном виде как имя api-endpoint-а так и соответствующие данные.
Ещё лет 5 назад на собеседованиях с backend-разработчиками был популярен вопрос: как переименовать колонку в высоконагруженной таблице (возможны вариации: как сделать любой другой alter table)? Ответ мог быть примерно следующий:
Зачем такие сложности с создаем новой колонки и постепенной миграцией? Почему бы просто не задать всем колонкам уникальное имя которое никогда не захочется поменять (случайный айдишник) а уже в коде хранить соответствие имя -> id при работе с этой бд. Тогда переименование колонки сведется к простейшей операции замены одного имени на другое в коде вообще без каких-либо затрат со стороны бд
вы только представляете а кто-то уже так делает и зарабатывает миллионы — вот один комментарий на HN который недавно набрал популярность в твиттере https://news.ycombinator.com/item?id=27454589
I currently have 10 fully remote engineering jobs. The bar is so low, oversight is non-existent, and everyone is so forgiving for under performance I can coast about 4-8 weeks before a given job fires me. Currently on a $1.5M run-rate for comp this year. And the interviewing process is so much faster today, companies are desperate, it takes me 2-3hrs of total effort to land a new job with thousands to chose from.
Насчет ангуляра не скажу но с реактом я применяю другой подход — просто пишу функции инлайном и таким образом вообще избавляюсь от диллемы именования функций и заодно очень просто и круто решается вопрос сематического соответствия кнопки и обработчика — вот есть кнопка а значит обработчик функции будет на этой кнопке (или наоборот — если видишь обработчик кнопки то и кнопка будет рядом)
Тут можно сказать мол "да ты захламляешь шаблон как такое вообще читать и поддерживать когда мол верстка в перемешку с логикой?" на что я отвечу "а code folding (сворачивание) в редакторе кода для кого придумали?" Если хотите видеть/редактировать только верстку то можно легко свернуть весь код обработчиков и увидеть чистый шаблон. И наоборот — если хотите посмотреть что делает какая-то кнопка то перемещаем мышку влево на пару сантиметров и кликаем-раскрываем обработчик кнопки.
Это намного удобнее чем переходить в отдельный файл или скроллить экран вверх-вниз (когда выносим обработчик функции в другое место). Это также удобно в поддержке — если допустим у нас поменялся дизайн/ux/логика и поменялась верстка в результате которой нужно удалить какой-то блок вместе с кнопкой то я удаляю нужный кусок верстки и вместе с этим удалится и обработчик этой кнопки поскольку он записан инлайном — мне не нужно беспокоится а не удалил/почистил ли я различные обработчики в другом файле или других местах по коду
И то же самое с добавлением какой-то фичи — я пишу кнопку и тут же пишу обработчик этой кнопки рядом и мне не нужно думать над тем куда вынести этот обработчик а главное — какое имя придумать этому обработчику. И также это удобно при ревью кода — когда добавляется какая-то фича то в git diff видишь изменения только в одном месте а не размазаны по кусочкам на кучу файлов/мест (верстка в одном месте, обработчик в другом месте и т.д)
Как вебсокеты могут быть более тяжёлой штукой чем http-запросы? Вы понимаете как работает http? HTTP-запрос это точно такое же тсп-соединение как и вебсокеты и ничем не легче по "тяжести". И если интернет-соединение нестабильное то http-запрос будет точно также обрываться с ошибкой как и вебсокет-соединение (и соотвественно нужно делать повторный запрос/установку соединения), в этом плане я вообще не вижу никакой разницы.
А вот во многих моментах http намного хуже и имеет больший оверхед.
Во-первых если не использовать keep-alive то браузер будет создавать тсп-соединение для каждого http-запроса и здесь http выходит намного тяжелее.
Во-вторых передача статуса и хедеров в текстовый формате для каждого запроса в итоге имеет больший размер и оверхед и это особенно видно при передаче небольших порций данных (с вебсокетами это может быть десяток байт когда же с http запрос будет занимать от сотни байт)
В-третих с http у браузеров есть ограничение на количество одновременных запросов (чего нет с вебсокетами).
В-четвертых проблема идемпотентности с http решается намного сложнее чем с вебсокетами (с http порядок запросов не гарантируются и они на сервер могут прийти в другом порядке чем были отправлены из клиента, когда же с вебсокетами у нас есть строгий порядок сообщений)
Ну и наконец вебсокеты это очень простая спецификация (по сути хедер с размером сообщения поверх тсп-протокола) и написать парсер вебсокетов можно за пару часов (формат фрейма объясняют даже для новичков) в то время как написать парсер http (не говоря уже про http2) это приключение на месяц учитываю кучу разнообразных нюансов самого текстового http формата, так и специфику корректно обработки различных хедеров (в частности keep-alive)
А все эти REST/RPC PUT/DELETE/OPTIONS/GET/POST и прочее это просто уровни абстракции придуманные самими разработчиками для удобства. Если писать большое приложение на WebSocket-ах вы придумаете свои такие же.
Нет, такого беспорядка и дилеммы выбора как с http не будет, зачем? Зачем нужны эти имена методов и статусы кодов ошибок если можно просто передать скажем объект {action: "createProject", params: [...]} и вернуть объект {error: "..." | null, data: "..." | null} и вебсокет-обработчик на сервере просто вызовет нужную функцию и уже в самой функции будет происходит вся необходимая обработка — будь то получение (GET) или обновление данных (POST, PUT) или удаление (DELETE) или когда все эти действия происходят одновременно вместе с if-ами и циклами для батч-обработки каких-то данных (что просто не имеет соответствия "семантическим" методам в случае http). То есть исходная задача — вызывать какую-то процедуру на сервере и передать ей нужные параметры чтобы она обработала "запрос" и получила/обновила нужные данные в бд и вернула ответ назад с вебсокетами решается на порядок проще чем с http (где вызов нужной процедуры почему-то додумались делать через ручное придумывание и конфигурацию имен для роутов с маппингом на path/query http-запроса)
А объясните пожалуйста зачем какому-то условному новичку (который примерно понимает как работает веб, как ходят запросы и что такое tcp) использовать все эти express/koa/fastify/nest/etc nodejs-фреймворки? Вот стоит перед новичком задача — со стороны клиента получить какие-то объекты с данными из бд или отправить объект с метаданными (для того чтобы изменить данные в бд). Новичок и так будет знать что вся информация в вебе ходит поверх тсп-протокола (который уже обеспечивает целостность данных при передаче), но чистый тсп в браузерах недоступен и браузеры предлагают два варианта — http или websockets
И какой самый простой способ решить эту задачу? Зачем этому новичку следует выбирать всякие express/fastify/nest-фреймворки со всякими непонятными middleware/сontrollers поверх http/rest подходов со всякими тонкостями/нюансами/дилеммами выбора методов (post vs put), способа передачи информации (path/query/headers/body) и выбора кодов для ошибок, зачем все это если можно просто установить вебсокет-соединение и просто пересылать нужные объекты/json от клиента к серверу и обратно. То есть исходная задача (установка транспорта между клиентом и сервером чтобы пересылать данные) с вебсокетами решается на порядок проще без всяких фреймворков чем c этими http/rest-фреймворками вроде express/fastify/nest и со стороны новичка становится совершенно непонятно зачем выбирать тот же fastify вместо вебсокетов
Вот он — триумф вертикального масштабирования! In-memory база данных поверх такого процессора способна будет обрабатывать десятки, сотни миллионов или возможно даже миллиард serializable транзакций в секунду и способна заменить сотни/тысячи серверов (и тот огромный оверхед при реализации распределенных транзакций в случае горизонтального масштабирования) определенно точно найдет своего покупателя.
Интересно как в этом процессоре работает механизм cache-coherence который обеспечивает CAS ("compare-and-swap") и другие атомарные обновления памяти? Cache-coherence это другими словами реализация атомарного бродкаста и поскольку это и так самая сложная часть при проектировании современных процессоров то здесь при 850 тысяч ядрер и возможном отказе этих ядер по отдельности проектировщики должны были выйти на уровень распределенных систем (интересно они реализовали paxos в железе?)
а чем плох вариант просто вызывать в цикле функцию вроде parseNextHttpRequest(buf) с передачей буфера и получением словаря http-хедеров и значений (а также статуса и тела). Вполне себе органично вписывается в тот пример кода выше где вместо вызова do_use_fd(..) будет вызов parseNextHttpRequest а потом уже обработка самого http-запроса вызовом еще одной функции
А чем плох вариант просто пробросить системный вызов epoll в линуксе? Linux уже предоставляет возможности асинхронного io виде синхронного вызова epoll_wait в бесконечном цикле. Вот я нашел такую библиотеку (https://github.com/chopins/php-epoll), какие есть недостатки у такого подхода ?
Понятно что это только работа с голым tcp но ведь можно подключить какую-нибудь библиотеку которая будет парсить из tcp-потока http-запросы и таким образом получим на php асинхронный http-сервер который будет не хуже чем в NodeJS (и также это избавляет от необходимости использовать Apache или Nginx перед php)
Сколько раз ревьюеру (который видит этот кусок кода первый раз) нужно будет промотать глазами чтобы понять что происходит в коде?
1) видим строчку COMPONENT_MAP[user.type] — ага, нужно найти взглядом эту переменную из внешнего скоупа и понять что там хранится,
2) видим строчку рендера какого-то компонента <Component name={user.name} /> — опять нужно найти взглядом переменную из внешнего скоупа и понять что записано в переменную Component.
3-6) и наконец по значениям-ссылкам на три компонента которые были записаны в COMPONENT_MAP мы теперь должны найти каждый компонент и понять что он рендерит
Сколько раз в этой версии разработчику нужно будет мотать глазами чтобы понять что происходит в коде? Не проще ли становится код когда вообще не нужно мотать глазами? Не является ли вторая версия более "декларативной" ?
А что вы скажете насчет веб-компонентов? Разве они были придуманы не для того чтобы избавиться от проблемы ограниченного набора и неоднозначности "семантических" html-тегов? Почему вместо разнообразных <div>, <span>, <header>, <footer>, <main>, <section>, <article>, <h1>, <ul>, etc нельзя использовать кастомные веб-компоненты которые будут лучше подходить по семантике и функционалу к конкретной ситуации и лучше соответствовать дизайн-системе и семантически-функциональной структуре сайта/веб-приложения?
А кто-нибудь знает почему не взлетела идея с FPGA? Ведь тогда каждая программа могла бы сама определять наиболее эффективные для своего исполнения инструкции, кеши и пайплайны. Или, например, с++/rust компилятор после всевозможных оптимизаций мог бы компилировать программу не в набор ассемблерных инструкций а сразу в схему транзисторов и связей которая будет максимально эффективна для конкретной программы
А почему такой скромный заголовок? Судя по тому что в википедии написано что температура ядра солнца всего лишь 15 млн градусов то достижение в 100 млн градусов надо было назвать как-нибудь "Ученые создали и двадцать секунд удерживали самое горячее вещество в солнечной системе, в 7 раз горячее солнца!!!"
А вообще цифры конечно впечатляют и сразу возникает такой вопрос почему так несимметрично — значит если нагревать так и до 100 млн можно а если охлаждать то всего лишь до -273 градусов и больше нельзя (так как абсолютный ноль), разница аж целых 6 порядков!
Ну вот я использую голый реакт и получается даже более удобно чем с mobx, вот пример — https://codesandbox.io/s/competent-engelbart-cltwg. Нет никакой магии геттеров-сеттеров или прокси-объектов, просто вызываем функцию актуализации когда нужно актуализировать view с состоянием. Есть и недостаток в виде менее эффективного обновления (реакт будет делать diff всего приложения при изменении состояния). Но тормоза появляются лишь на больших приложениях (> 10к дом-нод) что встречается нечасто и для 99% mvp-приложений можно обойтись без mobx (и потом можно легко подключить его только в случае появления тормозов diff-механизма реакта).
Сравнение некорректное. Java это язык а нода это рантайм (конкретная реализация взаимодействия с IO операционной системы). И скорость ноды и этой хваленый event loop и т.д — это все заслуга системного IO операционной системы — например на линуксе это системный вызов epoll_wait(..) — который позволяет одним потоком обслуживать много сокетов. Это значит что аналогичную эффективность NodeJS (скорости работы с io и обслуживание одним потоком многих tcp/http запросов) можно получить на всех популярных языках — не только на java но и например на php — достаточно всего лишь прокинуть системные вызовы epoll_wait/epoll_create/… в интерпретатор или компилятор языка.
Как известно, в Node.js реализовано однопоточная событийно-ориентированная модель. Это отлично подходит для большого количества запросов, но совершенно не годится для распараллеливания вычислений или реализации сложной бизнес-логики.
Вы серьезно? Тогда такой вопрос — как вы собираетесь поддерживать атомарность при выполнении сложной бизнес-логики? Многопоточная модель это прямой путь к race-conditions и неконсистетности данных. Вы знаете что в базах данных (включая postgres, mysql) по умолчанию не включен serializable уровень изоляции и поэтому любую бэкенд-логику которая работает с бд отдельными запросами нужно врапить в эти транзкции (от начала и до конца) иначе рано или поздно у вас появятся race-conditions и неконсистентность данных (https://www.youtube.com/watch?v=5ZjhNTM8XU8)?
А теперь вопрос — какой тип бд позволяет обрабатывать транзакции с любой логикой (запросы к различным таблицам, включая условия и циклы) c seriazlizable-уровнем изоляции со скоростью > 100к транзакций в секунду? Вы не поверите но это такие базы данных которые выполняют все транзакции в одном потоке и вполне могут быть написаны на Node.js. Яркий пример такой бд — это Tarantool (https://www.youtube.com/watch?v=yrTF3qH8ey8)
А что касается скорости одного потока javascript — то он вполне находится на уровне с++ (если не относиться наплевательски на советы по производительности) — вот доклад где сравнивается с++ и js — https://www.youtube.com/watch?v=UJPdhx5zTaw и js всего лишь на 17% медленнее чем с++ с O3-флагом оптимизации (и это было еще в 2012 году)
Сложности в виде защиты от reply атак возникают не из-за нулевого рандтрипа а из-за отсуствия долговременного хранения этого счетчика (которого вы передаете с каждым сообщением) на сервере и на клиенте. Вот у вашего протокола уже есть такая защита но внутри установленного соединения. А что происходит если у пользователя на некоторое время отключается интернет? Если соединение разрывавется (например из-за таймаута) то повторная попытка установить новое соединение с повторной отправкой сообщения как раз и приведет к replay багам. И решением этой проблемы как раз и будет долговременное хранение как на сервере так и на клиенте либо этого счетчика либо вообще контекст всего соединения
А почему бы просто не установить на сервере таймаут соединения в бесконечность (если там есть этот таймаут) а дальше на клиенте просто сохранять на диск контекст соединения и таким образом никогда не разрывать соединение? То есть даже если юзер выходит из приложения то при следующем запуске приложение просто загрузит контекст соединения из диска и просто продолжит отправлять и получать данные по этому уже установленному соединению.
Таким образом решается задача возобновления сессий - раз соединение практически никогда не будет разрываться то нет такой необходимости пытаться оптимизировать установку нового соединения и время повторного подключения
Если мы запекаем в бинарнике приложения ephemeral ключ то на сервере нужно будет хранить приватную часть этого ключа не все время а только до выхода новой версии приложения (а если приложение активно развивается то выход минорной версии происходит обычно не реже чем раз в месяц). Это конечно не идеальный perfect-forward-security но уже лучше чем в протоколе TLS 1.2 когда часто использовалась схема с использованием RSA и публичного ключа из сертификата для дальнейшей передачи симметричного ключа шифрования - там действительно утечка приватного ключа сертификата позволит расшифровать все перехваченные ранее сообщения за все время действия сертификата (год и больше) и поэтому в TLS 1.3 полностью отказались от этой варианта и оставили только схемы c согласованием ключа через DHE или ECDHE (diffie-hellman и аналог на эллиптических кривых).
Вообще "идеального" PFS не сущестсвует так как это всегда trade-off между удобством для пользователя (отсутствие необходимости делать полноценный хендшейк на каждый чих) и безопасностью. Вот скажем ради безопасности вы решили что зашивать ephemeral ключ в бинарнике приложения (с необходимостью хранить приватную часть на сервере до выхода новой версии приложения) это небезопасно с точки зрения PFS поэтому вы решаете генерировать приватную часть и согласовывать новый ключ с каждым соединением. Но проблема в том что если вы решаете организовать общение клиента с сервером не через установку нового соединения на каждый запрос к серверу а через установку одного долгоживущего соединения (с обменом сообщений внутри этого соединения) то это уже в какой-то степени жертвование безопасностью и уход от PFS. А если вы решаете добавить механизм возобновления сессии (ну или же мое предложение в виде сохранения контекста соединения на диск) то это еще больший уход и пожертвование perfect-forward-security ради увеличения эффективности и удобства.
В общем для себя я решил что проблемы PFS несколько преувеличены и по сути являются сомнительной (на мой взгляд) попыткой сгладить потенциальный взлом сервера (а иначе как этот долговременный приватный ключ может утечь?) и я такой себе дальше думаю - если уж произошел такой взлом с утечкой приватного ключа то мне кажется что возможность расшифровать перехваченный ранее обмен сообщениями это будет не самая большая проблема в сравнении с потенциальным доступом (в случае взлома) ко всем данным пользователей (то есть ко всему что этот сервер может хранить или обрабатывать)
Мне кажется или здесь зарыта дыра в вашем алгоритме? Что мешает "человеку посередине" перехватить это сообщение а потом выдать себя за сервер просто отправив эту копию? Приватного ключа у него нет но он ему и не нужен так как он просто отправит тот же эфемерный публичный ключ который был сформирован сервером и прикрепит ту же самую подпись и таким образом успешно выдаст себя за сервера. А чтобы предотвратить такую возможность нужно чтобы сервер подписал/зашифровал своим приватным ключом не свой эфемерный ключ а некое рандомное число которое клиент сгенерирует и пришлет серверу в своем первом сообщении (и дальше клиент получив ответ расшифрует число публичным ключом сервера и проверит совпадает ли оно с тем числом которое клиент отправил)
Очень интересно узнать как вы собираетесь обеспечивать этот инвариант. Например что помешает пользователю форкнуть исходники этой ноды и изменить сорцы так чтобы можно было отправить письмо без участия в хранении и транспортировке? В децентрализированных приложениях основанных на криптовалютах такое же почтовое приложение будет работать таким образом что отправка письма потребует оплаты этой криптовалютой которую можно заработать как раз и обеспечивая работу этой сети. Получается устойчивая замкнутая экосистема. А как будет обеспечиваться устойчивость работы в вашей сети непонятно. Можно предположить что для отправки письма пользователю нужно будет пропустить через себя н мегабайт трафика и продержать данные н дней но только непонятно как вы собираетесь обеспечить это на уровне алгоритма консенсуса то есть чтобы даже измененные исходники ноды со стороны юзера не позволили ему обмануть эти правила (то есть чтобы другие ноды не принимали отправленное письмо)
Есть несколько мыслей по оптимизации вашей схемы. У вас получается что клиент сможет отправить первый байты полезных данных (например имя api endpoint-a) только после полного раундтрипа? А что насчет сервера? Если сервер вычислил ключ шифрования уже на первом upd-пакете от клиента то значит он имеет теоретическую возможность отправить зашифрованные данные тут же не дожидаясь запроса от клиента, правильно? То есть сервер генерирует "ephemeral server public key" записывает его в udp-пакет который предназначен клиенту и тут же внутри этого же udp пакета следом записывает уже зашифрованные данные и тогда клиент получив кей-шару от сервера вычисляет симметрический ключ шифрования и дальше сможет расшифровать уже полученные данные от сервера.
В общем это довольно интересная техника которая позволяет избежать лишнего сетевого раундтрипа в случае если серверу нужно безусловно отправить клиенту какие-то данные (например свою версию api-ручек чтобы клиент увидев что у него старая версия бинарника которая не совместима с ними попросил юзера обновить приложение).
И дальше появляются мысли как можно еще сильнее зарыться в оптимизацию сетевых раундтрипов - если имя api-ручки сервера не является таким уж и секретным то клиент может включить это имя (в незашифрованном виде) в тот свой первый udp-пакет и тогда сервер сможет выполнить полученный запрос и отправить уже зашифрованные данные клиенту уже в том первом udp-пакете который клиент получит от сервера.
И кстати тут же напрашивается идея что можно сэкономить драгоценное место в upd-пакете (1472 байт) и закодировать имя api-ручки в номере udp-порта - то есть сервер будет слушать не один порт а например тысячу сетевых портов по одному на каждую api-ручку, правда для этого нужно чтобы это зашифрованное соединение не было привязано не только к ip-аддресу и порту клиента (что в принципе и так является хорошей практикой чтобы например избежать установки нового соединения при смене клиентом wi-fi сети на мобильную сеть и наоборот) но и к порту сервера.
Ну и наконец появляется такая идея что этот "ephemeral server public key" можно вообще включить в бинарник приложения и тогда клиент сможет вычислить ключ для симметричного шифрования вообще без общения с сервером и тогда он сможет в своем первом upd-пакете (включив сначала свой "ephemeral public key" ) отправить серверу в зашифрованном виде как имя api-endpoint-а так и соответствующие данные.
Зачем такие сложности с создаем новой колонки и постепенной миграцией? Почему бы просто не задать всем колонкам уникальное имя которое никогда не захочется поменять (случайный айдишник) а уже в коде хранить соответствие имя -> id при работе с этой бд. Тогда переименование колонки сведется к простейшей операции замены одного имени на другое в коде вообще без каких-либо затрат со стороны бд
вы только представляете а кто-то уже так делает и зарабатывает миллионы — вот один комментарий на HN который недавно набрал популярность в твиттере https://news.ycombinator.com/item?id=27454589
Насчет ангуляра не скажу но с реактом я применяю другой подход — просто пишу функции инлайном и таким образом вообще избавляюсь от диллемы именования функций и заодно очень просто и круто решается вопрос сематического соответствия кнопки и обработчика — вот есть кнопка а значит обработчик функции будет на этой кнопке (или наоборот — если видишь обработчик кнопки то и кнопка будет рядом)
Тут можно сказать мол "да ты захламляешь шаблон как такое вообще читать и поддерживать когда мол верстка в перемешку с логикой?" на что я отвечу "а code folding (сворачивание) в редакторе кода для кого придумали?" Если хотите видеть/редактировать только верстку то можно легко свернуть весь код обработчиков и увидеть чистый шаблон. И наоборот — если хотите посмотреть что делает какая-то кнопка то перемещаем мышку влево на пару сантиметров и кликаем-раскрываем обработчик кнопки.
Это намного удобнее чем переходить в отдельный файл или скроллить экран вверх-вниз (когда выносим обработчик функции в другое место). Это также удобно в поддержке — если допустим у нас поменялся дизайн/ux/логика и поменялась верстка в результате которой нужно удалить какой-то блок вместе с кнопкой то я удаляю нужный кусок верстки и вместе с этим удалится и обработчик этой кнопки поскольку он записан инлайном — мне не нужно беспокоится а не удалил/почистил ли я различные обработчики в другом файле или других местах по коду
И то же самое с добавлением какой-то фичи — я пишу кнопку и тут же пишу обработчик этой кнопки рядом и мне не нужно думать над тем куда вынести этот обработчик а главное — какое имя придумать этому обработчику. И также это удобно при ревью кода — когда добавляется какая-то фича то в git diff видишь изменения только в одном месте а не размазаны по кусочкам на кучу файлов/мест (верстка в одном месте, обработчик в другом месте и т.д)
Как вебсокеты могут быть более тяжёлой штукой чем http-запросы? Вы понимаете как работает http? HTTP-запрос это точно такое же тсп-соединение как и вебсокеты и ничем не легче по "тяжести". И если интернет-соединение нестабильное то http-запрос будет точно также обрываться с ошибкой как и вебсокет-соединение (и соотвественно нужно делать повторный запрос/установку соединения), в этом плане я вообще не вижу никакой разницы.
А вот во многих моментах http намного хуже и имеет больший оверхед.
Во-первых если не использовать keep-alive то браузер будет создавать тсп-соединение для каждого http-запроса и здесь http выходит намного тяжелее.
Во-вторых передача статуса и хедеров в текстовый формате для каждого запроса в итоге имеет больший размер и оверхед и это особенно видно при передаче небольших порций данных (с вебсокетами это может быть десяток байт когда же с http запрос будет занимать от сотни байт)
В-третих с http у браузеров есть ограничение на количество одновременных запросов (чего нет с вебсокетами).
В-четвертых проблема идемпотентности с http решается намного сложнее чем с вебсокетами (с http порядок запросов не гарантируются и они на сервер могут прийти в другом порядке чем были отправлены из клиента, когда же с вебсокетами у нас есть строгий порядок сообщений)
Ну и наконец вебсокеты это очень простая спецификация (по сути хедер с размером сообщения поверх тсп-протокола) и написать парсер вебсокетов можно за пару часов (формат фрейма объясняют даже для новичков) в то время как написать парсер http (не говоря уже про http2) это приключение на месяц учитываю кучу разнообразных нюансов самого текстового http формата, так и специфику корректно обработки различных хедеров (в частности keep-alive)
Нет, такого беспорядка и дилеммы выбора как с http не будет, зачем? Зачем нужны эти имена методов и статусы кодов ошибок если можно просто передать скажем объект
{action: "createProject", params: [...]}и вернуть объект{error: "..." | null, data: "..." | null}и вебсокет-обработчик на сервере просто вызовет нужную функцию и уже в самой функции будет происходит вся необходимая обработка — будь то получение (GET) или обновление данных (POST, PUT) или удаление (DELETE) или когда все эти действия происходят одновременно вместе с if-ами и циклами для батч-обработки каких-то данных (что просто не имеет соответствия "семантическим" методам в случае http). То есть исходная задача — вызывать какую-то процедуру на сервере и передать ей нужные параметры чтобы она обработала "запрос" и получила/обновила нужные данные в бд и вернула ответ назад с вебсокетами решается на порядок проще чем с http (где вызов нужной процедуры почему-то додумались делать через ручное придумывание и конфигурацию имен для роутов с маппингом на path/query http-запроса)А объясните пожалуйста зачем какому-то условному новичку (который примерно понимает как работает веб, как ходят запросы и что такое tcp) использовать все эти express/koa/fastify/nest/etc nodejs-фреймворки? Вот стоит перед новичком задача — со стороны клиента получить какие-то объекты с данными из бд или отправить объект с метаданными (для того чтобы изменить данные в бд). Новичок и так будет знать что вся информация в вебе ходит поверх тсп-протокола (который уже обеспечивает целостность данных при передаче), но чистый тсп в браузерах недоступен и браузеры предлагают два варианта — http или websockets
И какой самый простой способ решить эту задачу? Зачем этому новичку следует выбирать всякие express/fastify/nest-фреймворки со всякими непонятными middleware/сontrollers поверх http/rest подходов со всякими тонкостями/нюансами/дилеммами выбора методов (post vs put), способа передачи информации (path/query/headers/body) и выбора кодов для ошибок, зачем все это если можно просто установить вебсокет-соединение и просто пересылать нужные объекты/json от клиента к серверу и обратно. То есть исходная задача (установка транспорта между клиентом и сервером чтобы пересылать данные) с вебсокетами решается на порядок проще без всяких фреймворков чем c этими http/rest-фреймворками вроде express/fastify/nest и со стороны новичка становится совершенно непонятно зачем выбирать тот же fastify вместо вебсокетов
Вот он — триумф вертикального масштабирования! In-memory база данных поверх такого процессора способна будет обрабатывать десятки, сотни миллионов или возможно даже миллиард serializable транзакций в секунду и способна заменить сотни/тысячи серверов (и тот огромный оверхед при реализации распределенных транзакций в случае горизонтального масштабирования) определенно точно найдет своего покупателя.
Интересно как в этом процессоре работает механизм cache-coherence который обеспечивает CAS ("compare-and-swap") и другие атомарные обновления памяти? Cache-coherence это другими словами реализация атомарного бродкаста и поскольку это и так самая сложная часть при проектировании современных процессоров то здесь при 850 тысяч ядрер и возможном отказе этих ядер по отдельности проектировщики должны были выйти на уровень распределенных систем (интересно они реализовали paxos в железе?)
а чем плох вариант просто вызывать в цикле функцию вроде
parseNextHttpRequest(buf)с передачей буфера и получением словаря http-хедеров и значений (а также статуса и тела). Вполне себе органично вписывается в тот пример кода выше где вместо вызоваdo_use_fd(..)будет вызовparseNextHttpRequestа потом уже обработка самого http-запроса вызовом еще одной функцииА чем плох вариант просто пробросить системный вызов epoll в линуксе? Linux уже предоставляет возможности асинхронного io виде синхронного вызова epoll_wait в бесконечном цикле. Вот я нашел такую библиотеку (https://github.com/chopins/php-epoll), какие есть недостатки у такого подхода ?
Понятно что это только работа с голым tcp но ведь можно подключить какую-нибудь библиотеку которая будет парсить из tcp-потока http-запросы и таким образом получим на php асинхронный http-сервер который будет не хуже чем в NodeJS (и также это избавляет от необходимости использовать Apache или Nginx перед php)
Серьезно? Что же, давайте более подробно проанализируем этот кусок кода
Сколько раз ревьюеру (который видит этот кусок кода первый раз) нужно будет промотать глазами чтобы понять что происходит в коде?
1) видим строчку
COMPONENT_MAP[user.type]— ага, нужно найти взглядом эту переменную из внешнего скоупа и понять что там хранится,2) видим строчку рендера какого-то компонента
<Component name={user.name} />— опять нужно найти взглядом переменную из внешнего скоупа и понять что записано в переменную Component.3-6) и наконец по значениям-ссылкам на три компонента которые были записаны в COMPONENT_MAP мы теперь должны найти каждый компонент и понять что он рендерит
А теперь сравним это с такой версией
Сколько раз в этой версии разработчику нужно будет мотать глазами чтобы понять что происходит в коде? Не проще ли становится код когда вообще не нужно мотать глазами? Не является ли вторая версия более "декларативной" ?
А что вы скажете насчет веб-компонентов? Разве они были придуманы не для того чтобы избавиться от проблемы ограниченного набора и неоднозначности "семантических" html-тегов? Почему вместо разнообразных
<div>, <span>, <header>, <footer>, <main>, <section>, <article>, <h1>, <ul>, etcнельзя использовать кастомные веб-компоненты которые будут лучше подходить по семантике и функционалу к конкретной ситуации и лучше соответствовать дизайн-системе и семантически-функциональной структуре сайта/веб-приложения?А кто-нибудь знает почему не взлетела идея с FPGA? Ведь тогда каждая программа могла бы сама определять наиболее эффективные для своего исполнения инструкции, кеши и пайплайны. Или, например, с++/rust компилятор после всевозможных оптимизаций мог бы компилировать программу не в набор ассемблерных инструкций а сразу в схему транзисторов и связей которая будет максимально эффективна для конкретной программы
А почему такой скромный заголовок? Судя по тому что в википедии написано что температура ядра солнца всего лишь 15 млн градусов то достижение в 100 млн градусов надо было назвать как-нибудь "Ученые создали и двадцать секунд удерживали самое горячее вещество в солнечной системе, в 7 раз горячее солнца!!!"
А вообще цифры конечно впечатляют и сразу возникает такой вопрос почему так несимметрично — значит если нагревать так и до 100 млн можно а если охлаждать то всего лишь до -273 градусов и больше нельзя (так как абсолютный ноль), разница аж целых 6 порядков!
Ну вот я использую голый реакт и получается даже более удобно чем с mobx, вот пример — https://codesandbox.io/s/competent-engelbart-cltwg. Нет никакой магии геттеров-сеттеров или прокси-объектов, просто вызываем функцию актуализации когда нужно актуализировать view с состоянием. Есть и недостаток в виде менее эффективного обновления (реакт будет делать diff всего приложения при изменении состояния). Но тормоза появляются лишь на больших приложениях (> 10к дом-нод) что встречается нечасто и для 99% mvp-приложений можно обойтись без mobx (и потом можно легко подключить его только в случае появления тормозов diff-механизма реакта).
Сравнение некорректное. Java это язык а нода это рантайм (конкретная реализация взаимодействия с IO операционной системы). И скорость ноды и этой хваленый event loop и т.д — это все заслуга системного IO операционной системы — например на линуксе это системный вызов epoll_wait(..) — который позволяет одним потоком обслуживать много сокетов. Это значит что аналогичную эффективность NodeJS (скорости работы с io и обслуживание одним потоком многих tcp/http запросов) можно получить на всех популярных языках — не только на java но и например на php — достаточно всего лишь прокинуть системные вызовы epoll_wait/epoll_create/… в интерпретатор или компилятор языка.
Вы серьезно? Тогда такой вопрос — как вы собираетесь поддерживать атомарность при выполнении сложной бизнес-логики? Многопоточная модель это прямой путь к race-conditions и неконсистетности данных. Вы знаете что в базах данных (включая postgres, mysql) по умолчанию не включен serializable уровень изоляции и поэтому любую бэкенд-логику которая работает с бд отдельными запросами нужно врапить в эти транзкции (от начала и до конца) иначе рано или поздно у вас появятся race-conditions и неконсистентность данных (https://www.youtube.com/watch?v=5ZjhNTM8XU8)?
А теперь вопрос — какой тип бд позволяет обрабатывать транзакции с любой логикой (запросы к различным таблицам, включая условия и циклы) c seriazlizable-уровнем изоляции со скоростью > 100к транзакций в секунду? Вы не поверите но это такие базы данных которые выполняют все транзакции в одном потоке и вполне могут быть написаны на Node.js. Яркий пример такой бд — это Tarantool (https://www.youtube.com/watch?v=yrTF3qH8ey8)
А что касается скорости одного потока javascript — то он вполне находится на уровне с++ (если не относиться наплевательски на советы по производительности) — вот доклад где сравнивается с++ и js — https://www.youtube.com/watch?v=UJPdhx5zTaw и js всего лишь на 17% медленнее чем с++ с O3-флагом оптимизации (и это было еще в 2012 году)