TeqFW: servidor web
Fecha de publicación: 2021-12-15El servidor web TeqFW puede ejecutarse en HTTP/1, HTTP/2 o HTTPS sobre HTTP/2. HTTP/1 es práctico en desarrollo local, HTTP/2 puede estar detrás de otro servidor como nginx, y HTTPS-over-HTTP/2 puede actuar como endpoint público. El servidor se diseña como un pipeline de solicitudes componible, aportado por plugins Teq.
Fundamentos HTTP
HTTP es solicitud-respuesta: línea inicial, cabeceras y opcionalmente cuerpo; la respuesta envía cabeceras antes del cuerpo. El cuerpo puede transmitirse con el tiempo, como en Server-Sent Events. Node.js expone servidores http y http2 mediante eventos como request, connect, error y close.
server.on('request', (req, res) => {
// Read req and write res.
});
Solicitud y respuesta son streams. Las cabeceras deben decidirse antes de escribir cualquier cuerpo.
Procesamiento de solicitudes
El listener acepta HEAD, GET y POST; otros métodos reciben 405. Para cuerpos POST text/plain y application/json, lee y adjunta texto o JSON parseado. Otros tipos quedan como streams para handlers especializados.
Los plugins aportan handlers. Uno puede reconocer solicitud, añadir cabeceras, adjuntar cuerpo a res.teqBody o ruta de archivo a res.teqFile. El handler final envía primero cabeceras y después transmite archivo o termina con cuerpo. Si nadie reclama, responde 404.
Dispatcher y handlers
TeqFw_Web_Back_Server_Dispatcherdescubre e inicializa handlers antes de registrar su listener:
await dispatcher.createHandlers(); server.on('request', dispatcher.getListener());
Un handler implementa inicialización, comprobación de propiedad y procesador:
class RequestHandler {
async init() {}
requestIsMine({ method, address, headers } = {}) {}
getProcessor() { return async (req, res) => {}; }
}
Los descriptores ordenan handlers mediante before/after y reservan espacios de dirección:
{
"@teqfw/web": {
"handlers": {
"Vendor_Back_Handler_Upload": {
"after": ["TeqFw_Web_Back_Handler_WAPI"],
"before": ["TeqFw_Web_Back_Handler_Static"],
"space": ["upload"]
}
}
}
}
Los procesadores se ejecutan en orden y pueden enriquecer req o res para los siguientes. Un handler que responde por sí mismo debe dejarlo claro; los posteriores comprueban res.headersSent.
Respuesta final
El handler final envía cabeceras acumuladas y escoge archivo o cuerpo. Transmite archivos existentes y devuelve 404 si no hay ninguno ni cuerpo. La separación permite que los anteriores se centren en routing y negocio, manteniendo coherente la respuesta HTTP.
Roles integrados
SSE: eventos servidor-navegador;WAPI: Web API JSON sobre GET/POST;Upload: carga de archivos;Static: recursos estáticos;Final: respuesta HTTP final.
Reservan espacios como sse, api, upload, src y web. El modelo de dirección analiza URL y permite que cada handler reclame solo su área.
Recursos estáticos
Hay dos grupos principales:
src: fuentes de paquetes npm ennode_modules;web: archivos arbitrarios del directorioweb/de un plugin Teq.
Los metadatos de autoload asignan namespace a fuentes. Una URL como /src/@vendor/package/Path/To/Module.mjs puede mapear a archivo del paquete correspondiente. Dependencias no Teq se pueden mapear explícitamente, por ejemplo para exponer archivos Vue seleccionados. Solo se deben exponer raíces estáticas previstas; nunca convierta una ruta amplia del sistema de archivos en espacio URL público.
Web API, SSE y uploads
WAPI posee /api/, acepta GET y POST y devuelve JSON. Los plugins registran módulos de API en el descriptor bajo rutas con namespace. SSE y uploads requieren decisiones adicionales: autenticación, autorización, propiedad de canal compartido, límites, validación de contenido y política de almacenamiento. Los handlers no usados se pueden excluir en configuración.
Resumen
TeqFW ofrece HTTP/1, HTTP/2 y HTTPS mediante pipeline de handlers conectables para estáticos, API JSON, SSE y uploads. Los plugins añaden capacidades sin modificar dispatcher, mientras propiedad de espacios y handler final hacen predecible el procesamiento. En despliegue expuesto a Internet, use TLS, autenticación, límites de tamaño, validación, logs y proxy inverso o controles operativos equivalentes cuando corresponda.
Fragmentos adicionales de código fuente
function process(req, res) {} Подключение обработчиков происходит в дескрипторе teq-плагина (teqfw.json):
{
} } } }
/** @type {TeqFw_Web_Back_Model_Address} */ const mAddress = spec[‘TeqFw_Web_Back_Model_Address$’]; /** @type {TeqFw_Web_Back_Server_Respond.respond405|function} */ const respond405 = spec[‘TeqFw_Web_Back_Server_Respond.respond405’]; /** @type {TeqFw_Web_Back_Api_Request_IHandler[]} */ const handlers = []; async function onRequest(req, res) { function isMethodAllowed(method) { } async function parseBody(method, headers, req) { } const {headers, method, url} = req; if (isMethodAllowed(method)) { await parseBody(method, headers, req); const address = mAddress.parsePath(url); // collect processors const active = []; for (const one of handlers) if (one.requestIsMine({method, address, headers})) active.push(one.getProcessor()); // run processors one by one for (const one of active) await one(req, res); } else respond405(res); } В общем случае обработчик не должен сам отправлять ответ — ему неизвестно, какие ещё обработчики есть в очереди после него. Так как результатом обработки запроса является сообщение, состоящее из заголовков и тела запроса, то каждый обработчик может добавить свои заголовки вres(черезresponse.setHeader(name, value)), а тело ответа или имя файла для отправки клиенту сохранить вres[‘teqBody’]или вres[‘teqFile’]соответственно. Если обработчик считает, что его данных достаточно для формирования ответа, он добавляет код ответа вres[‘teqStatus’].
function process(req, res) {
if (!res.headersSent) {
const headers = res.getHeaders();
const statusCode = res[DEF.RES_STATUS] ?? HTTP_STATUS_OK;
const file = res[DEF.RES_FILE];
const body = res[DEF.RES_BODY];
let stat;
if (file) {
if (
existsSync(file)
) {
// …
const readStream = createReadStream(file);
res.writeHead(statusCode, headers);
pipeline(readStream, res);
}
} else if (body) {
res.writeHead(statusCode, headers);
res.end(body);
} else respond404(res);
}
}
{
} } } }
/** @type {TeqFw_Web_Back_Model_Address} */ const mAddress = await container.get(‘TeqFw_Web_Back_Model_Address$’); //… const address = mAddress.parsePath(url); if (address?.space === ‘api’) { /* … */}
{
“path”: “./sources” } } }
{
“/vue/”: “/vue/dist/” } } }
./node_modules/@vnd/pkg/web/css/style.css
{
“TeqFw_Web_Back_WAPI_Load_Namespaces” ] } }
{
} } }