denoland/deno · error · NodeTypeError

ERR_INVALID_HTTP_TOKEN

ERR_INVALID_HTTP_TOKEN

Error message

Trailer name must be a valid HTTP token ["${field}"]

What it means

OutgoingMessage#addTrailers iterates the supplied headers object (or [name, value] pairs) and requires every field name to be a non-empty string that passes checkIsHttpToken — the RFC 7230 token charset. Failing names (including non-strings such as numbers from object keys, empty strings, or names with separators/unicode) throw ERR_INVALID_HTTP_TOKEN for 'Trailer name'. The trailer string is assembled into this._trailer for the terminating chunk.

Source

Thrown at ext/node/polyfills/_http_outgoing.ts:542

    configurable: true,
  },
  addTrailers: {
    __proto__: null,
    value: function addTrailers(headers: any) {
      this._trailer = "";
      const keys = ObjectKeys(headers);
      const isArray = ArrayIsArray(headers);
      let field, value;
      for (let i = 0, l = keys.length; i < l; i++) {
        if (isArray) {
          field = headers[keys[i]][0];
          value = headers[keys[i]][1];
        } else {
          field = keys[i];
          value = headers[field];
        }
        if (typeof field !== "string" || !field || !checkIsHttpToken(field)) {
          throw new ERR_INVALID_HTTP_TOKEN("Trailer name", field);
        }
        if (checkInvalidHeaderChar(value)) {
          debug('Trailer "%s" contains invalid characters', field);
          throw new ERR_INVALID_CHAR("trailer content", field);
        }
        this._trailer += field + ": " + value + "\r\n";
      }
    },
    writable: true,
    enumerable: true,
    configurable: true,
  },
  end: {
    __proto__: null,
    value: function end(chunk: any, encoding: any, callback: any) {
      if (typeof chunk === "function") {
        callback = chunk;
        chunk = null;

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Use fixed, well-formed trailer names: 'content-md5', 'x-trace-id'
  2. Validate dynamic names against /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/ before addTrailers
  3. If the name cannot be a token, put the value in the body or a header instead of a trailer

Example fix

// before
res.addTrailers({ [metricName]: value }); // metricName = 'resp time'

// after
const TOKEN_RE = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
const name = TOKEN_RE.test(metricName) ? metricName : 'x-metric';
res.addTrailers({ [name]: value });
Defensive patterns

Strategy: validation

Validate before calling

const HTTP_TOKEN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
function validTrailerNames(t) {
  return Object.keys(t).every((k) => HTTP_TOKEN.test(k));
}
if (validTrailerNames(trailers)) res.addTrailers(trailers);

Type guard

const isTrailerTokenName = (n) => typeof n === 'string' && n.length > 0 && /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(n);

Try / catch

try { res.addTrailers(trailers); } catch (e) { if (e.code === 'ERR_INVALID_HTTP_TOKEN') { /* drop bad names or send plain headers */ } else throw e; }

Prevention

When it happens

Trigger: res.addTrailers({ 'Content-MD5 ': hash }) (trailing space); addTrailers(obj) where obj keys came from JSON with numeric-looking names; header names containing spaces or non-ASCII from dynamic generation.

Common situations: Generating trailer names from data (checksums, counters) without validating the charset; porting header objects whose keys are coerced by JSON parsing; copy-paste typos with invisible whitespace.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/0e78a03270e67989. Report an issue: GitHub.