CGI - это соглашения о том, как должна считывать и выдавать данные программа, работающая под управлением веб-сервера. Это достаточно низкоуровневые соглашения. Есть более высокий уровень, напримр WSGI/ASGI в экосистеме Python. Или сервлеты в экосистеме Java.
Для node.js разговор о CGI неактуален, т.к. приложение на node само является сервером и получает данные непосредственно от модулей, реализующих стек протоколов интернета.
Веб-сервер обычно помещает такие значения, как строку GET-запроса, в переменные окружения, доступные программе с помощью кода типа getenv("QUERY_STRING").
Данные в теле POST-запроса считываются из стандартного потока ввода с помощью кода типа fread(data, 1, num_bytes, stdin)
Программа может обработать данные и поместить результат (заголовки и контент) в стандартный поток вывода (stdout) с помощью кода типа printf("Content-type: text/html\n\n");
Скомпилированная программа выполняется очень быстро, а возможности таких языков, как C, очень широки. Поэтому и сложилась практика поручать таким программам осуществлять вычисления, забирая полученный результат и отправляя веб-клиенту.
В настоящий момент практически повсеместно используется более быстрый и безопасный вариант интерфейса FastCGI. FastCGI взаимодействует с клиентом через сокеты или TCP/IP-соединение. На основе FastCGI работает PHP-FPM. FPM считают лучшим способом взаимодействовать с PHP при использовании веб-сервера NGINX. PHP-FPM выполняет запросы веб-сервера на исполнение PHP-сценариев.
При использовании PHP-FPM появляется возможность масштабировать веб-сервер и приложение раздельно. В связке Apache+mod_php PHP суть процесс в составе веб-сервера. И деплоятся они вместе. В случае PHP-FPM они могут жить раздельно, как веб-сервер и сервер баз данных. Всё это не говоря уже о том, что Apache запускает отдельный дочерний процесс для каждого запроса...
Проведём эксперимент.
Возьмём файл script.c (curl) https://github.com/GossJS/php_starters1/blob/cgi/script.c и скомпилируем его на компьютере под управлением Ubuntu.
gcc ./script.c -o 'script.cgi'
ls -la script.cgi
-rwxr-xr-x 1 root root 8904 Dec 16 12:43 script.cgi
whoami
root
Компилятор проставил биты исполнимости во всех трёх разделах прав. Для исполнения из веба это избыточно - выясните, какое условие здесь необходимо и достаточно при использовании сервера NGINX.
Чтобы сценарий CGI выполнился обычным образом, он должен быть вызван веб-сервером. Обычно исполнимый файл помещается в папку типа cgi-bin с соответствующими настройками. Задача здесь в том, чтобы не допустить выдачи клиенту самого файла - в нём может содержаться чувствительная информация, хотя он и скомпилирован.
https://kodaktor.ru/cgi-bin/script.cgi
При использовании таких технологий как Node мы не можем просто указать этот путь и ожидать, что каким-то образом файл выполнится и его стандартный вывод уйдёт в браузер. Мы вообще не знаем в общем случае, что означают эти знаки после доменного имени.
Но если на сервере kodaktor.ru работает на линуксе веб-сервер типа NGINX и в папке типа /var/www/cgi-bin находится исполнимый файл script.cgi, то при таком обращении веб-сервер позволит файлу выполниться и отдаст клиенту результат работы.
А в случае Node.js шаги будут другими.
app.get('/cgi-bin/script.cgi', async r=>{
const execFile = require('util').promisify(require('child_process').execFile);
const { stdout } = await execFile( `${__dirname}/script.cgi`);
r.res.send(stdout);
});
Если исполнимый файл существует, то он совершенно не обязан находится в папке cgi-bin. Но при обращении к маршруту /cgi-bin/script.cgi node передаст ему управление, а затем выдаст в Response результат.
Пример непосредственной работы с потоком ввода:
// PHP – нет встроенного массива $_PUT, считываем из stdin
$method = $_SERVER['REQUEST_METHOD'];
if ('PUT' === $method) {
parse_str(file_get_contents('php://input'), $_PUT);
var_dump($_PUT); //$_PUT contains put fields
}
// node.js http – аналогично
require('http')
.Server((req, res) => req.on('data', d => console.log(String(d)))
.on('end', () => res.end('Hello!')))
.listen(4321, () => console.log(process.pid));