mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
The Headers and Response constructors combine duplicate entries (a: x,
a: "") into one value with the separator, which can end in its space
("x, "). They marked the pairs canonical anyway, so a copy took them
unchanged where Node trims the copied value ("x,"). The constructors
now mark the pairs canonical only when every value is already trimmed;
otherwise copies normalize them pair by pair.
126 lines
4.7 KiB
TypeScript
126 lines
4.7 KiB
TypeScript
// Headers copies and the cached sort-and-combine list: copies are
|
|
// independent of their source, reads see every mutation, and live
|
|
// iterators re-read the current list after a change.
|
|
function dump(label: string, h: Headers): void {
|
|
const parts: string[] = [];
|
|
h.forEach((value, name) => parts.push(`${name}=${value}`));
|
|
console.log(`${label}: ${parts.join(" | ")}`);
|
|
}
|
|
|
|
// The corpus lib has no DOM iterable typings for Headers, so iterators
|
|
// are reached through an untyped view.
|
|
const iter = (h: Headers): any => h;
|
|
|
|
const source = new Headers({ "X-B": " two ", "x-a": "one", Accept: "text/html" });
|
|
source.append("accept", "application/json");
|
|
source.append("Set-Cookie", "a=1");
|
|
source.append("set-cookie", "b=2");
|
|
dump("source", source);
|
|
|
|
// A Headers copy keeps the combined values and stays separate.
|
|
const copy = new Headers(source);
|
|
source.set("x-a", "changed");
|
|
copy.append("x-c", "three");
|
|
dump("source after set", source);
|
|
dump("copy after append", copy);
|
|
console.log(JSON.stringify(copy.get("accept")), JSON.stringify(copy.get("set-cookie")));
|
|
console.log(JSON.stringify(copy.getSetCookie()));
|
|
|
|
// Copying into an empty Response and a Request, then appending a repeat.
|
|
const response = new Response("body", { headers: copy, status: 201 });
|
|
response.headers.append("accept", "*/*");
|
|
response.headers.append("cookie", "c=3");
|
|
response.headers.append("Cookie", "d=4");
|
|
dump("response", response.headers);
|
|
dump("copy unchanged", copy);
|
|
const clone = new Response(response.body, response);
|
|
dump("response clone", clone.headers);
|
|
const request = new Request("http://example.test/", { headers: copy });
|
|
console.log("request x-b", JSON.stringify(request.headers.get("X-B")));
|
|
|
|
// Every read after a mutation sees it: get, has, iteration, spread.
|
|
const h = new Headers([["x-z", "z"], ["x-m", "m"]]);
|
|
console.log([...iter(h).entries()].map(([n, v]) => `${n}:${v}`).join(","));
|
|
h.delete("x-z");
|
|
console.log([...iter(h).keys()].join(","), h.has("x-z"));
|
|
h.delete("x-missing");
|
|
console.log([...iter(h).values()].join(","));
|
|
h.append("x-a", "first");
|
|
console.log([...iter(h).entries()].map(([n, v]) => `${n}:${v}`).join(","));
|
|
h.set("X-M", "again");
|
|
console.log(JSON.stringify(h.get("x-m")), [...iter(h).keys()].join(","));
|
|
|
|
// A live iterator: mutations between steps change what comes next.
|
|
const it = iter(h).entries();
|
|
console.log(JSON.stringify(it.next().value));
|
|
h.append("x-0", "before");
|
|
console.log(JSON.stringify(it.next().value));
|
|
h.delete("x-m");
|
|
h.append("x-zz", "after");
|
|
console.log(JSON.stringify(it.next()), JSON.stringify(it.next()));
|
|
|
|
// forEach that mutates as it walks.
|
|
const walk = new Headers([["b", "2"], ["d", "4"]]);
|
|
const seen: string[] = [];
|
|
walk.forEach((value, name) => {
|
|
seen.push(`${name}=${value}`);
|
|
if (name === "b") {
|
|
walk.append("c", "3");
|
|
walk.append("a", "1");
|
|
}
|
|
if (name === "c") walk.set("d", "four");
|
|
});
|
|
console.log(seen.join(" | "));
|
|
|
|
// Invalid input in a copy source is still rejected (plain object source).
|
|
try {
|
|
new Response("x", { headers: { "bad name": "v" } });
|
|
} catch (error) {
|
|
console.log((error as Error).name, (error as Error).message);
|
|
}
|
|
try {
|
|
new Headers(source).append("x", "a\nb");
|
|
} catch (error) {
|
|
console.log((error as Error).name, (error as Error).message);
|
|
}
|
|
|
|
// Appending an empty value leaves the separator's space at the end of the
|
|
// combined value; a copy trims it again, as Node's does. A leading
|
|
// separator has no whitespace to trim.
|
|
const spaced = new Headers();
|
|
spaced.append("a", "x");
|
|
spaced.append("a", "");
|
|
spaced.append("b", "");
|
|
spaced.append("b", "y");
|
|
spaced.append("c", "");
|
|
spaced.append("c", "");
|
|
spaced.append("cookie", "k=v");
|
|
spaced.append("cookie", "");
|
|
const spacedList = (headers: Headers): string => {
|
|
const out: string[] = [];
|
|
headers.forEach((value, name) => out.push(`${name}=${JSON.stringify(value)}`));
|
|
return out.join(" ");
|
|
};
|
|
console.log(spacedList(spaced));
|
|
console.log(spacedList(new Headers(spaced)));
|
|
console.log(spacedList(new Headers(new Headers(spaced))));
|
|
console.log(spacedList(new Response("", { headers: spaced }).headers));
|
|
console.log(spacedList(new Request("http://example.test/", { headers: spaced }).headers));
|
|
|
|
// The same through duplicate constructor entries, in a Headers object and
|
|
// in a Response's own headers.
|
|
const entries: [string, string][] = [
|
|
["a", "x"],
|
|
["a", ""],
|
|
["c", ""],
|
|
["c", ""],
|
|
["d", "plain"],
|
|
];
|
|
const built = new Headers(entries);
|
|
console.log(spacedList(built));
|
|
console.log(spacedList(new Headers(built)));
|
|
const builtResponse = new Response("", { headers: entries });
|
|
console.log(spacedList(builtResponse.headers));
|
|
console.log(spacedList(new Headers(builtResponse.headers)));
|
|
console.log(spacedList(new Request("http://example.test/", { headers: built }).headers));
|