Ориентация главы
Последняя глава книги. Проект 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概念
Архитектура: полная карта слоёв
Каждый из шести слоёв — главная тема какой-то предыдущей главы — webserv не изобрёл ни одного слоя, вся его работа — межслойная сборка и бизнес-клей (конфигурируемость отображений каталогов, белый список политики сжатия). Это финальная проверка «слоистой» философии книги: хорошие системы собирают, а не пишут.
Список семантики статического сервиса
| Семантика | Реализация | Главы |
|---|---|---|
| Отображение пути | префикс URL ↔ префикс каталога; после нормализации проверка префикса (защита от выхода) | 104/43 |
| MIME | табличный поиск по расширению | 104 |
| ETag | отпечаток содержимого; If-None-Match → 304 | 104/108 |
| Range | одинарный интервал 206 + Content-Range; многие интервалы multipart | 90/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 — минимальный контур статического файлового ответа:
#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; }
Что только что произошло. (1) Поверхность сборки статического слоя: отображение путей, MIME, путь нулевого копирования файлового ответа — весь статический слой webserv готов в библиотечном слое. (2) Условные запросы и Range согласуются внутри слоя (серверное зеркало If-None-Match/If-Range — xdl был клиентской стороной, здесь серверная — обе стороны одного протокола пройдены). (3) webserv добавляет сверху: конфигурируемые отображения (много каталогов), промежуточное ПО сжатия, вход TLS, журнал — четыре сборочные детали до полного сервиса.
Вторая полная программа: полная сборка маршрутизация + промежуточное ПО + статика
Вторая программа из examples/http/server_static — работоспособный скелет статического сервиса:
#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; }
Что только что произошло. (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; }
Пункты разбора. (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 (конечный автомат конфигурации/версии/здоровья) — знание последовательности проектов смыкается в последнем кольце производственной эксплуатации.
Типичные ошибки
Симптом: сканер безопасности сообщает, что /static/%2e%2e%2fsecret доступен — прямая склейка ../ ловится, кодированная форма просочилась.
Причина: URL декодируется один раз до маршрутизации (глава 90) — декодирование происходит до или после вашей проверки — вот что решает позицию линии обороны. Проверка отображения статического слоя идёт по декодированному пути (верно); самодельное отображение, проверяющее до декодирования, — кодированный выход.
snprintf(Path, "%s/%s", Root, RawUrlPath); /* склейка исходного нераскодированного пути */ check_prefix(Path); /* проверяется кодированная форма */
/* отображение статического слоя: встроенная нормализованная проверка после декодирования */ /* при своём управлении: сперва декодировать (глава 102) -> нормализовать -> проверять префикс — перепроверять после каждого слоя декодирования */
Симптом: захват пакетов показывает 304 с полным телом — вся оптимизация кэша насмарку (полоса тратится как раньше).
Причина: после попадания условного запроса при ручной сборке ответа забыли, что семантика 304 — только заголовки. Путь 304 статического слоя встроен; при самодельной сборке ветка 304 обязана идти через сборку «только заголовки».
if ( etag_match ) { reply(304, body); } /* 304 с телом: нарушение протокола */
if ( etag_match ) { respond_headers_only(304, ETag); /* только заголовки + ETag */ return; }
Симптом: релиз новой версии — 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; у самодельной версии каждый провал локализован и закрыт.
Слияние всего опыта шести проектов: конфигурация (форма 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 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки