Как стать автором
Обновить

node-direct — один NodeJS сервер на несколько сайтов

Время на прочтение5 мин
Количество просмотров6.2K

tl;dr


С node-direct можно заливать серверные .js файлы и обращаться к ним так же, как к .php скриптам: example.com/foo.srv.js.


  1. Установка.


    npm install -g node-direct

  2. Конфигурация nginx.


    location ~ \.srv\.js$ {
            root <path_to_website_files>;
            proxy_pass http://localhost:<port>;
            proxy_set_header X-Requested-File-Path $document_root$uri;
    }

  3. Запуск.


    node-direct --port=<port>

  4. Скрипт foo.srv.js, где req и res созданы сервером express.


    module.exports = function(req, res) {
            const someModule = require('some-module');
            res.send('Hello world!');
    }


Введение


Когда NodeJS стал более-менее популярным, мне было нелегко осознать, что с ним всё не так просто, как с PHP. Используя последний, можно было создать .php файл, залить его на сервер, обратиться по адресу example.com/путь/имяфайла.php и радоваться. Такая простота развертывания скриптов служила одной из причин, почему "пых" стал таким популярным.


В свою очередь, NodeJS, независимо от сложности приложения, заставляет очень многие вещи делать руками.


  • Гоняй каждое приложение на собственном порту
  • Определи роуты самостоятельно
  • Настрой деплой
  • Убедись, что приложение работает и после перезагрузки сервера
  • Не забудь сделать так, чтоб при изменении файлов, NodeJS сервер перезагружался

pm2 несколько облегчает просесс обновления и запуска приложения после перезагрузки или после брошеных приложением исключений. Но с ним, во первых, нужно набить руку, во-вторых, запуск очередного приложения заставлыть повторять рутинные действия.


Всё вышесказанное является, скорее, преимуществом, чем недостатком, если вы разрабатываете крупное приложение. Но что если приложение очень простое? Что если это вовсе не приложение, а обычный сайт, с небольшой "апишкой" (например, прокси для кроссдоменных запросов)? Что если на VPS гоняется много сайтов, и для каждого настраивать NodeJS сервер — лень.


node-direct — тулза, которая позволяет разворачивать серверные JavaScript скрипты так же просто, как и .php:


  • Залил файл
  • Обратился к нему так: example.com/путь/имяфайла.srv.js

При этом, один инстанс node-direct можно использовать на разных сайтах.


Конфигурация


За большую часть "магии" отвечает конфиг nginx. Его нужно научить обрабатывать запросы к файлам .srv.js (можно юзать любое расширение).


location ~ \.srv\.js$ {
    root <path_to_website_files>;
    proxy_pass http://localhost:<port>;
    proxy_set_header X-Requested-File-Path $document_root$uri;
}

  • path_to_website_files — путь к файлам сайта
  • port — порт, на котором живет node-direct (по умолчению, 8123)

Пример полного конфига:


server {
    listen 80;

    server_name example.com;

    # Serve static files
    location / {
        root /var/web/example.com/public;
        index index.srv.js index.html index.htm;
    }

    location ~ \.srv\.js$ {
        root /var/web/example.com/public;
        proxy_pass http://localhost:8123;
        proxy_set_header X-Requested-File-Path $document_root$uri;
    }
}

Т. е. статика сервится при помощи nginx (известно, что это в несколько раз быстрее статики, которую гоняет express без кеширования), плюс, в список поддерживаемых серверных файлов можно добавить даже .php (юзабельно для легаси проектов).


Использование


node-direct --port=8000

node-direct использует старый добрый Express. Серверные файлы должны экспортировать функцию, принимающую request и response.


Hello world:


module.exports = function(req, res) {
    const someModule = require('some-module');
    res.send('Hello world!');
}

JSON API:


module.exports = function(req, res) {
    if(req.method === 'POST') {
        req.json({
            message: 'Everything is awesome'
        });
    } else {
        req.status(400).json({
            message: 'Only POST requests are allowed'
        });
    }
}

Рендеринг


const fs = require('fs');
const ejs = require('ejs');
const template = ejs.compile(fs.readFileSync('./templates/index.html'));

module.exports = function(req, res) {
    res.type('html').send(template({ foo: 'bar' }));
}

За подробной информацией обратитесь к документации Express.


Пример гипотетического приложения:


/package.json - содержит dependencies and devDependencies специально для текущего проекта
/index.html - главный HTML файл
/js/app.js - client-side JavaScript
/css/style.css - стили
/node_modules/ - локальные модули установленные с помощью "npm install" (к папке нужно закрыть доступ извне)
/foo/index.srv.js - JSON API, позволяющий делать запросы на /foo/
/bar/index.srv.js - динамическиая HTML страница, которая возвращается при обращении к /bar/

Флаги


Общие


--port — порт сервера node-direct (8123 по умолчанию)


Режим standalone


В этом режиме создается HTTP сервер, раздающий статику и не ребующий nginx. Режим используется разработчиком для запуска на локальной машине.


--standalone — включает режим standalone
--root — путь к статичным файлам (process.cwd() по умолчанию)
--ext — расширение серверных JS файлов (.srv.js по умолчанию)


node-direct --port=8000 --standalone --root=./foo/bar --ext=.serverside.js

Запуск node-direct после перезагрузки сервера (требуется только один раз)


Можно добавить новое задание в crontab, вызовом crontab -e и добавлением новой задачи в файл.


@reboot <path_to_node> <path_to_installed_module> [<flags>]

  • path_to_node — абсолютный путь к бинарнику NodeJS (можно вызвать which node)
  • path_to_installed_module — абсолютный путь до установленного node-direct
  • flags — флаги

Пример:


@reboot /usr/local/bin/node /usr/local/lib/node_modules/node-direct/index.js --port=8000

Проблемы


Кеширование запрашиваемых модулей


Как известно, NodeJS кеширует значения, которые возвращает функция require. Когда вызывается require('foo') два или более раз, функция возвращает одно и то же значение. node-direct обновляет кеш автоматически, когда файл .srv.js изменен (например, вы залили новый файл на сервер) и перезагружать node-direct нет необходимости. Проблема может возникнуть тогда, когда .srv.js запрашивает другие модули.


// foo.srv.js
module.exports = function(req, res) {
    const bar = require('./bar');
    // ...
}

Когда меняется foo.srv.js, кеш обновляется, как и ожидается, но когда меняется ./bar, его значение остаётся прежним. node-direct мог бы обновлять все запрашиваемые модули самостоятельно, но это бы вызвало сайд-эффекты с непредсказуемым поведением в других модулях. Эту проблему можно решить созданием вотчера, котогрый чистит кеш, когда заданный модуль обновился.


В примере ниже для ./bar включена горячая замена, а для ./baz — нет.


// foo.srv.js

// для большего количества модулей с горяей заменой, нужно юзать цикл
const fs = require('fs');
const barPath = require.resolve('./bar');
const watcher = fs.watch(barPath, (eventType) => {
    if (eventType === 'change') {
        delete require.cache[barPath];
        watcher.close();
    }
});

module.exports = function(req, res) {
    const bar = require('./bar');
    const baz = require('./baz');
    // ...
}

Если выглядит слишком сложно, юзайте небольшой модуль fresh-up, который делает то же самое.


// foo.srv.js

// для большего количества модулей с горяей заменой, нужно юзать цикл
const freshUp = require('fresh-up');
freshUp(require.resolve('./bar');

module.exports = function(req, res) {
    const bar = require('./bar');
    const baz = require('./baz');
    // ...
}

Потенциальная уязвимость: хедер X-Requested-File-Path


Как вы, возможно, заметили, nginx передаёт адрес запрашиваемого файла node-direct серверу в виде хедера X-Requested-File-Path. Используя этот хедер, злоумышленник может вызывать произвольные JavaScript файлы на вашем сервере. Для того, чтоб сделать что-то плохое, хакеру нужно знать адрес "опасного" файла, относительно рута. Для закрытия уязвимости, нужно запретить обращаться к порту node-direct извне.


Вот, как это можно сделать в Linux используя файервол ufw


sudo ufw deny 8123
Теги:
Хабы:
Всего голосов 11: ↑7 и ↓4+3
Комментарии5

Публикации

Истории

Работа

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань