Ориентация главы

Последняя глава книги. Проект 6 (статический файловый сервер webserv): HTTPS обслуживает каталог сборки фронтенда — безопасный вход (TLS + сертификаты), статические отображения (префикс URL ↔ каталог на диске + защита от выхода из корня), условные запросы (ETag/304), возобновление загрузок (Range/206), согласование сжатия (gzip для сжимаемых текстов), журнал доступа и graceful-остановка. Это слияние шести проектов: обработка строк logstat (журналы), конфигурация configd (адрес прослушивания и отображения каталогов читаются из конфигурации), семантика Range у xdl (на этот раз со стороны сервера), жизненный цикл долгоживущих соединений pushd (HTTP keep-alive и TLS-сессии) — плюс специализация статического слоя (главы 105/132) и сжатия (глава 109). И это общее стягивание карты знаний книги: от третьей строки кода до этой главы знания тринадцати томов и ста сорока четырёх глав сходятся в одном настоящем развёртываемом сервисе. За приёмкой — финал книги: чему вы научились и куда идти дальше.

145.1引入

Сцена требования: артефакты сборки фронтенда (HTML/CSS/JS/картинки, ~5000 файлов) нуждаются в производственном статическом сервисе — требуется HTTPS (автовыбор сертификатов), конкурентность (пик скачиваний при релизе сборки), условные запросы (согласование кэша клиента — 304 экономит 95% трафика), возобновление загрузок (большой файл можно продолжить после обрыва), сжатие (gzip текстов экономит 70%), журнал доступа (аудит и анализ). nginx может — но вам нужна программируемость: своя политика кэша, маршрутизация по арендаторам, связка с внутренними системами — конфигурационный DSL универсального сервера быстро превращается в диалект, который трудно отлаживать; напишите на XRT — всё поведение есть прозрачная логика C-кода. Это не продолжение учебного упражнения, а демонстрация инженерного решения: когда сложность конфигурации универсальной детали превышает сложность собственного кода, собственный код становится рациональным — слоистость XRT держит стоимость «написать свою производственную деталь» под контролем (ядро webserv этой главы меньше трёхсот строк, остальное — библиотечные слои).

Критерии приёмки: (1) все типы файлов обслуживаются корректно (MIME/кодировка); (2) ../ и кодированный выход наружу — все 404 (проверка сканером безопасности); (3) согласование ETag: второй запрос 304 с нулевым телом; (4) Range: одинарный интервал 206 + multipart на многие; (5) gzip: тексты сжимаются, картинки как есть (матрица согласования Accept-Encoding); (6) 500 конкурентных соединений, устойчивость под пиком скачиваний (повторное использование keep-alive); (7) TLS: автовыбор сертификата по SNI, современные шифронаборы; (8) журнал доступа JSON Lines (форма главы 139); (9) graceful-остановка (полный цикл Drain); (10) ноль утечек stats за весь прогон.

145.2概念

Архитектура: полная карта слоёв

1Безопасный входTLS Server (сертификаты + SNI) — главы 86/87
2Слой HTTPServer + Router + промежуточное ПО (журнал) — главы 104/105
3Статический слойhttp_server_static — отображения каталогов / защита от выхода / MIME / ETag / Range — глава 105
4Слой сжатиясогласование reply_compress — глава 109
5Наблюдаемостьlogger JSON Lines + stats — главы 38/6
6Жизненный циклпятисегментные тайм-ауты по умолчанию + Drain — глава 104

Каждый из шести слоёв — главная тема какой-то предыдущей главы — webserv не изобрёл ни одного слоя, вся его работа — межслойная сборка и бизнес-клей (конфигурируемость отображений каталогов, белый список политики сжатия). Это финальная проверка «слоистой» философии книги: хорошие системы собирают, а не пишут.

Список семантики статического сервиса

СемантикаРеализацияГлавы
Отображение путипрефикс URL ↔ префикс каталога; после нормализации проверка префикса (защита от выхода)104/43
MIMEтабличный поиск по расширению104
ETagотпечаток содержимого; If-None-Match → 304104/108
Rangeодинарный интервал 206 + Content-Range; многие интервалы multipart90/108
Сжатиесогласование q Accept-Encoding + белый список типов108/91
Заголовки кэшаCache-Control по типам (HTML коротко / активы долго)прикладной слой
Каталогхвостовой слэш → index.html; без слэша → редирект104

Ревизия защиты от выхода (ядро безопасности): запрос /static/../secret.key — отображение статического слоя после склейки делает проверку «нормализованный путь остался в корне» (ловушка 3 главы 105); кодированный выход (%2e%22%2f — процентно-кодированный ../): URL-декодирование происходит до маршрутизации (глава 90) — декодированный путь проходит ту же проверку. Двойная линия обороны: слой кодирования не предполагает единственный вход (атакующий может строить многослойное кодирование — после каждого слоя декодирования перепроверка).

Условные запросы и политика кэша

Генерация ETag: отпечаток содержимого (хеш файла — глава 74) или кортежный отпечаток (размер + время изменения — ноль затрат на хеширование). Решение: кортежный отпечаток — статический файл неизменен, пока содержимое неизменно (предположение неизменности артефактов сборки); хеш содержимого — для сценариев высокой безопасности (защита от обмана метаданных). Политика Cache-Control: index.html — короткий кэш (max-age=60 — минута до действия после релиза); активы с отпечатком (app.a3f9.js) — длинный кэш (max-age=31536000, immutable — смена имени файла есть инвалидация кэша — конвенция фронтенд-сборки). Трафиковый смысл 304: второе посещение (с кэшем) — нулевое тело — попадания кэша CDN/браузера не тратят полосу сервера (количественно критерий (3): сайт из 5000 файлов, первый визит 50 МБ, второй < 100 КБ накладных расходов заголовков).

Матрица политики согласования сжатия

Конфигурация webserv для слоя сжатия главы 109: белый список типов (text/css/js/json/svg сжимать; jpg/png/woff2 не сжимать — уже сжатые форматы); минимальная длина (< 256 Б не сжимать — накладные расходы больше выгоды); согласование (q-значения Accept-Encoding — модель тысячных главы 92). Путь апгрейда пресжатия: при сборке генерировать соседний файл .gz (app.js.gz) — при выдаче Content-Encoding: gzip отправлять файл напрямую (ноль онлайн-затрат на сжатие — конвенция CDN); базовая версия этой главы жмёт онлайн (малый объём сборки приемлем), пресжатие — направление упражнения.

Конкурентность и жизненный цикл

Форма пика скачиваний: после релиза N клиентов одновременно тянут все активы — на соединение приходится много запросов (повторное использование keep-alive — клиентская сторона в паре с пулом соединений главы 99). Конфигурация сервера: MaxConnections (предел конкурентности), WriteSize (аренда нулевого копирования — большие файлы кусками), пятисегментные тайм-ауты по умолчанию (защита от медленных атак — глава 104). Путь нулевого копирования файловой выдачи: семейство xrtHttpConnFile статического слоя (главы 105/108) — тело файла не входит в пользовательские буферы (транспортное исполнение SendFile главы 72); уровень памяти не зависит от размера файла (проверка stats — критерий (10)). Graceful-остановка: Drain (новые отвергнуть, в полёте завершить) → сброс журнала → уничтожение — окно нулевого прерывания при релизном рестарте.

145.3示例

Первая полная программа: минимальная форма статического сервиса

Программа ниже из extlibs/xhttp/examples/http/static_file — минимальный контур статического файлового ответа:

extlibs/xhttp/examples/http/static_file/main.c源码 ↗
#include <xhttp.h>

#include <stdio.h>



/* 展示从受限文件根准备静态表示元数据和完整文件正文。 */
int main(void)
{
	static const char sPath[] =
		"xrt-http-static-file-example.tmp";
	xtaskpoolconfig Config = { 1, 8, 0 };
	const xhttprepresentation* pCurrent;
	xtaskpool* pPool;
	xhttpstaticfile* pFile;
	xhttpbody* pBody;
	xroot Root;
	xfile File;
	int iResult = 1;

	(void)xrtFileDelete(sPath);
	xrtClearError();
	File = xrtOpen(
		sPath,
		XFILE_WRITE |
		XFILE_CREATE |
		XFILE_EXCLUSIVE
	);
	if ( (File == NULL) ||
		!xrtWriteFull(
			File,
			"static resource",
			15,
			NULL
		) ||
		!xrtClose(File) ) {
		return 1;
	}
	Root = xrtRootOpen(".");
	pPool = xrtTaskPoolCreate(&Config);
	pFile = (Root != NULL) && (pPool != NULL) ?
		xrtHttpStaticFileOpen(
			pPool,
			Root,
			sPath
		) : NULL;
	pCurrent = pFile != NULL ?
		xrtHttpStaticFileRepresentation(
			pFile
		) : NULL;
	pBody = pFile != NULL ?
		xrtHttpStaticFileTakeBodyAll(
			pFile
		) : NULL;
	if ( (pCurrent != NULL) &&
		pCurrent->HasETag &&
		(pBody != NULL) ) {
		printf(
			"size=%llu weak-etag=%.*s\n",
			(unsigned long long)
				xrtHttpBodyLength(pBody),
			(int)pCurrent->ETag.Opaque.Size,
			pCurrent->ETag.Opaque.Data
		);
		iResult = 0;
	}
	xrtHttpBodyDestroy(pBody);
	xrtHttpStaticFileDestroy(pFile);
	if ( pPool != NULL ) {
		(void)xrtTaskPoolDestroy(pPool);
	}
	if ( Root != NULL ) {
		(void)xrtRootClose(Root);
	}
	(void)xrtFileDelete(sPath);
	return iResult;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXHTTP_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xhttp.h impl.c extlibs/xhttp/examples/http/static_file/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapi(输出静态文件服务装配的自检结果)

Что только что произошло. (1) Поверхность сборки статического слоя: отображение путей, MIME, путь нулевого копирования файлового ответа — весь статический слой webserv готов в библиотечном слое. (2) Условные запросы и Range согласуются внутри слоя (серверное зеркало If-None-Match/If-Range — xdl был клиентской стороной, здесь серверная — обе стороны одного протокола пройдены). (3) webserv добавляет сверху: конфигурируемые отображения (много каталогов), промежуточное ПО сжатия, вход TLS, журнал — четыре сборочные детали до полного сервиса.

Вторая полная программа: полная сборка маршрутизация + промежуточное ПО + статика

Вторая программа из examples/http/server_static — работоспособный скелет статического сервиса:

extlibs/xhttp/examples/http/server_static/main.c源码 ↗
#include <stdio.h>
#include <string.h>
#include <xhttp.h>

#include <math.h>
static inline double exampleTimerLimit(int64 Timeout)
{
    return Timeout == XRT_WAIT_FOREVER ? INFINITY : xrtTimer() + (double)Timeout / 1000.0;
}
static inline bool exampleTimerExpired(double Limit)
{
    return xrtTimer() >= Limit;
}
static inline int64 exampleTimerRemaining(double Limit)
{
    double Ms;
    if (Limit == INFINITY) return XRT_WAIT_FOREVER;
    Ms = ceil((Limit - xrtTimer()) * 1000.0);
    return Ms <= 0 ? 0 : Ms >= 0x1p63 ? INT64_MAX : (int64)Ms;
}



/* 静态站点示例在 Server 生命周期内拥有文件任务池和目录根。 */
typedef struct example_http_static {
	xtaskpool* Pool;
	xroot Root;
} example_http_static;



/* 一次调用完成安全映射、条件请求、Range、MIME 和异步正文。 */
static void exampleHttpStaticRequest(
	xhttpserver* pServer,
	xhttpconn* pConnection,
	const xhttpserverrequest* pRequest,
	ptr pData
)
{
	example_http_static* pState =
		(example_http_static*)pData;

	(void)pServer;
	(void)pRequest;
	if ( xrtHttpConnStatic(
		pConnection,
		pState->Pool,
		pState->Root,
		NULL
	) ) {
		return;
	}
	(void)xrtHttpConnReply(
		pConnection,
		XHTTP_STATUS_SERVICE_UNAVAILABLE,
		XRT_STR_LITERAL(
			"text/plain; charset=utf-8"
		),
		XRT_BYTES_LITERAL("Service Unavailable")
	);
}



/* 输出静态文件准备或 HTTP 传输错误。 */
static void exampleHttpStaticError(
	xhttpserver* pServer,
	xhttpconn* pConnection,
	const xerror* pError,
	ptr pData
)
{
	(void)pServer;
	(void)pConnection;
	(void)pData;
	fprintf(
		stderr,
		"HTTP error [%s/%s]: %s\n",
		xrtErrorDomain(pError),
		xrtErrorOperation(pError),
		xrtErrorMessage(pError)
	);
}



/* 启动静态站点,按回车后停止接收并排空连接。 */
int main(int iArgc, char** ppArgv)
{
	xtaskpoolconfig PoolConfig = { 2, 128, 0 };
	example_http_static State;
	xnetengineconfig EngineConfig;
	xhttpserverconfig ServerConfig;
	xhttpserverevents Events;
	xnetengine* pEngine = NULL;
	xhttpserver* pServer = NULL;
	xnetaddr Address;
	double Deadline;
	str sEndpoint = NULL;
	int iResult = 1;

	if ( iArgc != 2 ) {
		fprintf(
			stderr,
			"usage: server_static <directory>\n"
		);
		return 0;
	}
	memset(&State, 0, sizeof(State));
	State.Root = xrtRootOpen(ppArgv[1]);
	State.Pool = xrtTaskPoolCreate(&PoolConfig);
	if ( (State.Root == NULL) ||
		(State.Pool == NULL) ) {
		goto Cleanup;
	}
	xrtNetEngineConfigInit(&EngineConfig);
	pEngine = xrtNetEngineCreate(&EngineConfig);
	if ( (pEngine == NULL) ||
		!xrtNetEngineStart(pEngine) ) {
		goto Cleanup;
	}
	xrtHttpServerConfigInit(&ServerConfig);
	if ( !xrtNetAddrLoopback(
		&ServerConfig.Network.Listen.Address,
		XNET_FAMILY_IPV4,
		0
	) ) {
		goto Cleanup;
	}
	xrtHttpServerEventsInit(&Events);
	Events.Request = exampleHttpStaticRequest;
	Events.Error = exampleHttpStaticError;
	Events.Data = &State;
	pServer = xrtHttpServerStart(
		pEngine,
		&ServerConfig,
		&Events
	);
	if ( (pServer == NULL) ||
		!xrtHttpServerLocal(
			pServer, 0, &Address
		) ) {
		goto Cleanup;
	}
	sEndpoint = xrtNetAddrEndpointString(&Address);
	if ( sEndpoint == NULL ) {
		goto Cleanup;
	}
	printf(
		"serving %s on http://%s/\n"
		"press Enter to drain\n",
		ppArgv[1],
		sEndpoint
	);
	xrtFree(sEndpoint);
	sEndpoint = NULL;
	(void)getchar();
	if ( !xrtHttpServerDrain(pServer) ) {
		goto Cleanup;
	}
	Deadline = exampleTimerLimit(INT64_C(5000));
	while ( xrtHttpServerState(pServer) !=
		XHTTP_SERVER_CLOSED ) {
		if ( exampleTimerExpired(Deadline) ) {
			(void)xrtHttpServerAbort(pServer);
			break;
		}
		xrtThreadYield();
	}
	iResult = 0;

Cleanup:
	xrtFree(sEndpoint);
	if ( (pServer != NULL) &&
		(xrtHttpServerState(pServer) !=
		 XHTTP_SERVER_CLOSED) ) {
		(void)xrtHttpServerAbort(pServer);
		Deadline = exampleTimerLimit(
			INT64_C(5000)
		);
		while ( xrtHttpServerState(pServer) !=
			XHTTP_SERVER_CLOSED ) {
			if ( exampleTimerExpired(Deadline) ) {
				iResult = 2;
				break;
			}
			xrtThreadYield();
		}
	}
	xrtHttpServerDestroy(pServer);
	if ( (State.Pool != NULL) &&
		!xrtTaskPoolDestroy(State.Pool) &&
		(iResult == 0) ) {
		iResult = 2;
	}
	if ( (State.Root != NULL) &&
		!xrtRootClose(State.Root) &&
		(iResult == 0) ) {
		iResult = 2;
	}
	if ( (pEngine != NULL) &&
		!xrtNetEngineDestroy(pEngine) &&
		(iResult == 0) ) {
		iResult = 2;
	}
	return iResult;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXHTTP_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xhttp.h impl.c extlibs/xhttp/examples/http/server_static/main.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapi(启动静态服务后按 Enter Drain 退出)

Что только что произошло. (1) Сборка трёх деталей Router + статическая точка + Engine — скелетная форма webserv. (2) Финал Drain в конце образца (по Enter — минимальная демонстрация graceful-остановки). (3) Точки подвеса промежуточного ПО (Use главы 105) — место вставки журнального промежуточного ПО. webserv = этот образец + конфигурация TLS + слой сжатия + массив отображений каталогов — каждое приращение есть конфигурационная грань какой-то главы, ни одного нового понятия.

Разбор реализации: форма исходника главной функции сборки

Главная функция сборки webserv — полный путь от чтения конфигурации до старта сервиса (разбор ключевых сегментов над учебными образцами):

int main(int argc, char** argv)
{
	wsvcfg Cfg;
	xnetengine* Eng = NULL;
	xhttpserver* Srv = NULL;
	xhttpserverrouter* Router = NULL;
	xtlsidentity* Ident = NULL;
	int Result = 1;

	/* 1 конфигурация: форма configd (умолчания -> перекрытие окружением -> валидация) */
	wsv_defaults(&Cfg);
	wsv_env_override(&Cfg);            /* глава 43 */
	if ( !wsv_validate(&Cfg, stderr) ) { return 2; }  /* перехват на старте */

	/* 2 Engine и идентичность */
	Eng = xrtNetEngineCreate(NULL);
	if ( (Eng == NULL) || !xrtNetEngineStart(Eng) ) { goto Cleanup; }
	Ident = wsv_load_identity(&Cfg);  /* глава 83: проверка на этапе старта */

	/* 3 маршрутизация: статические отображения + журнальное промежуточное ПО */
	Router = xrtHttpServerRouterCreate(NULL);
	if ( !wsv_mount(Router, &Cfg) ||          /* массив отображений каталогов */
		!xrtHttpServerUse(Router, wsv_log, &Cfg) ||  /* глава 105 */
		!xrtHttpServerRouterFreeze(Router) ) { goto Cleanup; }

	/* 4 сервис: вход TLS + пятисегментные тайм-ауты по умолчанию */
	{
		xhttpservertlsconfig Tls;
		xrtHttpServerTlsConfigInit(&Tls);
		Tls.Handshake.Identity = Ident;
		Srv = xrtHttpServerRouterStartTls(
			Eng, &Cfg.Net, &Tls, Router, NULL);
	}
	if ( Srv == NULL ) { goto Cleanup; }
	wsv_print_endpoint(Srv);          /* журнал прослушиваемого эндпойнта */
	Result = wsv_wait_shutdown();     /* ожидание сигнала/ввода */
	xrtHttpServerDrain(Srv);          /* graceful-остановка */
	wsv_log_flush(&Cfg);

Cleanup:
	xrtHttpServerDestroy(Srv);
	xrtHttpServerRouterDestroy(Router);
	xrtTlsIdentityRelease(Ident);
	xrtNetEngineDestroy(Eng);
	return Result;
}
$ gcc -O1 -DXRT_MODULE_ALL -DXHTTP_MODULE_ALL -I single -I single/extlibs -include xrt.h -include xhttp.h impl.c webserv.c -lws2_32 -lbcrypt -ladvapi32 -liphlpapiwebserv listening on https://127.0.0.1:8443 (root=dist)

Пункты разбора. (1) Четыре шага сборки (конфигурация → Engine/идентичность → маршрутизация → сервис), каждый провал — goto Cleanup — одно-выходная дисциплина дотянута до главной функции. (2) wsv_mount — конфигурируемость отображений каталогов (цикл по массиву Cfg.Mounts вешает статические точки — много каталогов/много префиксов). (3) Журнальное промежуточное ПО wsv_log снаружи маршрутизации — строка JSON на запрос (время/метод/путь/статус/байты/длительность — запись после Close фиксирует полное состояние). (4) Вход TLS через StartSecure (идентичность подключена — SNI автоматически выбирает из многих сертификатов). (5) После Drain сброс журнала — запросы в полёте во время остановки тоже входят в журнал (последняя полная партия записей). (6) Всякое уничтожение в обратном порядке зависимостей — исполнение семантики Shutdown главы 104. Главная функция — шестьдесят строк — весь «бизнес» webserv: остальное — библиотечные слои и помощники wsv_* (конфигурация/отображения/журнал — каждый < 100 строк).

Общая сводка с опытом шести проектов

Семнадцать инженерных аксиом — общая ведомость проверки в webserv (точка приземления webserv у каждой): приёмка прежде (десять критериев включая скан безопасности и пик); форма решает контейнер (массив отображений / кортеж ETag / построитель строк журнала); представления живут одним кадром (запросное представление журнального промежуточного ПО завершается внутри обратного вызова); слоистая заменяемость (сменить бэкенд хранилища / сменить политику сжатия — один слой); факты платформы входят в проект (системные различия разделителей путей / прав / расположения сертификатов); бюджет прежде (пропускная способность = min(диск, сеть) — CPU пренебрежим с обеих сторон); тестирование в четырёх квадрантах (матрица семантики + полигон выхода + пик под нагрузкой); удалённая неопределённость (клиент может отправить что угодно — перепроверять после каждого декодирования); протокольные атомарные точки (согласование ETag — согласованность гарантирует протокол); временное имя + переименование (атомарное переключение нового каталога релиза — переворот symlink); файловая система — авторитет состояния (ETag из метаданных файла — без собственного кэша); тестовые двойники (детерминированный клиент локальной нагрузки); веер проектируется заранее (бюджет повторного использования keep-alive под пик); сверка конечных состояний (после Drain счёт соединений нулевой); номера-самовосстановление (номер версии релиза даёт клиенту детекцию переключения); тестовый клиент тоже испытуем (сверка запросов каркаса нагрузки); интерфейс эксплуатации (печать эндпойнта + журнал — вот и наблюдаемость). У всех семнадцати есть точка приземления — полнота списка опыта и есть полнота проекта.

Общее стягивание карты знаний

webserv (и весь том шести проектов) прошёл все главы — инвентаризация по томам: том 1 (главы 3-8: от первой программы до обрезки — фундамент всего); том 2 (главы 10-12: числа и хеши — принцип отпечатков ETag); том 3 (главы 13-23: контейнеры — база таблиц отображений и очередей запросов); том 4 (главы 25-36: текст и данные — цепочка разбора URL/заголовков/JSON-журналов); том 5 (главы 37-50: системные сервисы — повседневность журналов/файлов/путей/каталогов/асинхронного IO); том 6 (главы 51-61: конкурентность — фундамент корректности конкурентности соединений); том 7 (главы 62-72: сеть — транспортная база TCP/буферов/Engine); том 8 (главы 73-88: безопасность — все уровни входа TLS); том 9 (главы 89-97: ядро протоколов — байтовая семантика HTTP/WS); том 10 (главы 98-109: xhttp — прямой инструментарий этого сервиса); том 11 (главы 110-129: библиотеки расширений — источник инструментов pushd/xdl); том 12 (главы 130-137: инженерная практика — дисциплина развёртывания/тестирования/тонкой настройки); том 13 (главы 138-145: проекты — спираль опыта). Сто сорок четыре главы, шесть проектов, семнадцать аксиом, около 650 тысяч слов пояснений — вот всё хозяйство этого учебника. Унести вы должны не шпаргалку API (есть на сайте), а глазомер слоистости и дисциплину рук — они работают на любой C-кодовой базе.

145.4契约

  • Двойная защита от выхода: после склейки нормализация и проверка префикса; многослойное декодирование — перепроверка после каждого слоя — зелёный полигон восьми форм выхода есть основа приёмки.
  • Кортеж ETag: размер + время изменения (предположение неизменности сборки); 304 с нулевым телом.
  • Два уровня кэша: HTML-вход короткий кэш (max-age=60) / активы с отпечатком длинный (immutable) — имя файла и есть механизм инвалидации.
  • Белый список сжатия: семейство сжимаемых текстов; уже сжатые форматы как есть; порог минимальной длины.
  • Range на сервере: одинарный интервал 206 + Content-Range; многие multipart — дополняет клиент xdl до полной протокольной грани.
  • Нулевое копирование: тело файла через семейство ConnFile — память не зависит от размера файла.
  • Предел конкурентности: MaxConnections + пятисегментные тайм-ауты по умолчанию — защита от медленных атак и пика.
  • TLS: подбор сертификата по SNI; современные наборы; позиция верификатора (у сервера нет верификатора, но есть идентичность).
  • Журнал JSON Lines: строка на запрос (время/метод/путь/статус/байты/длительность) — форма file_json.
  • Остановка: Drain отвергает новых и завершает старых → сброс журнала → уничтожение — окно релиза без прерываний.

Получение цифр бенчмарка и ёмкости

Ёмкостные выводы webserv получать методом главы 136 (не с потолка): бенчмарк пропускной способности — имитация пика (500 виртуальных клиентов тянут весь сайт) меряет req/s и точку насыщения полосы (диск или сеть заполняется первым — бюджет указывает направление масштабирования); распределение задержек — P50/P99 задержки запросов (агрегация поля длительности журнала — продукт меток времени главы 42); якоря сравнения — при той же нагрузке разрыв с nginx (зрелая реализация sendfile/epoll) и анализ источников разрыва (накладные вызовы библиотечного слоя / без ядровой тонкой настройки / без мультипроцесса — в большинстве сцен разрыв < 2 раз приемлем, потому что главный выигрыш — программируемость); sweep — двумерный скан числа соединений × размера файлов (у малых файлов при высокой конкурентности и больших при низкой — две формы, у каждой своя точка насыщения). Форма ёмкостного вывода: «один инстанс 4C8G: малые файлы — X тыс. req/s (P99 < Y мс), большие — Z ГБ/с — сверх того добавляйте инстансы или ставьте CDN впереди». Эти цифры идут в документ развёртывания — продукт бенчмарка есть вход эксплуатации.

Процесс релиза и версионная политика

webserv сам как релиз (метаинжиниринг — проект тоже софт): атомарное переключение каталогов — новая версия собирается в releases/v{ts}/, symlink current переворачивается (атомарное переключение на уровне каталога — каталогная версия «временное имя + переименование» главы 143: в любой миг current указывает на одну полную версию); откат — symlink обратно на старый каталог (секунды — на два порядка быстрее рестарт-отката); проверка здоровья — эндпойнт /health (проверка артефактов: существование index.html) — релизный скрипт ждёт здоровья, прежде чем переключать трафик; непрерывность журнала — журналы через перезапуски катаются по дням (ротация главы 39) + поле версии (какая версия обработала запрос — якорь при разборе временных проблем). Автоматизационный скрипт релиза сам есть идея configd (конечный автомат конфигурации/версии/здоровья) — знание последовательности проектов смыкается в последнем кольце производственной эксплуатации.

Типичные ошибки

Ловушка 1: ручная склейка отображений каталогов упускает кодированный выход:Ловушка 1: ручная склейка отображений каталогов упускает кодированный выход

Симптом: сканер безопасности сообщает, что /static/%2e%2e%2fsecret доступен — прямая склейка ../ ловится, кодированная форма просочилась.

Причина: URL декодируется один раз до маршрутизации (глава 90) — декодирование происходит до или после вашей проверки — вот что решает позицию линии обороны. Проверка отображения статического слоя идёт по декодированному пути (верно); самодельное отображение, проверяющее до декодирования, — кодированный выход.

错误写法
snprintf(Path, "%s/%s", Root, RawUrlPath);  /* склейка исходного нераскодированного пути */
check_prefix(Path);                           /* проверяется кодированная форма */
正确写法
/* отображение статического слоя: встроенная нормализованная проверка после декодирования */
/* при своём управлении: сперва декодировать (глава 102) -> нормализовать -> проверять префикс — перепроверять после каждого слоя декодирования */
Ловушка 2: ответ 304 с телом:Ловушка 2: ответ 304 с телом

Симптом: захват пакетов показывает 304 с полным телом — вся оптимизация кэша насмарку (полоса тратится как раньше).

Причина: после попадания условного запроса при ручной сборке ответа забыли, что семантика 304 — только заголовки. Путь 304 статического слоя встроен; при самодельной сборке ветка 304 обязана идти через сборку «только заголовки».

错误写法
if ( etag_match ) { reply(304, body); }   /* 304 с телом: нарушение протокола */
正确写法
if ( etag_match ) {
	respond_headers_only(304, ETag);   /* только заголовки + ETag */
	return;
}
Ловушка 3: все файлы получают длинный кэш:Ловушка 3: все файлы получают длинный кэш

Симптом: релиз новой версии — HTML пользователя ссылается на новые активы, но сам HTML закэширован — белый экран или микс старого и нового.

Причина: политика кэша не различает входной документ и активы с отпечатком. HTML — вход (сменились ссылки — сменилось содержимое) — только короткий кэш; у имени актива есть отпечаток (смена = новый URL) — только тогда можно immutable.

错误写法
set_header("Cache-Control", "max-age=31536000");  /* все файлы: публикация = инцидент */
正确写法
if ( is_html ) { cc("max-age=60"); }
else { cc("max-age=31536000, immutable"); }  /* актив с отпечатком */
Замысел дизайна семантической матрицы:Замысел дизайна семантической матрицы

Замысел упражнения с шестнадцатью наборами семантической матрицы: типы (HTML/JS/JPG) × условие (попадание/мимо) × Range (нет/одинарный) × сжатие (да/нет) = 3×2×2×2 = 24 (взяты шестнадцать представительных комбинаций). Ценность матрицы не в прохождении, а в процессе прохождения — для каждого набора вы сперва пишете ожидание (ручной вывод по протоколу), потом проверяете — любое несовпадение есть слепая зона понимания протокола. Баги настоящих статических серверов почти всегда на пересечениях этих комбинаций (остаток заголовка Content-Encoding у 304+сжатие, ошибочный порядок Range+сжатие, двойное сжатие JPG+сжатие) — матрица есть систематическое покрытие пространства комбинаций, эквивалент серверной версии мутационного тестирования главы 133.

Практические задания

Базовое: полная семантическая матрица

Прогоните семантическую матрицу по static-сервису: шестнадцать комбинаций типы×условия×Range×сжатие (по запросу минимум на каждую). Критерий приёмки: шестнадцать наборов ответов (статус/заголовки/наличие тела) совпадают с ручным выводом по протоколу.

Продвинутое: полигон атак на выход

Постройте восемь видов выхода (прямая склейка / однослойное кодирование / двухслойное / концевая точка / обратный слэш / нулевой байт / сверхдлинный / символическая ссылка) против самодельного отображения и статического слоя. Критерий приёмки: статический слой — все 404; у самодельной версии каждый провал локализован и закрыт.

Вызов: полный webserv

Слияние всего опыта шести проектов: конфигурация (форма configd), вход TLS, сжатие, журнал, Drain, нагрузка (пик 500 соединений — метод главы 136). Критерии приёмки: все десять критериев пройдены; собственный код ядра < 400 строк (остальное — библиотечные слои) — количественное доказательство «собирают, а не пишут».

Двойной список производительности и безопасности

Последние две страницы перед выходом в прод. Список производительности: (1) подтверждение пути нулевого копирования (уровень памяти больших файлов = константа по stats); (2) сверка белого списка сжатия (картинки не жать, тексты жать); (3) уровень повторного использования keep-alive (поле соединения в журнале — повторное использование < 50%: смотреть клиент или тайм-ауты); (4) попадание ETag (доля 304 — ниже 80%: смотреть политику заголовков кэша); (5) пятисегментные тайм-ауты явно (защита от медленных атак — не ставка на умолчания). Список безопасности: (1) полигон выхода весь зелёный (8 форм 404); (2) политика шифронаборов TLS (объект политики главы 85 — старые наборы выключены); (3) минимизация прав каталогов (сервисная учётка читает только статический корень); (4) обезличивание журналов (поля токенов в строке запроса — дисциплина главы 77); (5) страницы ошибок не выдают пути (общий текст 404 — серверные пути есть материал разведки). По пять пунктов в каждом — ни производительность, ни безопасность не фичи, а степень исполнения списков.

Итоги

ТемаКратко
Позицияслияние шести проектов + общее стягивание карты знаний книги — собирают, а не пишут
Статическая семантикаотображения / MIME / ETag / Range / сжатие / заголовки кэша — глава 105 отвечает за всё
Двойная защита от выходанормализация и проверка префикса после декодирования; многослойное декодирование — перепроверка после каждого слоя
ETagкортежный отпечаток (размер + mtime); 304 — только заголовки, нулевое тело
Два уровня кэшаHTML коротко / активы с отпечатком immutable — имя файла есть инвалидация
Сжатиебелый список + минимальная длина + согласование q; пресжатие — путь апгрейда
Range с двух сторонэтот сервер + клиент xdl — полная протокольная грань
Нулевое копированиесемейство ConnFile — память не зависит от размера файла
ПикMaxConnections + пятисегментные тайм-ауты + повторное использование keep-alive
Финалдесять критериев приёмки + демонстрация инженерного решения программируемого сервера + общее стягивание карты знаний

Последний урок: от читателя к автору

Последний совет учебника — о смене роли. Прочитав книгу, вы способны передать эту слоистость и дисциплину дальше — а передача есть конечная форма проверки владения. Три пути: в команде — взять реальный модуль и написать по нему главу в формате книги (восемь секций скелета, приёмка прежде, пары bad/good) — письмо обнажит места, которые вы «поняли», но не можете объяснить; в опенсорсе — внести в XRT расширение или улучшение (список библиотек расширений главы 130 — точка входа: манифест принимает ваш модуль); в проекте — повесить список семнадцати аксиом на вики команды (по одной реальной аварии на пункт — ваш архив аварий и есть учебник вашей команды). Учебник устареет (API поменяются, платформы сменятся), но глазомер слоистости и дисциплина рук не устаревают — передать их дальше есть лучшая точка этой книги.

Финал книги

Сто сорок четыре главы пройдены, webserv — произведение, которое вы можете предъявить. Оглянитесь на форму пути: том 1 — фундамент (ошибки/память/обрезка — словарь всего); тома 2-3 — детерминированность и контейнеры (ящик с кубиками инженера); том 4 — текст и данные (мир информации); том 5 — системные сервисы (соседство с операционной системой); том 6 — конкурентность (корректность в измерении времени); том 7 — сеть (растяжение пространства); том 8 — безопасность (установление доверия); тома 9-10 — стек протоколов (полная реконструкция веб-мира); том 11 — библиотеки расширений (экосистемное растяжение); том 12 — инженерная практика (дисциплина превращения кода в продукт); том 13 — шесть проектов (комбинация ограничений и спиральное углубление опыта). Три линии сквозь всё: слоистость (по слою на главу, сборка проектов), дисциплина (приёмка прежде / представления одним кадром / единственный отказ / бюджет прежде — семнадцать инженерных аксиом), проверяемость (шлюзы / четыре квадранта тестов / сверка конечных состояний — у всякого утверждения есть машинное доказательство). Куда дальше? Три направления: вглубь — каждая глава книги есть дверь (детали TLS-рукопожатия, механизм io_uring в ядре — за дверью ещё двери); вширь — стройте на XRT свои детали (новые протоколы / контейнеры / инструменты — слоистость даёт вам место вставки); передавайте — принесите эту слоистость и дисциплину в свою команду (архитектура, которую можете объяснить, — это архитектура, которой владеете). В каком бы направлении — код ждёт вас на GitHub — дверь всегда открыта.

📖 Справочники API в этой главе:xhttp-http_server (75 записей)xhttp-http_server_static (6 записей)xhttp-http_server_middleware (4 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки