Изучая 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