TeqFW: Web Server
Publication date: 2021-12-15The TeqFW web server can run in HTTP/1, HTTP/2, or HTTPS-over-HTTP/2 modes. HTTP/1 is convenient for local development, HTTP/2 can sit behind another server such as nginx, and HTTPS-over-HTTP/2 can serve as the public endpoint. The server is designed as a composable request pipeline supplied by Teq plugins.
HTTP fundamentals
HTTP is request-response: a start line, headers, and optionally a body; the response sends headers before its body. A body may be streamed over time, as with Server-Sent Events. Node.js exposes http and http2 servers through events such as request, connect, error, and close.
server.on('request', (req, res) => {
// Read req and write res.
});
Both request and response are streams. Headers must be decided before any response body is written.
TeqFW request processing
The request listener accepts HEAD, GET, and POST; other methods receive 405. For text/plain and application/json POST bodies, it reads and attaches text or parsed JSON to the request. Other content types remain streams for specialised handlers.
Plugins contribute request handlers. A handler may recognize a request, add headers, attach a text body to res.teqBody, or set a file path in res.teqFile. The final handler sends headers first and then streams the file or ends with the body. If no handler claims the request, it sends 404.
Dispatcher and handlers
TeqFw_Web_Back_Server_Dispatcher discovers and initializes handlers before attaching its listener:
await dispatcher.createHandlers();
server.on('request', dispatcher.getListener());
A handler implements initialization, ownership test, and a processor:
class RequestHandler {
async init() {}
requestIsMine({ method, address, headers } = {}) {}
getProcessor() { return async (req, res) => {}; }
}
Plugin descriptors order handlers with before/after and reserve address spaces:
{
"@teqfw/web": {
"handlers": {
"Vendor_Back_Handler_Upload": {
"after": ["TeqFw_Web_Back_Handler_WAPI"],
"before": ["TeqFw_Web_Back_Handler_Static"],
"space": ["upload"]
}
}
}
}
Processors run in order and can enrich req or res for later processors. A handler that sends a response itself must make that finality clear; later handlers check res.headersSent.
Final response
The final handler sends the accumulated headers and chooses a file or body. It streams existing files and responds with 404 when no body or file is available. This separation lets earlier handlers focus on routing and business work while keeping HTTP response formation consistent.
Built-in handler roles
SSE: server-to-browser event streams;WAPI: JSON Web API over GET/POST;Upload: file upload;Static: static resources;Final: final HTTP response.
They reserve spaces such as sse, api, upload, src, and web. The address model parses the URL and lets handlers claim only their own area.
Static resources
Static content has two main groups:
src: source files from npm packages innode_modules;web: arbitrary files in a Teq plugin’sweb/directory.
Plugin autoload metadata maps a namespace to sources. Thus a URL such as /src/@vendor/package/Path/To/Module.mjs can map to a source file in the corresponding package. Non-Teq dependencies can be mapped explicitly, for example to expose selected Vue distribution files. Only intended static roots should be exposed; never turn a broad filesystem path into a public URL space.
Web API, SSE, and uploads
WAPI owns /api/, accepts GET and POST, and returns JSON. Plugins register API modules in their descriptor, usually beneath a namespace-based route. SSE and uploads require extra product decisions: authentication, authorization, ownership of a shared event channel, limits, content validation, and storage policy. Unused handlers can be excluded in application configuration.
Summary
TeqFW provides HTTP/1, HTTP/2, and HTTPS serving through a pipeline of pluggable handlers for static content, JSON APIs, SSE, and uploads. The architecture lets plugins add capabilities without changing the dispatcher, while address-space ownership and a final response handler keep request processing predictable. For an Internet-facing deployment, use TLS, authentication, request-size limits, validation, logging, and a reverse proxy or equivalent operational controls where appropriate.
Additional source-code excerpts
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” ] } }
{
} } }