node.js serverside - work on the bugs. Part 1
Good day.
This article is aimed at developers who have an idea about node.js.
Recently I was preparing material on facts that it is useful for developers to know for node.js in our office. The projects we are working on are API services that use the node.js express module as a web server. The material is based on real cases in which the code worked incorrectly or the logic in it was carefully hidden, or it provoked errors during expansion. On the basis of this material, a seminar was held on staff development.
Here, I decided to share. So far, only the first part, it is about 30%. If it will be interesting, will be continued!
I tried to provide an opportunity for quick familiarization, so I hid examples, arguments and comments in the spoilers. If the statements are obvious, “water” can be skipped. Although our “rake” in spoilers can also be interesting.
One colleague at the workshop asked me a question, why talk about it, if everything is already in this or that documentation. My answer was as follows. Despite the fact that the message is correct, everything is really in the documentation, we still make annoying mistakes related to the lack of understanding or ignorance of basic things.
Let's get started!
Unlike javavm, nodejs-vm is single-threaded ** .

A source
Scaling is done by running another node.js process or, if server resources are coming to an end, by running another server.
Starting with version 10.5.0 to node.js appeared experimental support for multi-threading .

A source
The heart of nodejs-vm is the event loop. When the execution of the code must be suspended or the code seems to be over, control passes to him.
package.json - our package description file. In this context, we are talking about our application, and not about dependencies. Listed below are the fields and explanations why it’s worth filling them out.
At this point, you can put an end. Information not entered is code simplification techniques used by our team.
If errors are found, I will try to fix them quickly!
This article is aimed at developers who have an idea about node.js.
Recently I was preparing material on facts that it is useful for developers to know for node.js in our office. The projects we are working on are API services that use the node.js express module as a web server. The material is based on real cases in which the code worked incorrectly or the logic in it was carefully hidden, or it provoked errors during expansion. On the basis of this material, a seminar was held on staff development.
Here, I decided to share. So far, only the first part, it is about 30%. If it will be interesting, will be continued!
I tried to provide an opportunity for quick familiarization, so I hid examples, arguments and comments in the spoilers. If the statements are obvious, “water” can be skipped. Although our “rake” in spoilers can also be interesting.
One colleague at the workshop asked me a question, why talk about it, if everything is already in this or that documentation. My answer was as follows. Despite the fact that the message is correct, everything is really in the documentation, we still make annoying mistakes related to the lack of understanding or ignorance of basic things.
Let's get started!
Node.js virtual machine
Single streaming
Unlike javavm, nodejs-vm is single-threaded ** .

A source
more
При этом существует пул вспомогательных потоков, которые используются самóй виртуальной машиной, например, для организации ввода-вывода. Но весь пользовательский код выполняется только в одном, «главном» потоке.
Это серьёзно упрощает жизнь, так как конкуренция отсутствует. Выполнение кода не может быть прервано в произвольном месте и продолжено в другом. Код просто выполняется, пока не встанет необходимость ожидания чего-либо, например, готовности данных при чтении из файла. Пока происходит ожидание, может выполняться другой обработчик, либо пока он не закончит работать, либо пока не начнёт тоже чего-то ждать.
То есть, если есть внутренняя структура данных, то не надо заботиться о синхронизации доступа к ней!
Что же делать, если «главный» поток не успевает обрабатывать данные?
Это серьёзно упрощает жизнь, так как конкуренция отсутствует. Выполнение кода не может быть прервано в произвольном месте и продолжено в другом. Код просто выполняется, пока не встанет необходимость ожидания чего-либо, например, готовности данных при чтении из файла. Пока происходит ожидание, может выполняться другой обработчик, либо пока он не закончит работать, либо пока не начнёт тоже чего-то ждать.
То есть, если есть внутренняя структура данных, то не надо заботиться о синхронизации доступа к ней!
Что же делать, если «главный» поток не успевает обрабатывать данные?
Scaling is done by running another node.js process or, if server resources are coming to an end, by running another server.
consequences and our "rake"
Тут тоже всё понятно. Нужно всегда быть готовым к тому, что процессов node.js может быть (и скорее всего будет) больше чем один. Да и серверов иногда тоже может быть несколько.
«Грабли», которые были
плохо не очень хорошо, а в данной ситуации и подавно. Без привлечения стороннего сервиса эта задача представляется мне не имеющей решения.
Коллега, который этим занимался, очень-очень хотел реализовать это без привлечения собственно базы данных. В конце концов, после нескольких «подходов к снаряду», это было реализовано… путём привлечения SharePoint.
«Грабли», которые были спрятаны найдены у нас в коде
Параллельные прямые в бесконечности пересекаются. Доказать нельзя, но я видел.Была сделана попытка обеспечить уникальность экземпляров сущностей в базе данных исключительно средствами приложения. В общем-то, это и в отрыве от контекста выглядит
Жан Эффель, «Роман Адама и Евы».
Коллега, который этим занимался, очень-очень хотел реализовать это без привлечения собственно базы данных. В конце концов, после нескольких «подходов к снаряду», это было реализовано… путём привлечения SharePoint.
** Multithreading or “if you really want”
Starting with version 10.5.0 to node.js appeared experimental support for multi-threading .

A source
But the paradigm remained the same.
Поэтому старый код будет продолжать работать и при использовании рабочих потоков.
Подробнее почитать можно тут.
- Для каждого нового рабочего потока создаётся свой изолированный экземпляр окружения виртуальной машины node.js.
- У рабочих потоков отсутствуют общие изменяемые данные. (Есть пара оговорок, но в основном утверждение справедливо.)
- Коммуникация осуществляется с помощью сообщений и SharedArrayBuffer.
Поэтому старый код будет продолжать работать и при использовании рабочих потоков.
Подробнее почитать можно тут.
Application life cycle
The heart of nodejs-vm is the event loop. When the execution of the code must be suspended or the code seems to be over, control passes to him.
Hidden text
В цикле обработки событий проверяется, не случилось ли (ох) событий, для которых мы зарегистрировали обработчики. Если случилось, то обработчики будут вызваны. А если нет, то будет проверено, а нет ли «генераторов» событий, для которых мы зарегистрировали обработчики. Открытое tcp соединение или таймер могут быть такими генераторами. Если же таковых обнаружить не удалось, то происходит выход из программы. Иначе происходит ожидание одного из таких событий, вызываются обработчики, и всё повторяется.
Следствием такого поведения является тот факт, что когда код вроде-бы закончился, выход из nodejs-vm не происходит, например потому, что мы зарегистрировали обработчик таймера, который должен быть вызван через какое-то время.
Это показано в следующем примере.
результат:
Подробнее почитать можно тут.
В результате, если администратор побывал в системе, любой пользователь, обратившийся к этому экземпляру сервиса, воспринимался как администратор.
Мне стоило неких усилий, показать коллеге, что в логике была ошибка. Коллега был уверен, что на каждый http запрос создаётся полностью новое окружение.
Следствием такого поведения является тот факт, что когда код вроде-бы закончился, выход из nodejs-vm не происходит, например потому, что мы зарегистрировали обработчик таймера, который должен быть вызван через какое-то время.
Это показано в следующем примере.
console.log('registering timer callbacks');
setTimeout( function() {
console.log('Timer Event 1');
}, 1000);
console.log('Is it the end?');
результат:
registering timer callbacks
Is it the end?
Timer Event 1
Подробнее почитать можно тут.
Ещё одни «грабли» в нашем коде
Управлять государством может каждый!Признак того, является ли пользователь администратором, сохранялся в глобальной переменной. Эта переменная инициализировалась значением false в начале работы программы. В дальнейшем, когда администратор регистрировался, этой переменной присваивалось значение true.
В результате, если администратор побывал в системе, любой пользователь, обратившийся к этому экземпляру сервиса, воспринимался как администратор.
Мне стоило неких усилий, показать коллеге, что в логике была ошибка. Коллега был уверен, что на каждый http запрос создаётся полностью новое окружение.
package.json - fields to fill out
package.json - our package description file. In this context, we are talking about our application, and not about dependencies. Listed below are the fields and explanations why it’s worth filling them out.
Hidden text
Пока мы не публикуем пакет в репозитории, поле можно и «зюками» забить. Вопрос в том, что это поле удобно использовать для именования файла инсталляции или, например, для показа имени продукта на его веб-странице. В общем «как вы яхту назовёте,..»
Основная идея — не забывать увеличивать номер версии при расширении функциональности, исправлении ошибок,… К сожалению, у нас в конторе ещё можно встретить продукты с неизменной версией 0.0.0. А потом поди догадайся, какая именно функциональность работает у клиента…
Это поле сообщает, какой файл будет запущен при старте нашего приложения (`npm start`). Если пакет используется в качестве зависимости, то какой файл будет импортирован при использовании нашего модуля другим приложением. Текущим каталогом считается каталог, где находится файл `package.json`.
А ещё, если мы, например, используем vscode, то файл, указанный в этом поле, будет запущен при вызове отладчика или при запуске команды «выполнить».
Расширение ".js" может быть опущено. Это скорее следствие всех возможных вариантов использования, поэтому в документации напрямую не прописано.
Это поле содержит кортеж: { «node»: version, «npm»: version,… }.
Мне известны поля «node» и «npm». Они определяют версии node.js и npm, необходимые для работы нашего приложения. Версии проверяются при выполнении команды «npm install».
Поддерживается стандартный синтаксис определения версий пакетов зависимостей: без префикса (единственная версия), префикс "~" (два первых числа версии должны совпадать) и префикс "^" (только первое число версии должно совпадать). При наличии префикса, версия должна быть больше или равна указанной в этом поле. Просто список версий; явное указание больше, меньше,… etc. тоже работает.
Оговорка. «npm install» проверяет версии, указанные в «engines», только если включён режим «engine-strict». Мы его включаем для каждого проекта, добавляя файл .npmrc со строкой: «engine-strict = true». Когда-то давно «npm install» делал эту проверку по умолчанию.
Некоторые контейнеры, как минимум в документации, пишут, что подходящие версии будут использованы по умолчанию. В данном случае речь идёт об Azure.
Пример:
С клиентом было неоднократно оговорено, что требуемая версия `node.js` должна быть не меньше 8. При поставке начальных версий приложения всё работало. «В один прекрасный день» после поставки новой версии у клиента приложение перестало запускаться. В наших тестах всё работало.
Проблема была в том, что в этой версии мы стали использовать функциональность, которая поддерживалась только, начиная с версии 8 node.js. Поле «engines» не было заполнено, поэтому раньше никто не замечал, что у клиента была установлена древняя версия node.js. (Azure web services default).
Поле содержит кортеж вида: { «script1»: script1, «script2»: script2,… }.
Существуют стандартные скрипты, которые выполняются в той или иной ситуации. Например скрипт «install» выполнится после отработки «npm install». Очень удобно, например, чтобы проверить наличие программ, необходимых для работы приложения. Или, скажем, для сжатия всех статических файлов, доступных через наш веб-сервис, чтобы их не приходилось сжимать налету.
При этом можно не ограничиваться только стандартными именами. Для того, чтобы выполнить произвольный скрипт, нужно запустить «npm run script-name».
Это удобно, чтобы собрать все используемые скрипты в одном месте.
Пример:
P.S. Расширение ".js" можно в большинстве случаев опускать.
name
Пока мы не публикуем пакет в репозитории, поле можно и «зюками» забить. Вопрос в том, что это поле удобно использовать для именования файла инсталляции или, например, для показа имени продукта на его веб-странице. В общем «как вы яхту назовёте,..»
version
Основная идея — не забывать увеличивать номер версии при расширении функциональности, исправлении ошибок,… К сожалению, у нас в конторе ещё можно встретить продукты с неизменной версией 0.0.0. А потом поди догадайся, какая именно функциональность работает у клиента…
main
Это поле сообщает, какой файл будет запущен при старте нашего приложения (`npm start`). Если пакет используется в качестве зависимости, то какой файл будет импортирован при использовании нашего модуля другим приложением. Текущим каталогом считается каталог, где находится файл `package.json`.
А ещё, если мы, например, используем vscode, то файл, указанный в этом поле, будет запущен при вызове отладчика или при запуске команды «выполнить».
Расширение ".js" может быть опущено. Это скорее следствие всех возможных вариантов использования, поэтому в документации напрямую не прописано.
engines
Это поле содержит кортеж: { «node»: version, «npm»: version,… }.
Мне известны поля «node» и «npm». Они определяют версии node.js и npm, необходимые для работы нашего приложения. Версии проверяются при выполнении команды «npm install».
Поддерживается стандартный синтаксис определения версий пакетов зависимостей: без префикса (единственная версия), префикс "~" (два первых числа версии должны совпадать) и префикс "^" (только первое число версии должно совпадать). При наличии префикса, версия должна быть больше или равна указанной в этом поле. Просто список версий; явное указание больше, меньше,… etc. тоже работает.
Оговорка. «npm install» проверяет версии, указанные в «engines», только если включён режим «engine-strict». Мы его включаем для каждого проекта, добавляя файл .npmrc со строкой: «engine-strict = true». Когда-то давно «npm install» делал эту проверку по умолчанию.
Некоторые контейнеры, как минимум в документации, пишут, что подходящие версии будут использованы по умолчанию. В данном случае речь идёт об Azure.
Пример:
"engines": {
"node": "~8.11", // require node version 8.11.* starting from 8.11.0"npm": "^6.0.1"// require npm version 6.* starting from 6.0.1
},
очередные «грабли»
А король-то голый!
С клиентом было неоднократно оговорено, что требуемая версия `node.js` должна быть не меньше 8. При поставке начальных версий приложения всё работало. «В один прекрасный день» после поставки новой версии у клиента приложение перестало запускаться. В наших тестах всё работало.
Проблема была в том, что в этой версии мы стали использовать функциональность, которая поддерживалась только, начиная с версии 8 node.js. Поле «engines» не было заполнено, поэтому раньше никто не замечал, что у клиента была установлена древняя версия node.js. (Azure web services default).
scripts
Поле содержит кортеж вида: { «script1»: script1, «script2»: script2,… }.
Существуют стандартные скрипты, которые выполняются в той или иной ситуации. Например скрипт «install» выполнится после отработки «npm install». Очень удобно, например, чтобы проверить наличие программ, необходимых для работы приложения. Или, скажем, для сжатия всех статических файлов, доступных через наш веб-сервис, чтобы их не приходилось сжимать налету.
При этом можно не ограничиваться только стандартными именами. Для того, чтобы выполнить произвольный скрипт, нужно запустить «npm run script-name».
Это удобно, чтобы собрать все используемые скрипты в одном месте.
Пример:
"scripts": {
"install": "node scripts/install-extras",
"start": "node src/well/hidden/main/server extra_param_1 extra_param_2",
"another-script": "node scripts/another-script"
}
P.S. Расширение ".js" можно в большинстве случаев опускать.
package-lock.json - helps to install specific versions of dependencies, but not the "freshest" ones
Hidden text
Этот файл появился в npm сравнительно недавно. Его цель — организовать повторяемость сборки.
На машине коллеги приложение прекрасно работало. На другом компьютере в идентичном окружении, в приложении, помещённом из git в новый каталог, после выполнения 'npm install', 'npm start' появлялись доселе невиданные ошибки.
Проблема была вызвана тем, что файл 'package-lock.json' отсутствовал в git-репозитории. Поэтому, при инсталляции пакетов, все зависимости второго и более уровня (естественно, не прописанные в package.json), инсталлировались максимально свежими. На компьютере коллеги всё было хорошо. На тестируемом компьютере подобралась несовместимая совокупность версий.
Возвращаясь от лирического отступления. Файл 'package-lock.json' содержит список всех модулей, инсталлированных локально для нашего приложения. Наличие этого файла позволяет воссоздать один-в-один набор версий модулей.
Резюме: не забываем класть в git и вкючать в файл поставки (инсталляции) приложения!
Полезно: если файл 'package-lock.json' отсутствует, но есть каталог 'node_modules' со всеми необходимыми модулями, файл 'package-lock.json' можно воссоздать:
To git or not to git?..
Этот файл появился в npm сравнительно недавно. Его цель — организовать повторяемость сборки.
и ещё одни «грабли»
Но я же ничего не менял в моей программе! Ещё вчера она работала!
На машине коллеги приложение прекрасно работало. На другом компьютере в идентичном окружении, в приложении, помещённом из git в новый каталог, после выполнения 'npm install', 'npm start' появлялись доселе невиданные ошибки.
Проблема была вызвана тем, что файл 'package-lock.json' отсутствовал в git-репозитории. Поэтому, при инсталляции пакетов, все зависимости второго и более уровня (естественно, не прописанные в package.json), инсталлировались максимально свежими. На компьютере коллеги всё было хорошо. На тестируемом компьютере подобралась несовместимая совокупность версий.
package-lock.json — to git!
Возвращаясь от лирического отступления. Файл 'package-lock.json' содержит список всех модулей, инсталлированных локально для нашего приложения. Наличие этого файла позволяет воссоздать один-в-один набор версий модулей.
Резюме: не забываем класть в git и вкючать в файл поставки (инсталляции) приложения!
Полезно: если файл 'package-lock.json' отсутствует, но есть каталог 'node_modules' со всеми необходимыми модулями, файл 'package-lock.json' можно воссоздать:
npm shrinkwrap
rename npm-shrinkwrap.json package-lock.json
At this point, you can put an end. Information not entered is code simplification techniques used by our team.
If errors are found, I will try to fix them quickly!