Skip to content

Commit

Permalink
perf: lazy header instantiation for HTTP requests (denoland#10150)
Browse files Browse the repository at this point in the history
This commit introduces a performance optimization for the native HTTP
server. From my testing it is about 2-6% faster than `main`. Request
headers in the HTTP servers are now lazilly instatated when they are
accessed, rather than being preemptively wrapped in the `Headers` class.
  • Loading branch information
lucacasonato committed Apr 13, 2021
1 parent 9f26e63 commit dabce81
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 6 deletions.
29 changes: 25 additions & 4 deletions op_crates/fetch/26_fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,7 @@
// fastBody and dontValidateUrl allow users to opt out of certain behaviors
const fastBody = Symbol("Body#fast");
const dontValidateUrl = Symbol("dontValidateUrl");
const lazyHeaders = Symbol("lazyHeaders");

class Body {
#contentType = "";
Expand Down Expand Up @@ -960,7 +961,7 @@
#method = "GET";
/** @type {string} */
#url = "";
/** @type {Headers} */
/** @type {Headers | string[][]} */
#headers;
/** @type {"include" | "omit" | "same-origin" | undefined} */
#credentials = "omit";
Expand Down Expand Up @@ -999,26 +1000,42 @@
}

let headers;
let contentType = "";
// prefer headers from init
if (init.headers) {
headers = new Headers(init.headers);
if (init[lazyHeaders] && Array.isArray(init.headers)) {
// Trust the headers are valid, and only put them into the `Headers`
// strucutre when the user accesses the property. We also assume that
// all passed headers are lower-case (as is the case when they come
// from hyper in Rust), and that headers are of type
// `[string, string][]`.
headers = init.headers;
for (const tuple of headers) {
if (tuple[0] === "content-type") {
contentType = tuple[1];
}
}
} else {
headers = new Headers(init.headers);
contentType = headers.get("content-type") || "";
}
} else if (input instanceof Request) {
headers = input.headers;
contentType = headers.get("content-type") || "";
} else {
headers = new Headers();
}

const contentType = headers.get("content-type") || "";
super(b, { contentType });
this.#headers = headers;

if (input instanceof Request) {
if (input.bodyUsed) {
throw TypeError(BodyUsedError);
}
// headers are already set above. no reason to do it again
this.#method = input.method;
this.#url = input.url;
this.#headers = new Headers(input.headers);
this.#credentials = input.credentials;
} else {
// Constructing a URL just for validation is known to be expensive.
Expand Down Expand Up @@ -1085,6 +1102,9 @@
}

get headers() {
if (!(this.#headers instanceof Headers)) {
this.#headers = new Headers(this.#headers);
}
return this.#headers;
}

Expand Down Expand Up @@ -1515,5 +1535,6 @@
createHttpClient,
fastBody,
dontValidateUrl,
lazyHeaders,
};
})(this);
5 changes: 3 additions & 2 deletions runtime/js/40_http.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"use strict";

((window) => {
const { Request, dontValidateUrl, fastBody, Response } =
const { Request, dontValidateUrl, lazyHeaders, fastBody, Response } =
window.__bootstrap.fetch;
const { Headers } = window.__bootstrap.headers;
const errors = window.__bootstrap.errors.errors;
Expand Down Expand Up @@ -61,8 +61,9 @@
const request = new Request(url, {
body,
method,
headers: new Headers(headersList),
headers: headersList,
[dontValidateUrl]: true,
[lazyHeaders]: true,
});

const respondWith = createRespondWith(responseSenderRid, this.#rid);
Expand Down

0 comments on commit dabce81

Please sign in to comment.