mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Read Web Headers pairs without copying them, and cache their sorted list
Headers kept its name/value pairs in a string array and read every element through scr_arr_get_ref (index checks, retain, release). forEach and the iterators re-sorted and rescanned the whole list for every step, and every Response or Headers built from another Headers object re-validated and re-normalized each copied pair. The Hono fixture spends 12-15% of a request there. - Pair loops read borrowed elements of the dense array (sf_pair_at). - The sort-and-combine list is cached on the Headers object and dropped by every mutation, so live iteration keeps Node's semantics. - get() of a single value returns the stored string. - Copying a Headers object whose pairs are canonical (built by the constructor or a Response) into an empty list copies the pairs; wire response headers keep the per-pair path because they may repeat names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8476f68136
commit
056dda3633
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/fetch-headers-copy-cache.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+144
-124
@@ -172,7 +172,14 @@ struct SfStream {
|
||||
struct SfHeaders {
|
||||
size_t rc;
|
||||
ScrArr *pairs;
|
||||
/* Fetch's "sort and combine" list of `pairs` as flat name/value pairs,
|
||||
* built on the first iteration step and dropped by every mutation. */
|
||||
ScrArr *sorted;
|
||||
bool immutable;
|
||||
/* `pairs` is in the form sf_add_converted_headers builds: lowercase valid
|
||||
* names, trimmed valid UTF-8 values, one combined pair per name except
|
||||
* set-cookie. Headers built from wire lines are not (duplicates stay). */
|
||||
bool canonical;
|
||||
};
|
||||
|
||||
struct SfResponse {
|
||||
@@ -2164,6 +2171,24 @@ static ScrDyn *sf_controller_get(void *ptr, const char *key, size_t len) {
|
||||
|
||||
static bool sf_header_name_ok(const char *s, size_t len);
|
||||
|
||||
/* A borrowed element of a runtime-built header pair array. These arrays are
|
||||
* only ever appended to or rewritten whole, so they are dense. */
|
||||
static inline ScrStr *sf_pair_at(ScrArr *pairs, size_t i) {
|
||||
if (i < pairs->filled) return (ScrStr *)(uintptr_t)scr_arr_dense_get(pairs, i);
|
||||
return (ScrStr *)scr_arr_peek_ref(pairs, (double)i);
|
||||
}
|
||||
|
||||
static ScrStr *sf_header_name_lower(const char *data, size_t len);
|
||||
|
||||
/* `name` in lowercase, +1: the same string when it has no uppercase byte. */
|
||||
static ScrStr *sf_header_name_lower_str(ScrStr *name) {
|
||||
for (size_t i = 0; i < name->len; i++) {
|
||||
char c = name->data[i];
|
||||
if (c >= 'A' && c <= 'Z') return sf_header_name_lower(name->data, name->len);
|
||||
}
|
||||
return scr_str_retain(name);
|
||||
}
|
||||
|
||||
static ScrStr *sf_header_name_lower(const char *data, size_t len) {
|
||||
char *lower = malloc(len ? len : 1);
|
||||
if (!lower) sf_oom();
|
||||
@@ -2232,13 +2257,8 @@ static SfHeaders *sf_headers_new_response(ScrArr *raw_pairs) {
|
||||
size_t n = (size_t)scr_arr_len(raw_pairs);
|
||||
ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, n);
|
||||
for (size_t i = 0; i + 1 < n; i += 2) {
|
||||
ScrStr *raw_name = scr_arr_get_ref(raw_pairs, (double)i);
|
||||
scr_arr_push_ref(
|
||||
pairs, sf_header_name_lower(raw_name->data, raw_name->len));
|
||||
scr_str_release(raw_name);
|
||||
ScrStr *raw = scr_arr_get_ref(raw_pairs, (double)(i + 1));
|
||||
scr_arr_push_ref(pairs, sf_latin1_to_utf8(raw));
|
||||
scr_str_release(raw);
|
||||
scr_arr_push_ref(pairs, sf_header_name_lower_str(sf_pair_at(raw_pairs, i)));
|
||||
scr_arr_push_ref(pairs, sf_latin1_to_utf8(sf_pair_at(raw_pairs, i + 1)));
|
||||
}
|
||||
scr_arr_release(raw_pairs);
|
||||
return sf_headers_new(pairs, true);
|
||||
@@ -2252,9 +2272,17 @@ static SfHeaders *sf_headers_retain(SfHeaders *h) {
|
||||
static void sf_headers_release(SfHeaders *h) {
|
||||
if (!h || --h->rc > 0) return;
|
||||
scr_arr_release(h->pairs);
|
||||
if (h->sorted) scr_arr_release(h->sorted);
|
||||
free(h);
|
||||
}
|
||||
|
||||
/* Every change to h->pairs goes through here first. */
|
||||
static void sf_headers_changed(SfHeaders *h) {
|
||||
if (!h->sorted) return;
|
||||
scr_arr_release(h->sorted);
|
||||
h->sorted = NULL;
|
||||
}
|
||||
|
||||
static void *sf_headers_retain_v(void *p) {
|
||||
return sf_headers_retain((SfHeaders *)p);
|
||||
}
|
||||
@@ -2294,7 +2322,7 @@ static ScrStr *sf_headers_name(const ScrDyn *value, const char *method) {
|
||||
scr_str_release(raw);
|
||||
return NULL;
|
||||
}
|
||||
ScrStr *out = sf_header_name_lower(raw->data, raw->len);
|
||||
ScrStr *out = sf_header_name_lower_str(raw);
|
||||
scr_str_release(raw);
|
||||
return out;
|
||||
}
|
||||
@@ -2305,40 +2333,36 @@ static bool sf_headers_key_eq(const ScrStr *left, const ScrStr *right) {
|
||||
}
|
||||
|
||||
static ScrStr *sf_headers_get_value(SfHeaders *h, const ScrStr *name) {
|
||||
size_t n = (size_t)scr_arr_len(h->pairs);
|
||||
ScrArr *pairs = h->pairs;
|
||||
size_t n = (size_t)scr_arr_len(pairs);
|
||||
size_t count = 0;
|
||||
size_t total = 0;
|
||||
ScrStr *first = NULL;
|
||||
for (size_t i = 0; i + 1 < n; i += 2) {
|
||||
ScrStr *key = scr_arr_get_ref(h->pairs, (double)i);
|
||||
ScrStr *value = scr_arr_get_ref(h->pairs, (double)(i + 1));
|
||||
if (sf_headers_key_eq(key, name)) {
|
||||
if (count > 0) total += 2;
|
||||
total += value->len;
|
||||
count++;
|
||||
}
|
||||
scr_str_release(key);
|
||||
scr_str_release(value);
|
||||
if (!sf_headers_key_eq(sf_pair_at(pairs, i), name)) continue;
|
||||
ScrStr *value = sf_pair_at(pairs, i + 1);
|
||||
if (count > 0) total += 2;
|
||||
else first = value;
|
||||
total += value->len;
|
||||
count++;
|
||||
}
|
||||
if (count == 0) return NULL;
|
||||
if (count == 1) return scr_str_retain(first);
|
||||
|
||||
char *joined = malloc(total > 0 ? total : 1);
|
||||
if (!joined) sf_oom();
|
||||
size_t at = 0;
|
||||
size_t emitted = 0;
|
||||
for (size_t i = 0; i + 1 < n; i += 2) {
|
||||
ScrStr *key = scr_arr_get_ref(h->pairs, (double)i);
|
||||
ScrStr *value = scr_arr_get_ref(h->pairs, (double)(i + 1));
|
||||
if (sf_headers_key_eq(key, name)) {
|
||||
if (emitted > 0) {
|
||||
joined[at++] = ',';
|
||||
joined[at++] = ' ';
|
||||
}
|
||||
memcpy(joined + at, value->data, value->len);
|
||||
at += value->len;
|
||||
emitted++;
|
||||
if (!sf_headers_key_eq(sf_pair_at(pairs, i), name)) continue;
|
||||
ScrStr *value = sf_pair_at(pairs, i + 1);
|
||||
if (emitted > 0) {
|
||||
joined[at++] = ',';
|
||||
joined[at++] = ' ';
|
||||
}
|
||||
scr_str_release(key);
|
||||
scr_str_release(value);
|
||||
memcpy(joined + at, value->data, value->len);
|
||||
at += value->len;
|
||||
emitted++;
|
||||
}
|
||||
ScrStr *out = scr_str_new(joined, total);
|
||||
free(joined);
|
||||
@@ -2348,17 +2372,17 @@ static ScrStr *sf_headers_get_value(SfHeaders *h, const ScrStr *name) {
|
||||
static void sf_headers_delete_name(SfHeaders *h, const ScrStr *name) {
|
||||
ScrArr *old = h->pairs;
|
||||
size_t n = (size_t)scr_arr_len(old);
|
||||
bool present = false;
|
||||
for (size_t i = 0; i + 1 < n && !present; i += 2)
|
||||
present = sf_headers_key_eq(sf_pair_at(old, i), name);
|
||||
if (!present) return;
|
||||
sf_headers_changed(h);
|
||||
ScrArr *next = scr_arr_new(SCR_ELEM_STR, n);
|
||||
for (size_t i = 0; i + 1 < n; i += 2) {
|
||||
ScrStr *key = scr_arr_get_ref(old, (double)i);
|
||||
ScrStr *value = scr_arr_get_ref(old, (double)(i + 1));
|
||||
if (sf_headers_key_eq(key, name)) {
|
||||
scr_str_release(key);
|
||||
scr_str_release(value);
|
||||
continue;
|
||||
}
|
||||
scr_arr_push_ref(next, key);
|
||||
scr_arr_push_ref(next, value);
|
||||
ScrStr *key = sf_pair_at(old, i);
|
||||
if (sf_headers_key_eq(key, name)) continue;
|
||||
scr_arr_push_ref(next, scr_str_retain(key));
|
||||
scr_arr_push_ref(next, scr_str_retain(sf_pair_at(old, i + 1)));
|
||||
}
|
||||
h->pairs = next;
|
||||
scr_arr_release(old);
|
||||
@@ -2399,15 +2423,13 @@ static bool sf_headers_convert_pair(
|
||||
* Fetch-specific semicolon separator for Cookie. */
|
||||
static void sf_headers_append_pair(
|
||||
SfHeaders *h, ScrStr *name, ScrStr *value) {
|
||||
sf_headers_changed(h);
|
||||
if (!(name->len == 10 && memcmp(name->data, "set-cookie", 10) == 0)) {
|
||||
size_t n = (size_t)scr_arr_len(h->pairs);
|
||||
for (size_t i = 0; i + 1 < n; i += 2) {
|
||||
ScrStr *key = scr_arr_get_ref(h->pairs, (double)i);
|
||||
bool match = sf_headers_key_eq(key, name);
|
||||
scr_str_release(key);
|
||||
if (!match) continue;
|
||||
if (!sf_headers_key_eq(sf_pair_at(h->pairs, i), name)) continue;
|
||||
|
||||
ScrStr *previous = scr_arr_get_ref(h->pairs, (double)(i + 1));
|
||||
ScrStr *previous = sf_pair_at(h->pairs, i + 1);
|
||||
const char *separator =
|
||||
name->len == 6 && memcmp(name->data, "cookie", 6) == 0
|
||||
? "; "
|
||||
@@ -2420,7 +2442,6 @@ static void sf_headers_append_pair(
|
||||
memcpy(joined_data + previous->len + 2, value->data, value->len);
|
||||
ScrStr *joined = scr_str_new(joined_data, joined_len);
|
||||
free(joined_data);
|
||||
scr_str_release(previous);
|
||||
scr_str_release(name);
|
||||
scr_str_release(value);
|
||||
scr_arr_set_ref(h->pairs, (double)(i + 1), joined);
|
||||
@@ -2447,7 +2468,7 @@ static ScrStr **sf_headers_sorted_names(SfHeaders *h, size_t *count_out) {
|
||||
if (capacity && !names) sf_oom();
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i + 1 < n; i += 2) {
|
||||
ScrStr *key = scr_arr_get_ref(h->pairs, (double)i);
|
||||
ScrStr *key = sf_pair_at(h->pairs, i);
|
||||
bool seen = false;
|
||||
for (size_t j = 0; j < count; j++) {
|
||||
if (sf_headers_key_eq(names[j], key)) {
|
||||
@@ -2455,8 +2476,7 @@ static ScrStr **sf_headers_sorted_names(SfHeaders *h, size_t *count_out) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (seen) scr_str_release(key);
|
||||
else names[count++] = key;
|
||||
if (!seen) names[count++] = key;
|
||||
}
|
||||
if (count > 1) {
|
||||
qsort(names, count, sizeof *names, &sf_headers_name_cmp);
|
||||
@@ -2469,44 +2489,44 @@ static bool sf_headers_is_set_cookie(const ScrStr *name) {
|
||||
return name->len == 10 && memcmp(name->data, "set-cookie", 10) == 0;
|
||||
}
|
||||
|
||||
/* The `index`th entry of Fetch's "sort and combine" list: names in
|
||||
* byte order, each with its combined value, except that every set-cookie
|
||||
* value is its own entry (in insertion order), as Node's Headers
|
||||
* iterator yields them. False past the end. Outputs are +1. */
|
||||
/* Fetch's "sort and combine" list as flat name/value pairs: names in byte
|
||||
* order, each with its combined value, except that every set-cookie value
|
||||
* is its own entry (in insertion order), as Node's Headers iterator yields
|
||||
* them. Cached until the next mutation; borrowed. */
|
||||
static ScrArr *sf_headers_sorted(SfHeaders *h) {
|
||||
if (h->sorted) return h->sorted;
|
||||
size_t count = 0;
|
||||
ScrStr **names = sf_headers_sorted_names(h, &count); /* borrowed names */
|
||||
size_t n = (size_t)scr_arr_len(h->pairs);
|
||||
ScrArr *sorted = scr_arr_new(SCR_ELEM_STR, n);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (!sf_headers_is_set_cookie(names[i])) {
|
||||
scr_arr_push_ref(sorted, scr_str_retain(names[i]));
|
||||
scr_arr_push_ref(sorted, sf_headers_get_value(h, names[i]));
|
||||
continue;
|
||||
}
|
||||
for (size_t j = 0; j + 1 < n; j += 2) {
|
||||
if (!sf_headers_is_set_cookie(sf_pair_at(h->pairs, j))) continue;
|
||||
scr_arr_push_ref(sorted, scr_str_retain(names[i]));
|
||||
scr_arr_push_ref(sorted, scr_str_retain(sf_pair_at(h->pairs, j + 1)));
|
||||
}
|
||||
}
|
||||
free(names);
|
||||
h->sorted = sorted;
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/* The `index`th entry of the sort-and-combine list (sf_headers_sorted).
|
||||
* False past the end. Outputs are +1. */
|
||||
static bool sf_headers_sorted_entry(SfHeaders *h, size_t index,
|
||||
ScrStr **name_out,
|
||||
ScrStr **value_out) {
|
||||
size_t count = 0;
|
||||
ScrStr **names = sf_headers_sorted_names(h, &count);
|
||||
bool found = false;
|
||||
size_t at = 0;
|
||||
for (size_t i = 0; i < count && !found; i++) {
|
||||
if (!sf_headers_is_set_cookie(names[i])) {
|
||||
if (at == index) {
|
||||
*name_out = scr_str_retain(names[i]);
|
||||
*value_out = sf_headers_get_value(h, names[i]);
|
||||
found = true;
|
||||
}
|
||||
at++;
|
||||
continue;
|
||||
}
|
||||
size_t n = (size_t)scr_arr_len(h->pairs);
|
||||
for (size_t j = 0; j + 1 < n && !found; j += 2) {
|
||||
ScrStr *key = scr_arr_get_ref(h->pairs, (double)j);
|
||||
bool match = sf_headers_is_set_cookie(key);
|
||||
scr_str_release(key);
|
||||
if (!match) continue;
|
||||
if (at == index) {
|
||||
*name_out = scr_str_retain(names[i]);
|
||||
*value_out = scr_arr_get_ref(h->pairs, (double)(j + 1));
|
||||
found = true;
|
||||
}
|
||||
at++;
|
||||
}
|
||||
}
|
||||
for (size_t j = 0; j < count; j++) scr_str_release(names[j]);
|
||||
free(names);
|
||||
return found;
|
||||
ScrArr *sorted = sf_headers_sorted(h);
|
||||
size_t entries = (size_t)scr_arr_len(sorted) / 2;
|
||||
if (index >= entries) return false;
|
||||
*name_out = scr_str_retain(sf_pair_at(sorted, 2 * index));
|
||||
*value_out = scr_str_retain(sf_pair_at(sorted, 2 * index + 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
/* One step of a live Headers iterator (entries, keys or values): the
|
||||
@@ -2570,14 +2590,8 @@ static ScrDyn *sf_headers_invoke(void *ptr, ScrDyn *self,
|
||||
ScrDyn *out = scr_dyn_new_arr();
|
||||
size_t n = (size_t)scr_arr_len(h->pairs);
|
||||
for (size_t i = 0; i + 1 < n; i += 2) {
|
||||
ScrStr *key = scr_arr_get_ref(h->pairs, (double)i);
|
||||
ScrStr *value = scr_arr_get_ref(h->pairs, (double)(i + 1));
|
||||
if (key->len == 10 &&
|
||||
memcmp(key->data, "set-cookie", 10) == 0) {
|
||||
scr_dyn_arr_push(out, scr_dyn_new_str(value));
|
||||
}
|
||||
scr_str_release(key);
|
||||
scr_str_release(value);
|
||||
if (sf_headers_is_set_cookie(sf_pair_at(h->pairs, i)))
|
||||
scr_dyn_arr_push(out, scr_dyn_new_str(sf_pair_at(h->pairs, i + 1)));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -3323,14 +3337,13 @@ static bool sf_pairs_have(ScrArr *pairs, const char *name) {
|
||||
size_t n = (size_t)scr_arr_len(pairs);
|
||||
size_t want = strlen(name);
|
||||
for (size_t i = 0; i + 1 < n; i += 2) {
|
||||
ScrStr *key = (ScrStr *)scr_arr_get_ref(pairs, (double)i);
|
||||
ScrStr *key = sf_pair_at(pairs, i);
|
||||
bool same = key->len == want;
|
||||
for (size_t j = 0; j < want && same; j++) {
|
||||
char c = key->data[j];
|
||||
if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
|
||||
same = c == name[j];
|
||||
}
|
||||
scr_str_release(key);
|
||||
if (same) return true;
|
||||
}
|
||||
return false;
|
||||
@@ -3437,11 +3450,14 @@ static ScrStr *sf_trim_header_value(const ScrStr *value) {
|
||||
(value->data[end - 1] == ' ' || value->data[end - 1] == '\t')) {
|
||||
end--;
|
||||
}
|
||||
if (start == 0 && end == value->len) return scr_str_retain((ScrStr *)value);
|
||||
return scr_str_new(value->data + start, end - start);
|
||||
}
|
||||
|
||||
static bool sf_push_header(ScrArr *pairs, const char *name, size_t name_len,
|
||||
static bool sf_push_header(ScrArr *pairs, ScrStr *name_str,
|
||||
ScrStr *value, bool request_guard) {
|
||||
const char *name = name_str->data;
|
||||
size_t name_len = name_str->len;
|
||||
if (!sf_header_name_ok(name, name_len)) {
|
||||
ScrStr *display = scr_str_new(name, name_len);
|
||||
sf_headers_invalid("append", display, "name");
|
||||
@@ -3467,7 +3483,7 @@ static bool sf_push_header(ScrArr *pairs, const char *name, size_t name_len,
|
||||
if (!sf_header_name_ci(name, name_len, "set-cookie")) {
|
||||
size_t n = (size_t)scr_arr_len(pairs);
|
||||
for (size_t i = 0; i + 1 < n; i += 2) {
|
||||
ScrStr *key = scr_arr_get_ref(pairs, (double)i);
|
||||
ScrStr *key = sf_pair_at(pairs, i);
|
||||
bool match = key->len == name_len;
|
||||
for (size_t j = 0; j < name_len && match; j++) {
|
||||
unsigned char a = (unsigned char)key->data[j];
|
||||
@@ -3476,11 +3492,9 @@ static bool sf_push_header(ScrArr *pairs, const char *name, size_t name_len,
|
||||
if (b >= 'A' && b <= 'Z') b = (unsigned char)(b - 'A' + 'a');
|
||||
match = a == b;
|
||||
}
|
||||
scr_str_release(key);
|
||||
if (!match) continue;
|
||||
|
||||
ScrStr *previous =
|
||||
scr_arr_get_ref(pairs, (double)(i + 1));
|
||||
ScrStr *previous = sf_pair_at(pairs, i + 1);
|
||||
const char *separator =
|
||||
sf_header_name_ci(name, name_len, "cookie") ? "; " : ", ";
|
||||
size_t separator_len = 2;
|
||||
@@ -3494,7 +3508,6 @@ static bool sf_push_header(ScrArr *pairs, const char *name, size_t name_len,
|
||||
normalized->data, normalized->len);
|
||||
ScrStr *joined = scr_str_new(joined_data, joined_len);
|
||||
free(joined_data);
|
||||
scr_str_release(previous);
|
||||
scr_str_release(normalized);
|
||||
if (request_guard && !sf_request_header_ok(name, name_len, joined)) {
|
||||
scr_str_release(joined);
|
||||
@@ -3513,8 +3526,8 @@ static bool sf_push_header(ScrArr *pairs, const char *name, size_t name_len,
|
||||
scr_arr_push_ref(
|
||||
pairs,
|
||||
request_guard
|
||||
? scr_str_new(name, name_len)
|
||||
: sf_header_name_lower(name, name_len));
|
||||
? scr_str_retain(name_str)
|
||||
: sf_header_name_lower_str(name_str));
|
||||
scr_arr_push_ref(pairs, normalized);
|
||||
return true;
|
||||
}
|
||||
@@ -3541,10 +3554,13 @@ static void sf_headers_pair_length_error(size_t len) {
|
||||
* leaves header-name/value syntax validation for initialization. Response
|
||||
* construction converts this snapshot before status/statusText and only
|
||||
* validates it after body extraction, matching Undici's two phases. */
|
||||
static ScrArr *sf_headers_convert_init(const ScrDyn *headers) {
|
||||
/* *canonical_out reports that the result copies a canonical Headers
|
||||
* object's pairs (SfHeaders.canonical). */
|
||||
static ScrArr *sf_headers_convert_init(const ScrDyn *headers, bool *canonical_out) {
|
||||
*canonical_out = false;
|
||||
if (headers && headers->kind == SCR_DYN_TYPED_REF) {
|
||||
ScrDyn *materialized = scr_dyn_typed_ref_materialize(headers);
|
||||
ScrArr *converted = sf_headers_convert_init(materialized);
|
||||
ScrArr *converted = sf_headers_convert_init(materialized, canonical_out);
|
||||
scr_dyn_release(materialized);
|
||||
return converted;
|
||||
}
|
||||
@@ -3635,13 +3651,9 @@ static ScrArr *sf_headers_convert_init(const ScrDyn *headers) {
|
||||
headers->v.handle.tag == SCR_DYNH_FETCH_HEADERS) {
|
||||
SfHeaders *source = (SfHeaders *)headers->v.handle.ptr;
|
||||
size_t n = (size_t)scr_arr_len(source->pairs);
|
||||
for (size_t i = 0; i + 1 < n; i += 2) {
|
||||
ScrStr *name = scr_arr_get_ref(source->pairs, (double)i);
|
||||
ScrStr *value =
|
||||
scr_arr_get_ref(source->pairs, (double)(i + 1));
|
||||
scr_arr_push_ref(converted, name);
|
||||
scr_arr_push_ref(converted, value);
|
||||
}
|
||||
for (size_t i = 0; i < n - n % 2; i++)
|
||||
scr_arr_push_ref(converted, scr_str_retain(sf_pair_at(source->pairs, i)));
|
||||
*canonical_out = source->canonical;
|
||||
return converted;
|
||||
}
|
||||
/* Functions are WebIDL objects and an ordinary function has no
|
||||
@@ -3656,25 +3668,29 @@ fail:
|
||||
}
|
||||
|
||||
static bool sf_add_converted_headers(
|
||||
ScrArr *pairs, ScrArr *converted, bool request_guard) {
|
||||
ScrArr *pairs, ScrArr *converted, bool request_guard, bool canonical) {
|
||||
size_t n = (size_t)scr_arr_len(converted);
|
||||
/* Pushing canonical pairs into an empty list one by one through
|
||||
* sf_push_header would reproduce them exactly. */
|
||||
if (canonical && !request_guard && scr_arr_len(pairs) == 0) {
|
||||
for (size_t i = 0; i < n; i++)
|
||||
scr_arr_push_ref(pairs, scr_str_retain(sf_pair_at(converted, i)));
|
||||
return true;
|
||||
}
|
||||
for (size_t i = 0; i + 1 < n; i += 2) {
|
||||
ScrStr *name = scr_arr_get_ref(converted, (double)i);
|
||||
ScrStr *value = scr_arr_get_ref(converted, (double)(i + 1));
|
||||
bool ok = sf_push_header(
|
||||
pairs, name->data, name->len, value, request_guard);
|
||||
scr_str_release(name);
|
||||
scr_str_release(value);
|
||||
if (!ok) return false;
|
||||
if (!sf_push_header(pairs, sf_pair_at(converted, i),
|
||||
sf_pair_at(converted, i + 1), request_guard))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sf_add_headers(
|
||||
ScrArr *pairs, const ScrDyn *headers, bool request_guard) {
|
||||
ScrArr *converted = sf_headers_convert_init(headers);
|
||||
bool canonical = false;
|
||||
ScrArr *converted = sf_headers_convert_init(headers, &canonical);
|
||||
if (!converted) return false;
|
||||
bool ok = sf_add_converted_headers(pairs, converted, request_guard);
|
||||
bool ok = sf_add_converted_headers(pairs, converted, request_guard, canonical);
|
||||
scr_arr_release(converted);
|
||||
return ok;
|
||||
}
|
||||
@@ -4854,6 +4870,7 @@ static ScrDyn *sf_response_construct(ScrDyn *body, ScrDyn *init,
|
||||
SfJsonSerialize serialize) {
|
||||
ScrArr *header_pairs = NULL;
|
||||
ScrArr *converted_headers = NULL;
|
||||
bool converted_canonical = false;
|
||||
ScrBytes *body_bytes = NULL;
|
||||
ScrStr *status_text = NULL;
|
||||
SfStream *stream = NULL;
|
||||
@@ -4918,7 +4935,7 @@ static ScrDyn *sf_response_construct(ScrDyn *body, ScrDyn *init,
|
||||
* later response-initialization step, after body extraction. */
|
||||
ScrDyn *init_headers = sf_response_init_get(init, "headers", 7);
|
||||
if (light_ok) light_form = sf_light_header_form(init_headers, json_body, &light_record);
|
||||
converted_headers = sf_headers_convert_init(init_headers);
|
||||
converted_headers = sf_headers_convert_init(init_headers, &converted_canonical);
|
||||
scr_dyn_release(init_headers);
|
||||
if (!converted_headers) goto fail;
|
||||
|
||||
@@ -5013,7 +5030,7 @@ static ScrDyn *sf_response_construct(ScrDyn *body, ScrDyn *init,
|
||||
|
||||
header_pairs = scr_arr_new(SCR_ELEM_STR, 8);
|
||||
if (!sf_add_converted_headers(
|
||||
header_pairs, converted_headers, false)) goto fail;
|
||||
header_pairs, converted_headers, false, converted_canonical)) goto fail;
|
||||
scr_arr_release(converted_headers);
|
||||
converted_headers = NULL;
|
||||
|
||||
@@ -5044,6 +5061,7 @@ static ScrDyn *sf_response_construct(ScrDyn *body, ScrDyn *init,
|
||||
response->rc = 1;
|
||||
response->body = stream;
|
||||
response->headers = sf_headers_new(header_pairs, false);
|
||||
response->headers->canonical = true;
|
||||
response->url = scr_str_new("", 0);
|
||||
response->status_text = status_text;
|
||||
response->status = status;
|
||||
@@ -5159,13 +5177,15 @@ static ScrDyn *sf_request_invoke(void *ptr, ScrDyn *self, const char *method, Sc
|
||||
}
|
||||
|
||||
ScrDyn *scr_fetch_headers_new(ScrDyn *init) {
|
||||
ScrArr *converted = sf_headers_convert_init(init);
|
||||
bool canonical = false;
|
||||
ScrArr *converted = sf_headers_convert_init(init, &canonical);
|
||||
if (!converted) return NULL;
|
||||
ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, 8);
|
||||
bool ok = sf_add_converted_headers(pairs, converted, false);
|
||||
bool ok = sf_add_converted_headers(pairs, converted, false, canonical);
|
||||
scr_arr_release(converted);
|
||||
if (!ok) { scr_arr_release(pairs); return NULL; }
|
||||
SfHeaders *headers = sf_headers_new(pairs, false);
|
||||
headers->canonical = true;
|
||||
ScrDyn *result = scr_dyn_new_handle(headers, SCR_DYNH_FETCH_HEADERS);
|
||||
sf_headers_release(headers);
|
||||
return result;
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 6400, "san": 9200 }
|
||||
Reference in New Issue
Block a user