Skip to content

About

Use @chubbyts/chubbyts-http-types on node.js: turns a node IncomingMessage into a ServerRequest and emits a Response to a node ServerResponse.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

chubbyts-http-node

CI Coverage Status Mutation testing badge npm-version

bugs code_smells coverage duplicated_lines_density ncloc sqale_rating alert_status reliability_rating security_rating sqale_index vulnerabilities

Description

Use @chubbyts/chubbyts-http-types on node.js: turns a node IncomingMessage into a ServerRequest and emits a Response to a node ServerResponse.

The bridge works with any implementation of the types: the server request gets created and changed through the injected UriFactory, ServerRequestFactory and RequestHelpers (or ServerRequestHelpers), for example the ones of @chubbyts/chubbyts-http.

Requirements

Installation

Through NPM as @chubbyts/chubbyts-http-node.

npm i @chubbyts/chubbyts-http-node@^1.1.2

Usage

import type { Server } from 'node:http';
import { createServer } from 'node:http';
import type { Handler } from '@chubbyts/chubbyts-http-types/handler';
import { createServerRequestFactory, createUriFactory } from '@chubbyts/chubbyts-http/message-factory';
import { createServerRequestHelpers } from '@chubbyts/chubbyts-http/message-helper';
import {
  createNodeRequestToServerRequestFactory,
  createResponseToNodeResponseEmitter,
} from '@chubbyts/chubbyts-http-node/node';

const serverHost = process.env.SERVER_HOST as string;
const serverPort = parseInt(process.env.SERVER_PORT as string);

const shutdownServer = (server: Server) => {
  server.close((err) => {
    if (err) {
      console.warn(`Shutdown server with error: ${err}`);
      process.exit(1);
    }

    console.log('Shutdown server');
    process.exit(0);
  });
};

const nodeRequestToServerRequestFactory = createNodeRequestToServerRequestFactory(
  // any implementation of @chubbyts/chubbyts-http-types
  createUriFactory(),
  createServerRequestFactory(),
  createServerRequestHelpers(),
  // optional: the base url (scheme, host, port and path prefix), defaults to the local address of the socket
  'https://example.com',
  // optional: the request body must be fully received within 30s
  30_000,
  // optional: the request body must not exceed 1MiB
  1024 * 1024,
);

// for example @chubbyts/chubbyts-framework app (which implements Handler)
declare const handler: Handler;

// argument (optional): the response must be fully sent within 60s
const responseToNodeResponseEmitter = createResponseToNodeResponseEmitter(60_000);

const server = createServer(async (req, res) => {
  try {
    const serverRequest = nodeRequestToServerRequestFactory(req, res);
    const response = await handler(serverRequest);
    responseToNodeResponseEmitter(response, res);
  } catch (error) {
    console.error(`Failed to handle request: ${error}`);

    // once headers are sent the response cannot be turned into a 500 anymore:
    // destroying the socket is the only way to signal the failure to the client
    if (res.headersSent) {
      res.destroy(error instanceof Error ? error : new Error(String(error)));

      return;
    }

    res.writeHead(500, { 'content-type': 'text/plain' }).end('Internal Server Error');
  }
});

server.listen(serverPort, serverHost, () => {
  console.log(`Listening to ${serverHost}:${serverPort}`);
});

process.on('SIGINT', () => shutdownServer(server));
process.on('SIGTERM', () => shutdownServer(server));

Server request

The server request gets created by serverRequestFactory(method, uriFactory(url)), then withProtocolVersion, withSignal, withHeader (for each header) and, except for GET and HEAD, withBody get applied:

  • the url gets built from the base url (scheme, host, port and path prefix) and the origin-form request target, without a base url from the local address and port of the socket. Request targets in another form get rejected.
  • headers keep every value of a repeated header (e.g. cookie), multiple authorization or proxy-authorization headers get rejected. http/2 pseudo-headers (:method, :path, :authority, :scheme) get skipped.
  • protocolVersion is the http version of node, 2.0 becomes 2.
  • body is the IncomingMessage itself (an AsyncIterable<Buffer>), with a request body max bytes limit a Transform piped from it, GET and HEAD keep the (empty) body of the factory.
  • signal gets aborted when the ServerResponse gets closed before it has been fully sent (client disconnected, response destroyed), which is why the factory takes the ServerResponse as well.

Response

The body gets piped into the ServerResponse. A node stream body is used as is and a web stream body gets converted with Readable.fromWeb, so both get stopped (destroyed / cancelled) when the client disconnects, even while a read is pending. Any other AsyncIterable can only be stopped once its pending chunk arrives.

An already destroyed (e.g. errored) node stream body lets the emitter throw before anything has been written, so the caller is still able to respond with an error (piping it would destroy the connection).

Limits and timeouts

Node does not limit the request body size itself, a client could otherwise fill the memory of a handler reading the whole body:

  • createNodeRequestToServerRequestFactory(uriFactory, serverRequestFactory, requestHelpers, baseUrl, requestBodyTimeoutMs, requestBodyMaxBytes): a request with a larger content-length gets rejected before reading the body, a chunked body (or one with a wrong content-length) while reading it. The request gets destroyed, which closes the connection (the client would otherwise keep on sending the body), and the error surfaces to the handler while iterating the body. The limit must be a non-negative safe integer, otherwise the factory throws.

Slow clients (slowloris) can otherwise tie up sockets indefinitely:

  • createNodeRequestToServerRequestFactory(uriFactory, serverRequestFactory, requestHelpers, baseUrl, requestBodyTimeoutMs): if the request body has not been fully received within the given time, the request gets destroyed and the error surfaces to the handler while iterating the body.
  • createResponseToNodeResponseEmitter(responseSendTimeoutMs): if the response has not been fully sent within the given time (slow reading client or stalling response body), the response gets destroyed.

The limit and both timeouts are optional and disabled by default. They only cover the body phases handled by this adapter, so make sure the node server level timeouts are configured as well: server.headersTimeout (slow header senders), server.requestTimeout and server.keepAliveTimeout.

Copyright

2026 Dominik Zogg

About

Use @chubbyts/chubbyts-http-types on node.js: turns a node IncomingMessage into a ServerRequest and emits a Response to a node ServerResponse.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages