Node.JS / [Из песочницы] Научить node.js перезапускаться по хоткею в любимом редакторе

в 0:21, , рубрики: linux, netcat, node.js, nodejs, vim, метки: , , , ,

Изучая node.js я обнаружил, что добиться успеха в этом непростом деле мне мешает постоянная необходимость перезапускать руками сервер node в командной строке. Так появилась идея повесить перезапуск сервера на горячую клавишу в любимом редакторе (в моем случае, Vim'е) сохранив возможность следить за выводом STDOUT и STDERR сервера. Решение linux-only, поскольку написано на sh.

Однако, одним node.js дело не ограничивается. Используя это решение можно организовать перезапуск и перенаправление вывода любой утилиты (python/php/etc), просто указав ее имя в настройках скрипта вместо node.

Не исключаю вероятность, всё это (и намного больше) реализовано в «нормальной IDE». Но, как известно, всяк кулик свой Eclipse/Vim/Emacs хвалит и переходить на иной не торопится.

Представляю вашему вниманию утилиту appcontrol.
Работает она очень просто. В терминале пишем
appcontrol listen
Теперь в этот терминал будет выводиться весь STDOUT и STDERR нашего node.
Вызов
appcontrol restart (из другого терминала или откуда-нибудь еще) перезапустит наш node сервер в виде демона, а весь вывод STDOUT и STDERR перенаправится в терминал с запушеным «appcontrol listen».
Команда
appcontrol stop
остановит всё что только можно остановить.

Это мой первый скрипт на shell, который делает что-то полезное, поэтому не
обошлось без подводных камней.

Подводный камень первый

Перезапустить процесс — дело нехитрое
pkill node && node index.js &

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

Не исключаю вероятность, что правильные разработчики организуют вывод
исключительно через логирование (в том числе и вывод exception'ов). Если
таоквые здесь присутствут, просьба поделиться своим опытом организации вывода всего и вся из node.js.

Как бы то ни было, было решено задействовать «швейцарский нож любого хакера» — утилитку netcat. Один экземпляр netcat у нас будет в роли сервера — получать
весь вывод node. А второй netcat будет, соответственно, этот вывод отправлять
первому.

Подводный камень второй

В любом уважающем себя linux-дистрибутиве есть невероятно полезная утилита
«pkill pattern», убивающая процесс соответствующий регулярному выражению pattern. Проблема в том, что мы используем два процесса «nc» (то бишь netcat), и рызов pkill "^nc$" убъет оба. Как обойти сей неприятный факт не пускаясь во все тяжкие я так и не узнал. Поэтому скрипт просто создает две символьные ссылки:
ln -s /usr/bin/nc ~/.nc_client
ln -s /usr/bin/nc ~/.nc_server

Теперь запуская
~/.nc_server -l -p 8081
вместо
nc -l -p 8081
мы можем убивать экземпляры nc по отдельности, поскольку имя процесса стало
именем символьной ссылки:
pkill "^.nc_server$"

Подводный камень третий

Netcat сервер завершает работу каждый раз, когда завершает работу подключенный к нему клиент. Чтобы каждый раз после рестарта node не запускать appcontrol listen вручную, запуск netcat сервера обернут в цикл

while [ -f $NC_LOCK ]; do
my_log "Restarting nc_server at port $NC_PORT.."
$NC_SERVER -l -p $NC_PORT
done

Проверка [ -f $NC_LOCK ] используется чтобы все-таки можно было выйти из
цикла командой appcontrol stop. netcat сервер будет переподключаться при разрыве до тех пор, пока существует файл $NC_LOCK. appcontrol listen создает lock-файл, а appcontrol stop удаляет его.

Собственно, скрипт (надо просто скопировать код в файл appcontrol, и сделать файлу «chmod 755 appcontrol»):

#!/bin/sh  NC_HOST=127.0.0.1 NC_PORT=8081 PRJ_DIR=/docs/code/js/node_book APP_NAME=node APP_ARGS=index.js BASE_DIR=~  NC_PATH=/usr/bin/nc NC_CLIENT_N=.nc_client NC_SERVER_N=.nc_server NC_LOCK=$BASE_DIR/.nc_listen.lock NC_CLIENT=$BASE_DIR/$NC_CLIENT_N NC_SERVER=$BASE_DIR/$NC_SERVER_N  my_log(){     echo "[$0] : $1" }  verbose_pkill(){     pkill "^$1$" && my_log "$1 was killed" || my_log "Can't kll $1" }  case "$1" in     listen)         touch $NC_LOCK         [ -L $NC_CLIENT ] || ln -s $NC_PATH $NC_CLIENT         [ -L $NC_SERVER ] || ln -s $NC_PATH $NC_SERVER         while [ -f $NC_LOCK ]; do             my_log "Restarting nc_server at port $NC_PORT.."             $NC_SERVER  -l -p $NC_PORT         done         my_log "Stop listening"         ;;     restart)         verbose_pkill $APP_NAME         verbose_pkill $NC_CLIENT_N         if [ -f $NC_LOCK ]; then             cd $PRJ_DIR             sleep 0.5             $APP_NAME $APP_ARGS 2>&1 | $NC_CLIENT $NC_HOST $NC_PORT &             my_log "Restarted"         else             my_log "$NC_LOCK not found, stopped"         fi         ;;     stop)         rm $NC_LOCK         verbose_pkill $APP_NAME         verbose_pkill $NC_CLIENT_N         verbose_pkill $NC_SERVER_N         my_log "Stopped"         ;;     *)         echo "usage: $0 {listen|restart|stop}" esac 

В разделе «restart» используется sleep 0.5 для того, чтобы сервер netcat успел
рестартнуться. Если sleep не использовать, nc_client не сможет подключиться к netcat серверу и всё взорвется.

Скрипт настраивается через переменные:

NC_HOST=127.0.0.1пока только локалхост, надо бы допилить скрипт, чтобы можно было запускать netcat сервер на другой машине
NC_PORT=8081порт, на котором висит netcat сервер
PRJ_DIR=/docs/code/js/node_bookпапка с нашим node.js проектом
APP_NAME=nodeсобственно, node (или python/php/etc) который будем слушать
APP_ARGS=index.jsаргументы, передаваемые node
BASE_DIR=~папка, в которой создадутся вспомогательные файлы (.nc_lock,
.nc_client, .nc_server)

Вешаем хуки

Осталось только повесить выполнение appcontrol restart на клавишу в нашем
любимом редакторе:

map <F5> <ESC>:write<CR>:silent !~/appcontrol restart 2>&1 > /dev/null<CR>

silent нужен чтобы после выполнения внешней команды vim не просил «press any key», а сразу возвращался в редактор. Так же перенаправляем весь вывод в /dev/null, чтоб не мешал.

Любые замечания, критика, исправления и троллинг добросердечно приветствуются.
Спасибо за внимание.

Автор: makoven


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js