Ориентация главы
Всё вне бизнес-маршрутизации — журналирование, хронометраж, авторизация, CORS, сжатие — должно пройти по каждому запросу. xrtHttpServerUse регистрирует их как middleware: вход в порядке регистрации, выход в обратном (луковичная модель); xrtHttpServerNext синхронно входит в следующий слой, после возврата исполняется пост-логика (хронометраж/завершение журнала);не вызвать Next — значит замкнуть цепь — middleware может ответить напрямую или удержать Connection для асинхронной обработки. Ключевая деталь контракта: Next действительна только внутри текущего синхронного стека обратных вызовов (нельзя сохранять в поток/Future/корутину), вызывается максимум один раз (повтор = путь ошибки) — асинхронное middleware должно замыкать цепь и удерживать Connection само, а не откладывать Next. Вторая половина главы — слой статических файлов: ответ одним файлом, отображение каталогов с песочницей (http_server_static — серверное приземление идеи dir-песочницы главы 47).
105.1引入
Ценность луковичной модели — в паре «до + после»: middleware журналирования до вызывает помечает старт, зовёт Next, после возврата помечает финал и статус — один слой кода накрывает весь жизненный цикл запроса. Но асинхронность ломает наивную луковицу: если middleware хочет «спросить кэш и решить, пускать ли» (асинхронный запрос), когда звать Next? Ответ контракта — не звать — замкнуть цепь и удержать Connection; по завершении асинхронности ответить самому или вручную диспетчеризировать на финальный маршрут. Дисциплина идёт от технического ограничения: Next — дескриптор кадра синхронного стека — сохранить его в другом месте и позвать позже значит войти заново в машину состояний (тот же запрет, что у обратных вызовов Select главы 86).
Статические файлы — другой род сквозной задачи: почти каждому сервису надо «отправить файл» — favicon, артефакты фронтенд-сборки, архивные загрузки. Ручное «прочитать файл + угадать тип + ответить Range» — традиционный номер каждого C-официанта; http_server_static стандартизирует это: один файл, отображение префикса каталога, согласование Range (Range главы 91 в серверной форме) — всё в одном месте.
105.2概念
Регистрация и луковица
Два способа регистрации: Use (данными управляет вызывающий) и UseOwned (после успешной регистрации ответственность Release(Data) переходит к Router — разрушение последней ссылки освобождает в обратном порядке регистрации).Возврат обратного вызова: true = этот слой принимает запрос; false = неустранимая прикладная ошибка — среда выполнения посильно фиксирует фиксированные 500, при уже зафиксированном ответе — аварийное закрытие. Количество middleware ограничено потолком MaxRoutes Router; рост регистрации атомарен (провал не меняет видимого числа и не передаёт ответственность за очистку).
Точная семантика Next
Три легко неправильно понятых пункта. (1) Возврат Next ≠ ответ дописан: возврат означает лишь возврат диспетчеризации следующего слоя — синхронный прямой ответ обычно уже вошёл в состояние XHTTP_CONN_RESPONSE, а асинхронный маршрут может ещё ждать; хронометраж полного ответа ведите по пути завершения ответа Connection. (2) Действительна внутри стека: xhttpservernext действительна только в текущем синхронном стеке обратного вызова — сохранить её в другой поток/Future/корутину и позвать — неопределённое поведение; повторный вызов выставляет XHTTP_SERVER_MIDDLEWARE_ERROR_NEXT и входит в путь ошибки. (3) Проброс параметров: на совпавшем маршруте middleware получает те же Params/Count, что видит финальный маршрут (при несовпадении — пусто) — middleware может видеть параметры маршрута (id в вашем журнале).
Сотрудничество с 404/405/OPTIONS
Unified-журналу и обработчику ошибок надо видеть 404/405 и автоматические OPTIONS — при включённом хотя бы одном middleware тело несовпавшего запроса сперва отбрасывается, затем запрос входит в полную цепочку (слой журнала видит полную форму запроса); без middleware несовпавший запрос отвечает напрямую на стадии Headers, тело отвергается (удобный быстрый путь). Включение меняет «глубину обработки несовпавших» — ровно это и есть исполнение «middleware принимает сквозные задачи на себя».
Слой статических файлов
http_server_static даёт отображение файлов на маршруты:
- Один файл: семейство
xrtHttpConnFile— один путь на один файл (favicon, robots и компания); Range-вариантxrtHttpConnFileRangeна том же уровне. - Отображение каталога: префикс URL ↔ каталог на диске —
/static/*отображается на каталог артефактов сборки; защита от выхода за пределы (атаки../перехватываются в слое отображения — идея dir-песочницы главы 47). - Согласование: Content-Type по расширению (база MIME), ETag/Last-Modified с условными запросами (путь 304), запросы Range (зеркало
http_cache_rangeна сервере — докачка). - Путь ответа: файловые ответы идут через файловый источник тела
ReplyBody(путь прямого ответа главы 104) — отправка без копирования (семейство SendFile с передачей напрямую в ядро из главы 68 исполняется на транспортном слое).
Единство пути диспетчеризации
Фиксированный Router и Host Mux (мультиплексор, ветвящийся по заголовку Host, — один сервис на много доменных имён) используют один и тот же путь диспетчеризации — таблица middleware и таблица маршрутов замораживаются вместе в совместное чтение. Диспетчеризация горячего пути без блокировок (та же философия производительности, что и фрагментация пула главы 99). Синтаксис захвата сегментов {name} в образцах маршрутов (http_route.h) родствен захвату {name} ограниченного родича pattern главы 30 — заморозка и есть точка, где «скомпилировать раз, на горячем пути только смотреть» приземляется в слой маршрутизации.
105.3示例
Первая полная программа: полная сборка middleware + маршруты
Программа ниже из examples/http/server_middleware — middleware журналирования оборачивает health-маршрут:
#include <stdio.h> #include <xrt/http_server_middleware.h> /* 请求日志层在 Next 前后包裹路由,短路时也可以直接提交响应。 */ static bool exampleHttpMiddlewareLog( xhttpserver* pServer, xhttpconn* pConnection, const xhttpserverrequest* pRequest, const xhttprouteparam* pParams, size_t iParamCount, xhttpservernext* pNext, ptr pData ) { xstrview Method = xrtHttpServerRequestMethod(pRequest); xstrview Target = xrtHttpServerRequestTarget(pRequest); bool bResult; (void)pServer; (void)pConnection; (void)pParams; (void)iParamCount; (void)pData; printf( "> %.*s %.*s\n", (int)Method.Size, Method.Data, (int)Target.Size, Target.Data ); bResult = xrtHttpServerNext(pNext); printf("< state=%d\n", (int)xrtHttpConnState(pConnection)); return bResult; } /* 常用业务路由直接提交固定 JSON,不要求构造响应对象。 */ static void exampleHttpMiddlewareRoute( xhttpserver* pServer, xhttpconn* pConnection, const xhttpserverrequest* pRequest, const xhttprouteparam* pParams, size_t iParamCount, ptr pData ) { (void)pServer; (void)pRequest; (void)pParams; (void)iParamCount; (void)pData; (void)xrtHttpConnReply( pConnection, XHTTP_STATUS_OK, XRT_STR_LITERAL("application/json; charset=utf-8"), XRT_BYTES_LITERAL("{\"code\":200,\"msg\":\"OK\"}") ); } /* 展示中间件先注册、路由后注册、最后一次冻结的完整构造顺序。 */ int main(void) { xhttpserverrouter* pRouter = xrtHttpServerRouterCreate(NULL); if ( (pRouter == NULL) || !xrtHttpServerUse( pRouter, exampleHttpMiddlewareLog, NULL ) || !xrtHttpServerGet( pRouter, XRT_STR_LITERAL("/health"), exampleHttpMiddlewareRoute, NULL ) || !xrtHttpServerRouterFreeze(pRouter) ) { xrtHttpServerRouterDestroy(pRouter); return 1; } printf( "middleware: %u, routes: %u\n", (unsigned)xrtHttpServerMiddlewareCount(pRouter), (unsigned)xrtHttpServerRouterCount(pRouter) ); xrtHttpServerRouterDestroy(pRouter); return 0; }
Что только что произошло. (1) Порядок сборки и есть луковичный порядок: Use(日志中间件) (middleware журналирования) регистрируется первым, Get("/health", 路由) (маршрут) вторым, RouterFreeze замораживает — middleware всегда снаружи маршрутов (порядок регистрации и есть порядок оборачивания). (2) Шаблон формы middleware журналирования: до печатает > 方法 目标 (метод цель) -> xrtHttpServerNext(pNext) входит в следующий слой (health-маршрут отвечает JSON) → после возврата печатает < state — xrtHttpConnState показывает ответ уже зафиксирован (синхронный прямой путь). (3) Вывод middleware: 1, routes: 1 эхом повторяет факты сборки — MiddlewareCount/RouterCount есть интроспекция конфигурации (та же поза «конфигурация проверяема», что и Stats главы 99). (4) В реальном сервисе этот Router вешается в конфигурацию диспетчеризации xhttpserverconfig — образец этой главы сфокусирован на слое сборки, потому сеть не поднимается.
Вторая полная программа: сервис статических файлов
Вторая программа из 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) Статический слой ставит отображение «путь → файл» в систему маршрутов — регистрация одного файла (класс favicon) и отображение префикса каталога (/static/*) имеют свои входы; суждение расширение→MIME встроено. (2) Файловые ответы идут через файловый источник ReplyBody главы 104 — без чтения в память, ноль копий на транспортном слое (форма памяти сервиса больших файлов не зависит от размера файла). (3) Условные запросы (If-None-Match/If-Modified-Since) и Range согласуются внутри слоя — 304 и 206 автоматические пути; examples/http/static_path (отображение каталога и защита от выхода), static_multipart_body (локально собранные multipart-ответы), server_file (форма работающего сервиса) покрывают остальные грани статического семейства.
105.4契约
- Луковичный порядок: вход в порядке регистрации, выход в обратном; сперва middleware, потом маршруты; после заморозки — совместное чтение (фиксированный Router и Host Mux на одном пути диспетчеризации).
- Семантика Next: действительна в синхронном стеке, максимум один раз; возврат = возврат диспетчеризации ≠ завершение I/O; повтор = путь ошибки.
- Замыкание цепи: не вызвать Next — замкнуть — ответить напрямую или удержать Connection для асинхронности; асинхронное middleware обязано замыкать, а не откладывать Next.
- Возврат обратного вызова: true = принято; false = неустранимая ошибка → посильно 500 (при зафиксированном ответе — аварийное закрытие).
- Проброс параметров: middleware видят те же Params, что и финальный маршрут; дескриптор и Next только заимствуют текущий стек.
- Глубина несовпавших: с middleware тело несовпавших сперва отбрасывается, затем входит в цепь (журнал видит 404/405/OPTIONS); без — прямой ответ на стадии Headers.
- Потолки и атомарность: число middleware ограничено MaxRoutes; рост регистрации атомарен; UseOwned освобождает в обратном порядке регистрации.
- Статический слой: один файл/отображение каталога/защита от выхода/MIME/условные запросы/Range встроены; файловые ответы идут путём без копий.
- Обрезка:
HTTP_SERVER_MIDDLEWARE/HTTP_SERVER_STATIC/HTTP_SERVER_FILEнезависимые макросы.
Типичные ошибки
Симптом: перемежающиеся сбои машины состояний или MIDDLEWARE_ERROR_NEXT — обратный вызов асинхронного завершения позвал Next, сохранённый давно.
Причина: xhttpservernext — дескриптор кадра синхронного стека. Правильная асинхронная форма — замкнуть цепь + управлять соединением самому, а не носить синхронный дескриптор в другое место.
static bool onMiddleware(..., xhttpservernext* pNext, ...) { /* хотим продолжить после асинхронного lookup-а кэша */ g_SavedNext = pNext; /* сохранили дескриптор: кадр стека давно мёртв */ async_lookup(later_call_next); /* вызов позже: неопределённое поведение */ }
static bool onMiddleware(xhttpserver* pS, xhttpconn* pC, const xhttpserverrequest* pReq, const xhttprouteparam* pP, size_t iP, xhttpservernext* pNext, ptr pD) { /* замыкание: без вызова Next; удерживаем соединение для асинхронного решения */ async_cache_lookup(pC, pReq); return true; /* этот слой принял - ответ фиксируется асинхронным путём */ }
Симптом: в журнале все длительности суб-миллисекундные — ибо в момент возврата Next асинхронный маршрут ещё ждёт; «длительность» измерила только стек диспетчеризации.
Причина: возврат Next = возврат диспетчеризации следующего слоя; когда байты ответа дописаны — другой вопрос. Полный хронометраж — по пути завершения ответа Connection (события Close/осушения ответа).
uint64_t t0 = now(); Next(pNext); log_duration(now() - t0); /* меряет время стека, а не время запроса */
/* до: пометить старт (хранить по идентификатору запроса); парный хронометраж в обратном вызове завершения ответа */ on_request_start(pConn); /* записать */ Next(pNext); /* момент завершения сверяется на пути завершения ответа (например, финальное состояние перед Close) */
Симптом: запросы вроде /static/../../etc/passwd действительно читают системные файлы — самописное отображение склеивает пути строками без нормализации.
Причина: перед склейкой путь обязан проходить проверку «после нормализации всё ещё внутри корня» — ровно та защита, что встроена в отображение каталога http_server_static; обходя слой и склеивая пути сами, вы обходите и защиту.
snprintf(Path, "%s/%s", RootDir, UrlPath); /* склейка без проверки */ send_file(Path); /* ../ прорывается насквозь */
/* брать отображение каталога статического слоя: защита от выхода/нормализация встроены */ /* или при самостоятельном управлении: сперва нормализация (UrlPathPathNormalize, глава 102), потом проверка префикса */ if ( !path_inside_root(RootDir, Normalized) ) { reply_404(); }
Практические задания
Зарегистрируйте middleware журналирования, хронометража и CORS, оборачивающие один маршрут, — печатайте метки до и после каждого слоя и наблюдайте порядок входа/выхода. Критерии приёмки: последовательность вывода строго «вход в порядке регистрации, выход в обратном»; после замыкания любого слоя внутренние слои больше не исполняются.
Реализуйте middleware auth_required: проверяйте заголовок Authorization; отсутствует/недействителен — прямо 401 (замыкание); действителен — разбор токена в контекст запроса для маршрута. Сверьте с семейством схем аутентификации главы 106. Критерии приёмки: защищённый маршрут без токена 401, с токеном достижим; при замыкании middleware маршрут не исполняется.
Отображение каталога + согласование в middleware: ETag-middleware (генерация на первом ответе, сравнение на условных запросах — 304) + Range статического слоя. curl-ом проверьте три пути (первый 200, повторный 304, докачка 206). Критерии приёмки: статусы и байты трёх классов ответов верны; 304 без тела; фрагменты Range совпадают со смещениями файла.
Итоги
| Тема | Кратко |
|---|---|
| Луковичная модель | вход в порядке регистрации, выход в обратном; сперва middleware, потом маршруты; Freeze — совместное чтение |
| Семантика Next | действительна в синхронном стеке, максимум один раз; возврат = возврат диспетчеризации ≠ завершение I/O |
| Замыкание цепи | без вызова Next — ответить напрямую или удержать соединение для асинхронности; асинхронное middleware обязано замыкать |
| UseOwned | ответственность Release переходит к Router; разрушение последней ссылки освобождает в обратном порядке регистрации |
| Возврат обратного вызова | true принимает / false неустранимо → посильно 500 |
| Проброс параметров | middleware и маршруты видят одни и те же Params; дескриптор заимствует только текущий стек |
| Глубина несовпавших | с middleware: тело сперва отбрасывается, потом в цепь; без: прямой ответ на стадии Headers |
| Статический слой | один файл/отображение каталога/защита от выхода/MIME/условные запросы/Range; ответы без копий |
| Единство диспетчеризации | фиксированный Router и Host Mux на одном пути; горячий путь без блокировок |
| Обрезка | MIDDLEWARE/STATIC/FILE независимые макросы |
📖 Справочники API в этой главе:xhttp-http_server_middleware (4 записей)xhttp-http_server_static (6 записей)xhttp-http_server_router (17 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки