software-development
Today

Маленький веб

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

Именно такие проекты, реализующие различные серверы и клиенты для веба вы найдете в этой статье.

Проект "Sandbird' в действии

Ода миниатюризации

Есть много причин, по которым миниатюрные девушк.. ээ реализации программ заслуживают внимания:

  • обучение — врядли получится разобраться как устроен вебсервер, перелопачивая исходники монстров вроде Apache или Nginx, спасет только миниатюрная реализация;
  • основа для собственных проектов — большой объем чужого исходного кода под капотом вашего проекта будет висеть гирей и отвлекать ресурсы на поддержку, в отличие от чего-то маленького и простого;
  • борьба с энтропией — популярные библиотеки постоянно растут и раздуваются, при этом объем используемого функционала не особо меняется. Таким образом, большая часть кода в современном проекте с кучей внешних библиотек не используется никогда.

Разумеется есть определенные риски использования таких «наколенных» библиотек, связанные с неполной реализацией, безопасностью, работой под нагрузкой и так далее.

Но говоря откровенно, всего этого ныне хватает с головой и в больших популярных реализациях.

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

Тестовое окружение

Не стал опять заморачиваться с BSD, чтобы в третий раз не описывать специальную прослойку epoll-shim, позволяющую быстро и более-менее безболезненно портировать серверный софт с линукса.

На этот раз в качестве тестового окружения выступает обычная Ubuntu 25.10, хотя и с немного нестандартным ядром.

Компилятором выступит штатный GCC:

gcc version 15.2.0 (Ubuntu 15.2.0-4ubuntu4)

Поехали смотреть интересное!

Sandbird

https://github.com/rxi/sandbird/tree/master

Начнем погружение с весьма практичного проекта:

A tiny (~800sloc) embeddable HTTP server written in C89, compatible with Linux, OSX and Windows.

800 строк на чистом С, причем наиболее портабельного стандарта С89, c поддержкой Windows, Linux и MacOS — отличный набор для реального применения, например в качестве встроенного вебсервера.

Например в каком-нибудь устройстве.

Один из примеров, демонстрирующих работу этой библиотеки — на заглавном скриншоте к статье, причем показана обработка формы.

Вот так выглядит сборка примера:

cd example
gcc hello.c ../src/*.c -I../src -std=c89 -pedantic -Wall -Wextra -o hello

Так выглядит код тестового приложения с использованием этой библиотеки:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "sandbird.h"

static int event_handler(sb_Event *e) {
  if (e->type == SB_EV_REQUEST) {
    printf("%s - %s %s\n", e->address, e->method, e->path);
    sb_send_status(e->stream, 200, "OK");
    sb_send_header(e->stream, "Content-Type", "text/plain");
    sb_writef(e->stream, "Hello world");
  }
  return SB_RES_OK;
}

int main(void) {
  sb_Options opt;
  sb_Server *server;

  memset(&opt, 0, sizeof(opt));
  opt.port = "8000";
  opt.handler = event_handler;
  server = sb_new_server(&opt);

  if (!server) {
    fprintf(stderr, "failed to initialize server\n");
    exit(EXIT_FAILURE);
  }
  printf("Server running at http://localhost:%s\n", opt.port);

  for (;;) {
    sb_poll_server(server, 1000);
  }
  sb_close_server(server);
  return EXIT_SUCCESS;
}

Важный нюанс:

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

но с неблокирующими сокетами:

..
static void set_socket_non_blocking(sb_Socket sockfd) {
#ifdef _WIN32
  u_long mode = 1;
  ioctlsocket(sockfd, FIONBIO, &mode);
#else
  int flags = fcntl(sockfd, F_GETFL);
  fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
#endif
}
..

webs

https://github.com/nicholascok/webs

Следующий интересный и даже в чем-то уникальный проект, с лаконичным описанием:

a simple websocket server library.

реализует с помощью ~700 строк на С89.. серверные вебсокеты!

Вебсокеты это чаты, это «live-streaming», например отображение логов в реальном времени и тому подобные динамические штуки.

Так это выглядит в работе:

Статику отдает пример из предыдущего проекта.

Так выглядит сборка тестового приложения:

gcc -c *.c examples/test.c -Wall -Wextra -Wpedantic -Wno-overlength-strings -std=c89
gcc -o webs *.o -lpthread

Как нетрудно догадаться по -lpthread, тут уже используется многопоточность на базе POSIX threads:

..
static void* __webs_main(void* _srv) {
	webs_server* srv = (webs_server*) _srv;
	webs_client* user_ptr;
	webs_client user;
	
	for (;;) {
		user.fd = __webs_accept_connection(srv->soc, &user);
		user.srv = srv;		
		if (user.fd >= 0) {
			user_ptr = __webs_add_client(srv, user);
			pthread_create(&user_ptr->thread, 0, __webs_client_main,
			 user_ptr);
		}
	}	
	return NULL;
}
..

К сожалению этот интересный проект по большей части прототип, иллюстрирующий как можно на коленке без внешних библиотек реализовать серверную сторону вебсокетов на чистом С.

В случае реального использования, столь вольное использование malloc для обработки входящих пакетов быстро превратится в проблему:

..
/* deal with normal frames (non-fragmented) */
		if (WEBSFR_GET_OPCODE(frm.info) != 0x0) {
			/* read data */
			if (data) free(data);
			data = malloc(frm.length + 1);
..		

А вот так выглядит сокращенная версия тестового сервера для вебсокетов, c минимумом обработчиков:

#include "../webs.h"

int myFuncZ(webs_client* self) {
	printf("server %ld: (id %ld) connected!\n", 
	       self->srv->id, self->id);
	webs_send(self, "greetings, salutations!");
	return 0;
}
int myFunc2(webs_client* self) {
	printf("server %ld: (id %ld) disconnected!\n", 
	         self->srv->id, self->id);
	return 0;
}
int main(void) {
	webs_server* server1 = webs_start(7754);	
	if (!server1) {
		printf("failed to initialise a server.\n");
		return 1;
	}
	server1->events.on_open = myFuncZ;
	server1->events.on_close = myFunc2;
	
	webs_hold(server1);	
	webs_close(server1);	
	return 0;
}

Как видно из кода выше, все интересное происходит именно в обработчиках, где собственно и будет находиться ваша собственная логика, если вдруг решитесь использовать эту библиотеку.

Повторюсь, что весь этот проект, несмотря на всю свою интересность — сырой прототип и тащить в прод подобный код "as-is" точно не стоит.

Собирается только для Linux и только с помощью gcc.

Прототип как он есть.

cpp-httplib

https://github.com/yhirose/cpp-httplib

Это уже более зрелая библиотека:

A C++ header-only HTTP/HTTPS server and client library

11к строк кода на C++11 и несколько зависимостей от внешних библиотек:

pthreads, brotli, OpenSSL, zlib

реализуют весьма продвинутый HTTP/HTTPS сервер и клиент, причем фактически в одном файле.

Есть поддержка сборки как на Linux так и Windows, причем для второй есть готовый проект для Visual Studio.

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

Так выглядит сборка тестового сервера, использующего эту библиотеку:

g++ -o server -O2 -std=c++11 -I.. -Wall -Wextra -pthread \
  server.cc -DCPPHTTPLIB_OPENSSL_SUPPORT -lssl -lcrypto \
  -DCPPHTTPLIB_ZLIB_SUPPORT -lz -DCPPHTTPLIB_BROTLI_SUPPORT \
  -lbrotlicommon -lbrotlienc -lbrotlidec

Несложно догадаться по флагам вроде CPPHTTPLIB_ZLIB_SUPPORT, что большая часть зависимостей отключаема.

Еще думаю, заблудиться в 11к строчках кода и одном единственном файле будет проблематично даже у нейросети не самых опытных «плюсолюбов».

Так выглядит серверный «Hello, world!»:

#include <httplib.h>
using namespace httplib;

int main(void) {
  Server svr;

  svr.Get("/hi", [](const Request & /*req*/, Response &res) {
    res.set_content("Hello World!", "text/plain");
  });

  svr.listen("0.0.0.0", 8080);
}

В лучших традициях Django или Rails, тут есть маршрутизация запросов и привязка к URL — в данном случае обработчик связывается с урлом /hi.

Вот так выглядит обработка загрузки файлов с помощью формы и POST-запроса:

#include <fstream>
#include <httplib.h>
#include <iostream>
using namespace httplib;
using namespace std;

const char *html = R"(
<form id="formElem">
  <input type="file" name="image_file" accept="image/*">
  <input type="file" name="text_file" accept="text/*">
  <input type="submit">
</form>
<script>
  formElem.onsubmit = async (e) => {
    e.preventDefault();
    let res = await fetch('/post', {
      method: 'POST',
      body: new FormData(formElem)
    });
    console.log(await res.text());
  };
</script>
)";

int main(void) {
  Server svr;

  svr.Get("/", [](const Request & /*req*/, Response &res) {
    res.set_content(html, "text/html");
  });

  svr.Post("/post", [](const Request &req, Response &res) {
    const auto &image_file = req.form.get_file("image_file");
    const auto &text_file = req.form.get_file("text_file");

    cout << "image file length: " << image_file.content.length() << endl
         << "image file name: " << image_file.filename << endl
         << "text file length: " << text_file.content.length() << endl
         << "text file name: " << text_file.filename << endl;
    {
      ofstream ofs(image_file.filename, ios::binary);
      ofs << image_file.content;
    }
    {
      ofstream ofs(text_file.filename);
      ofs << text_file.content;
    }

    res.set_content("done", "text/plain");
  });

  svr.listen("localhost", 1234);
}

Так это выглядит в действии:

Разумеется подобный функционал — не откровение, особенно на 2026й год, но блин господа офицеры:

11к строк кода на все красоты!

Теперь переходим к миниатюрным реализациям протокола FTP — для передачи файлов, если кто вдруг забыл.

tiny_ftpserver

https://github.com/adamwym/tiny_ftpserver

Вот такой «игрушечный» проект:

a tiny FTP server written in C++

всего 1.5к строк на С++11 от безвестного китайского автора реализуют полнофункциональный FTP/FTPS-сервер:

с chroot, поддержкой локальных и анонимных юзеров и всем прочим.

FTPS это довольно редко встречающееся расширение протокола FTP, с поддержкой шифрования передачи данных. Не набравшее большой популярности ввиду появления SFTP и органичений самого FTP.

Так это выглядит в работе:

Проект использует известную библиотеку libnet, о чем автор забыл сообщить:

apt install libnet1-dev

Cборка происходит с помощью cmake:

mkdir build && cd build
cmake ..

Реализация FTP-сервера тут максимально классическая — используется chroot, а использование 21 и 20 портов зашито в код:

..
int socketfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_port = htons(21);
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
..   

Все это означает, что запускать сервер придется от суперпользователя:

sudo ./tiny_ftpserver ../tiny_ftpserver.conf 

У этого проекта есть один неожиданный нюанс:

на самом деле это.. студенческая работа.

И подобных проектов на Github оказалось великое множество.

Так что далеко не везде в 2026м году забыли что такое высшее техническое образование, что не может не радовать.

fineFTP Server

https://github.com/eclipse-ecal/fineftp-server

Следующий интересный и весьма редкий проект:

FineFTP is a minimal FTP server library for Windows and Unix flavors.

~2k строк на С++14 реализуют FTP-сервер в виде.. библиотеки!

В этом и есть основной прикол — возможность встроить FTP (причем сервер а не клиент) в ваш собственный проект.

Так выглядит код примера, с внедрением FTP-сервера:

#include <fineftp/server.h>
#include <thread>
 
int main() {
  // Create an FTP Server on port 2121. We use 2121 instead of the default port
  // 21, as your application would need root privileges to open port 21.
  fineftp::FtpServer ftp_server(2121);
 
  // Add the well known anonymous user. Clients can log in using username
  // "anonymous" or "ftp" with any password. The user will be able to access
  // your C:\ drive and upload, download, create or delete files. On Linux just
  // replace "C:\\" with any valid path. FineFTP is designed to be cross-platform.
  ftp_server.addUserAnonymous("C:\\", fineftp::Permission::All);
  
  // Start the FTP Server with a thread-pool size of 4.
  ftp_server.start(4);
 
  // Prevent the application from exiting immediately
  for (;;) std::this_thread::sleep_for(std::chrono::milliseconds(100));
  return 0;
}

Зачем и для чего такое может быть нужно — другой вопрос, все же обычно работу с файлами в конечном приложении (например обновление прошивки) стараются реализовать в виде клиента и через HTTP/HTTPS.

Но с точки зрения использования все отлично работает:

Это хорошая, взрослая библиотека, с несколькими коммитерами, с историей разработки, которая активно поддерживается и развивается.

Старого цирка с chroot и 21м портом тут нет, поэтому все работает без привилегий суперпользователя — на скриншоте выше как раз видно использование нестандартного порта для работы.

Поддерживается сборка для Linux, MacOS и Windows c приоритетом для последней.

Отличный проект, но далеко не последний в сегодняшней подборке.

rcpd

https://github.com/tenox7/rcpd

Этот проект — привет из далекого и славного прошлого:

This is a modern re-implementation of rcp (remote copy protocol) daemon, originally part berkeley r-commands.

Да, это самый настоящий сервер RCP, всего 300 строк на Golang от знаменитого в узких кругах компьютерных реконструкторов автора Tenox.

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

Крайне актуальный, если имеете дело с устаревшим оборудованием или встраиваемыми системами, которые до сих пор используют именно rcp для загрузки прошивок по сети.

Возвращаясь к проекту:

rcpd от Tenox реализует серверную сторону — сервер RCP

К которому подключаются клиенты для загрузки или скачивания файлов.

Так выглядит процесс копирования файла, в качестве клиента тут rcp из пакета GNU Inetutils:

Как видите и клиент и сервер запускаются от суперпользователя — такие были времена, RCP использует 514 порт, который нельзя занять без привилегий.

Сборка:

go build -o rcpd .

Кстати в Makefile проекта есть поддержка кроссплатформенных сборок, с весьма богатым выбором:

Стоит добавить, что протокол RCP сам по себе очень простой и не менялся весь период своего существования, что позволяет подключаться к этому RCP-серверу даже с помощью клиентских программ из 80х и 90х.

За кадром

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

url.c

https://github.com/cozis/url.c/

Парсер строк URL на чистом С и без зависимостей:

This is a small library to parse and manipulate URLs in conformance to RFC 3986 and (most of) the WHATWG specification.

Что умеет:

  • No allocations
  • No dependencies
  • The ability to switch between RFC 3986 and WHATWG with a flag
  • Relative reference parsing and resolution
  • URL normalization
  • Doesn't rely on null-terminated strings

LightFTP

https://github.com/hfiref0x/LightFTP

Еще один интересный проект миниатюрного FTP-сервера:

Small x86-32/x64 FTP Server

Поддерживается сборка под Linux/Windows/Mac, сам проект не мертвый и судя по коммитам - развивается.

uftpserver

https://github.com/cpopp/MicroFTPServer

Весьма специфичная реализация FTP-сервера для встраиваемых систем:

Minimal FTP Server that can run on an ESP8266 with MicroPython

Так я впервые узнал о существовании «микропетона» — давно существующего проекта, с огромным количеством поддерживаемого железа и широким функционалом.

Ну и в качестве финального аккорда:

https://github.com/mtheall/ftpd

FTP Server for 3DS/Switch/Linux.

Реализация FTP-сервера для.. Nintendo Switch!

Это такая игровая консоль для задротов если кто вдруг не в курсе: