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.
| test | heft | request | base | what differs from the base |
|---|---|---|---|---|
| forms.urlencoded | 2 | POST /forms/urlencoded | query.many | the same fields read from a urlencoded form body instead of the query string |
| forms.multipart | 3 | POST /forms/multipart | forms.urlencoded | a multipart body with a file part instead of a urlencoded one |
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.
POST /forms/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}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.
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 });
},
});
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.
POST /forms/multipart
--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 totalHTTP 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.
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"] }),
});