Введение

В 1994 году, если требовалось создать динамический веб-сайт, разработчик писал программу на C, помещал её в каталог cgi-bin и позволял Apache выполнять её при каждом запросе. Спустя тридцать два года один программист отправил в production коммерческий веб-продукт, используя принципиально ту же технологию.

marketyour.games — полностью современное приложение. Это веб-продукт с пользовательскими аккаунтами, поисковой панелью для создателей контента, папками для организации материалов, платежами через Stripe и трансакционными письмами через Postmark. Приложение следует принципам прогрессивного улучшения и вообще не требует JavaScript.

Всё в нём современно, кроме самого способа создания и развертывания. Здесь — размышления о написании реального продукта на C и его публикации через CGI в 2026 году.

Суть

Отказ от CGI был обоснован в 2004 году, но в 2026 году это уже не выглядит столь убедительно.

Можно написать современное веб-приложение на C. Это уже сделано. Один бинарный файл, SQLite база, три зависимости — и всё приложение умещается в голове.

Как это было реализовано

Стек основан на BCHS: OpenBSD, C, httpd и SQLite.

Никаких фреймворков. Пишется программа на C, которая читает переменные окружения CGI и отвечает.

Никаких языков шаблонизации. Код на C напрямую выводит HTML.

Никакого отдельного сервера БД. Приложение открывает файл базы данных через C API SQLite.

Никаких контейнеров. Собирается статически линкованный бинарник и помещается в папку.

Каждый запрос запускает свежий процесс. Развертывание новой версии — это замена одного файла. Это означает отсутствие перезагрузки сервера и простоев. Новые запросы просто подхватывают новый бинарник.

OpenBSD поставляется с разумными настройками по умолчанию и предоставляет инструменты для реальной защиты C-программы, такие как pledge и unveil. Httpd по умолчанию служит из chroot, поэтому CGI бинарник работает в изолированной части файловой системы.

В проекте используются три дополнительные библиотеки:

  • libcurl обрабатывает все исходящие запросы к Stripe и Postmark.
  • libsodium выполняет генерацию случайных идентификаторов, хеширование паролей и HMAC-верификацию для вебхуков Stripe.
  • libcjson парсит JSON от Stripe и Postmark.

Только две из них — реальные зависимости при сборке: libcurl и libsodium.

  • libcjson — это один файл C без внешних зависимостей.
  • SQLite поставляется с официальным аmal­gamation-билдом (один файл C).

Обе встроены и собираются как часть проекта.

Результат — сборка с минимальным количеством движущихся частей. Чистая сборка занимает несколько секунд, полная пересборка — менее половины секунды.

Вместо традиционного способа подачи статических активов весь трафик от httpd маршрутизируется в CGI бинарник. Каждый ассет компилируется в исполняемый файл и подаётся из памяти.

Что понравилось

Фундаментальные принципы вместо фреймворков

CGI — это не фреймворк для изучения. Спецификация версии 1.1 была опубликована в 2004 году и с тех пор не менялась.

Нет API для изучения. Весь интерфейс — это несколько фундаментальных концепций HTTP:

  • заголовки запроса;
  • методы запроса;
  • строки запроса;
  • данные в теле запроса.

Если понимаешь HTTP, то уже понимаешь CGI.

Это язык- и платформо-независимо. Изучить один раз — знания остаются навсегда.

Ясное разделение ответственности

Современное веб-приложение обычно использует веб-сервер вроде nginx спереди и reverse-proxy на приложение-сервер, написанный на другом языке (Ruby, Python, Go, Rust). Это всегда казалось странным: по сути, два веб-сервера, и один из них отвечает за бизнес-логику.

CGI даёт один веб-сервер, который вызывает бизнес-логику напрямую. Ясно, кто что делает.

Это упрощает отладку. Приложение — обычная программа, которую можно запустить из shell: передать ей запрос, смотреть stderr, пошагово отладить через gdb.

В некотором смысле CGI похож на serverless архитектуру: супервизор вызывает код при каждом запросе, передаёт мешок переменных окружения и собирает результат. CGI — это serverless без вендора (и неожиданных счетов).

Минимальный риск цепочки поставок

Со взлётом автономных агентов и еженедельными атаками на репозитории пакетов приятно иметь очень мало зависимостей.

Большинство современных стеков идут с менеджером пакетов и экосистемой. Удобно, но граф зависимостей раздувается. Одна или две прямые зависимости могут вытянуть сотни косвенных, каждая поддерживается кем-то незнакомым, с учётными данными, защищёнными паролем, переиспользуемым с 2015 года.

Цепочка поставок здесь — компилятор C и три библиотеки. Чтобы её скомпрометировать, нужно скомпрометировать libcurl, проект, за которым следит весь интернет, а не пакет с одиннадцатью еженедельными загрузками и владельцем, уехавшим в 2019-м.

Простой инструментарий

Работаешь в минималистичном стиле. Используется classic Vim без плагинов. Это означает отсутствие LSP, автодополнения, форматеров кода и AI.

Сложные стеки требуют сложных инструментов: они требуют language server для прохода сквозь слои абстракции. Но этот стек настолько простой, что обычный текстовый редактор — всё, что нужно. Приложение целиком — это папка с C-файлами и Makefile.

Открываешь файл, читаешь его, понимаешь, что он делает.

Хороший инструментарий в пределах досягаемости

Когда инструменты действительно нужны, они близко. Sanitizers, valgrind, gdb, ctags, cscope — это зрелые (почти старческие) инструменты, отточенные десятилетиями, установка в одну команду на любой системе, способной компилировать C.

Зрелость выходит за рамки самих инструментов: интеграция встроена во многие редакторы. Vim поставляется с встроенной поддержкой ctags и cscope, имеет графический фронтенд gdb (см. :h Termdebug).

Писать HTML и CSS на C — не так плохо

Сомневался в написании всего фронтенда на C, но оказалось не таким болезненным, как казалось.

Слишком часто приходилось:

  • бороться с vim для подсветки html/template синтаксиса Go;
  • бороться с helix для детектирования Go шаблонов без нарушения html-детектирования;
  • бороться с RustRover для распознавания jinja шаблонов в проекте с askama.

Использование C означает отсутствие второго языка, который редактор полу-детектирует, нет смешанного синтаксиса, где подсветка разваливается, нет танца плагинов для кооперации двух языков.

Было освежающе просто писать C. Некрасиво, но так же некрасиво и Tailwind.

SQLite — глоток свежего воздуха

Всегда выбирал Postgres для веб-приложений. Отличная БД, но приносит операционную нагрузку. Есть процесс сервера, за которым нужно наблюдать, частые релизы для обновления.

SQLite этого не имеет. Никакого процесса, конфигурации, зависимостей. Приложение открывает файл базы напрямую. Операционная нагрузка минимальна.

Что я бы изменил

CGI — это интерфейс между веб-сервером и приложением. Никакого фреймворка или библиотеки. Это означает, что из коробки ты получаешь очень мало.

Нужно писать собственные утилиты для:

  • парсинга и создания cookies;
  • парсинга и валидации параметров запроса;
  • парсинга и валидации данных форм;
  • экранирования входных данных для отображения.

Написал свои в одном заголовочном файле (cgi.h), но не уверен, что это была правильная стратегия. Существуют библиотеки вроде kcgi, но принятие одной означает согласие с её API и моделью безопасности.

Хотелось бы видеть набор общих утилит, которые разработчики могут вставить (вроде stb-библиотек).

Может быть, опубликует cgi.h как начало.

Заключение

CGI старая, но не забытая.

Большинство причин, по которым мы отказались от неё в начале 2000-х, не выдерживают критики сегодня. Форкинг процесса уже не так дорогостоящ, как был; современные ядра его сильно оптимизируют.

И если стоимость форкинга когда-то станет проблемой, FastCGI — простое обновление.

Написание современного веб-приложения на C — более чем осуществимо. marketyour.games — коммерческий продукт, работающий на одном бинарнике с моделью развертывания, официально стандартизированной в 2004 году.

Он имеет меньше движущихся частей, чем всё, что отправлялось раньше. Этот стек станет первым выбором в следующий раз.