← tests

forms

Request bodies that are not JSON: a urlencoded form and a multipart upload, each bound through the framework's own form support.

Comparable across every framework. The urlencoded row answers what query.many answers, so the pair differs only in where the eight fields came from.

A value written {run.name} is drawn once per run and never given to the framework, so it cannot answer from a table.

testheftrequestbasewhat differs from the base
forms.urlencoded2POST /forms/urlencodedquery.manythe same fields read from a urlencoded form body instead of the query string
forms.multipart3POST /forms/multipartforms.urlencodeda multipart body with a file part instead of a urlencoded one

forms.urlencoded

heft 2

base query.manybase of forms.multipart

query.many's eight fields posted as an application/x-www-form-urlencoded body instead of a query string, bound and echoed. The answer is exactly what query.many answers, so the difference between the two is the form parser against the query parser.

Request

POST /forms/urlencoded
content-typeapplication/x-www-form-urlencoded
page={run.page}&size={run.size}&status={run.status}&category={run.category}&sort={run.sort}&q={run.q}&minPrice={run.minPrice}&maxPrice={run.maxPrice}

Expected response

HTTP 200

payload items.small

{"size":"small","count":1,"items":[{"id":1,"name":"slate-lamp-6647","category":"tools","priceCents":18928,"inStock":true}],"echo":{"page":{run.page},"size":{run.size},"status":"{run.status}","category":"{run.category}","sort":"{run.sort}","q":"{run.q}","minPrice":{run.minPrice},"maxPrice":{run.maxPrice}}}

Compared as parsed JSON, so key order and how a number is written do not matter.

Test source, tests/forms/urlencoded.ts
tests/forms/urlencoded.tsopen on GitHub →
import { performanceTest } from "#kit";
import { items } from "#payloads";

const path = "/forms/urlencoded";
const FIELDS = ["page", "size", "status", "category", "sort", "q", "minPrice", "maxPrice"] as const;

export default performanceTest({
  id: { family: "forms", name: "urlencoded" },
  path,
  base: "query.many",
  varies: "form",
  heft: 2,
  about:
    "query.many's eight fields posted as an application/x-www-form-urlencoded " +
    "body instead of a query string, bound and echoed. The answer is exactly " +
    "what query.many answers, so the difference between the two is the form " +
    "parser against the query parser.",

  request: (c) => {
    const form = new URLSearchParams(FIELDS.map((name) => [name, String(c.run[name])])).toString();
    return c.post(path).raw(form, "application/x-www-form-urlencoded").okWith(items.small, { echo: FIELDS });
  },
});

forms.multipart

heft 3

base forms.urlencoded

A multipart/form-data upload of two fields and a 32 KB text file. The handler echoes the fields and answers the file's name and byte count, so it has to have read the whole part. Read against forms.urlencoded, the difference is the multipart parser and a body of 32 KB instead of a line.

Request

POST /forms/multipart
content-typemultipart/form-data; boundary=rb-7c4f1e0a9d
--rb-7c4f1e0a9d
Content-Disposition: form-data; name="tenant"

{run.tenant}
--rb-7c4f1e0a9d
Content-Disposition: form-data; name="requestId"

{run.requestId}
--rb-7c4f1e0a9d
Content-Disposition: form-data; name="file"; filename="forms.file.txt"
Content-Type: text/plain

id,name,category,priceCents,inStock
1,slate-lamp-6647,tools,18928,true
2,brass-hinge-5716,garden,17577,true
3,linen-ring-6971,kitchen,5540,true
4,copper-bolt-9217,outdoor,11562,true
5,linen-lamp-8288,office,1413,true
6,copper-pan-9308,tools,10795,true
7,slate-seed-3051,garden,9667,false
8,oak-lamp-8662,kitchen,18032,false
9,amber-hinge-8405,outdoor,9566,true
10,linen-lamp-9676,office,4288,true
11,brass-trowel-2042
… 33,066 bytes total

Expected response

HTTP 200

payload forms.file.txt as received

{"file":{"name":"forms.file.txt","bytes":32762},"echo":{"tenant":"{run.tenant}","requestId":"{run.requestId}"}}

Compared as parsed JSON, so key order and how a number is written do not matter.

Test source, tests/forms/multipart.ts
tests/forms/multipart.tsopen on GitHub →
import { performanceTest } from "#kit";
import type { RunValues } from "#kit";
import { forms, uploaded } from "#payloads";

const path = "/forms/multipart";
const BOUNDARY = "rb-7c4f1e0a9d";

function part(disposition: string, body: string, type?: string): string {
  const typed = type === undefined ? "" : `Content-Type: ${type}\r\n`;
  return `--${BOUNDARY}\r\nContent-Disposition: form-data; ${disposition}\r\n${typed}\r\n${body}\r\n`;
}

/** Built once per run's values, so the 32 KB concatenation is not inside every instance's timed window. */
const bodies = new WeakMap<RunValues, string>();

function multipart(run: RunValues): string {
  let body = bodies.get(run);
  if (body === undefined) {
    body =
      part('name="tenant"', run.tenant) +
      part('name="requestId"', run.requestId) +
      part(`name="file"; filename="${forms.file.name}"`, forms.file.value, "text/plain") +
      `--${BOUNDARY}--\r\n`;
    bodies.set(run, body);
  }
  return body;
}

export default performanceTest({
  id: { family: "forms", name: "multipart" },
  path,
  base: "forms.urlencoded",
  varies: "multipart",
  heft: 3,
  about:
    "A multipart/form-data upload of two fields and a 32 KB text file. The " +
    "handler echoes the fields and answers the file's name and byte count, so " +
    "it has to have read the whole part. Read against forms.urlencoded, the " +
    "difference is the multipart parser and a body of 32 KB instead of a " +
    "line.",

  request: (c) =>
    c
      .post(path)
      .raw(multipart(c.run), `multipart/form-data; boundary=${BOUNDARY}`)
      .okWith(uploaded, { echo: ["tenant", "requestId"] }),
});