summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcos Casagrande <marcoscvp90@gmail.com>2020-07-08 04:25:34 +0200
committerGitHub <noreply@github.com>2020-07-07 22:25:34 -0400
commite4899b6ba417a801ce3d9128c9769e855487682f (patch)
treee610fc41996679ed9e863cddee028a0a22c36fa8
parentcb98a594522e44fca885ca94ffdcc181ad902603 (diff)
perf(cli/body): improve .arrayBuffer() speed (#6669)
-rw-r--r--cli/js/web/body.ts34
-rw-r--r--cli/js/web/fetch.ts3
-rw-r--r--cli/js/web/request.ts2
-rw-r--r--cli/tests/unit/body_test.ts13
-rw-r--r--cli/tests/unit/fetch_test.ts19
5 files changed, 48 insertions, 23 deletions
diff --git a/cli/js/web/body.ts b/cli/js/web/body.ts
index 80c4b646a..3bcda0634 100644
--- a/cli/js/web/body.ts
+++ b/cli/js/web/body.ts
@@ -18,6 +18,11 @@ import { MultipartParser } from "./fetch/multipart.ts";
const { TextEncoder, TextDecoder } = encoding;
const DenoBlob = blob.DenoBlob;
+interface BodyMeta {
+ contentType: string;
+ size?: number;
+}
+
function validateBodyType(owner: Body, bodySource: BodyInit | null): boolean {
if (isTypedArray(bodySource)) {
return true;
@@ -40,11 +45,17 @@ function validateBodyType(owner: Body, bodySource: BodyInit | null): boolean {
}
async function bufferFromStream(
- stream: ReadableStreamReader
+ stream: ReadableStreamReader,
+ size?: number
): Promise<ArrayBuffer> {
const encoder = new TextEncoder();
const buffer = new Buffer();
+ if (size) {
+ // grow to avoid unnecessary allocations & copies
+ buffer.grow(size);
+ }
+
while (true) {
const { done, value } = await stream.read();
@@ -71,14 +82,13 @@ export const BodyUsedError =
export class Body implements domTypes.Body {
protected _stream: ReadableStreamImpl<string | ArrayBuffer> | null;
-
- constructor(
- protected _bodySource: BodyInit | null,
- readonly contentType: string
- ) {
+ #contentType: string;
+ #size: number | undefined;
+ constructor(protected _bodySource: BodyInit | null, meta: BodyMeta) {
validateBodyType(this, _bodySource);
this._bodySource = _bodySource;
- this.contentType = contentType;
+ this.#contentType = meta.contentType;
+ this.#size = meta.size;
this._stream = null;
}
@@ -111,15 +121,15 @@ export class Body implements domTypes.Body {
public async blob(): Promise<Blob> {
return new DenoBlob([await this.arrayBuffer()], {
- type: this.contentType,
+ type: this.#contentType,
});
}
// ref: https://fetch.spec.whatwg.org/#body-mixin
public async formData(): Promise<FormData> {
const formData = new FormData();
- if (hasHeaderValueOf(this.contentType, "multipart/form-data")) {
- const params = getHeaderValueParams(this.contentType);
+ if (hasHeaderValueOf(this.#contentType, "multipart/form-data")) {
+ const params = getHeaderValueParams(this.#contentType);
// ref: https://tools.ietf.org/html/rfc2046#section-5.1
const boundary = params.get("boundary")!;
@@ -128,7 +138,7 @@ export class Body implements domTypes.Body {
return multipartParser.parse();
} else if (
- hasHeaderValueOf(this.contentType, "application/x-www-form-urlencoded")
+ hasHeaderValueOf(this.#contentType, "application/x-www-form-urlencoded")
) {
// From https://github.com/github/fetch/blob/master/fetch.js
// Copyright (c) 2014-2016 GitHub, Inc. MIT License
@@ -184,7 +194,7 @@ export class Body implements domTypes.Body {
enc.encode(this._bodySource).buffer as ArrayBuffer
);
} else if (this._bodySource instanceof ReadableStreamImpl) {
- return bufferFromStream(this._bodySource.getReader());
+ return bufferFromStream(this._bodySource.getReader(), this.#size);
} else if (
this._bodySource instanceof FormData ||
this._bodySource instanceof URLSearchParams
diff --git a/cli/js/web/fetch.ts b/cli/js/web/fetch.ts
index 156c218a4..33cf12069 100644
--- a/cli/js/web/fetch.ts
+++ b/cli/js/web/fetch.ts
@@ -110,8 +110,9 @@ export class Response extends Body.Body implements domTypes.Response {
}
const contentType = headers.get("content-type") || "";
+ const size = Number(headers.get("content-length")) || undefined;
- super(body, contentType);
+ super(body, { contentType, size });
this.url = url;
this.statusText = statusText;
diff --git a/cli/js/web/request.ts b/cli/js/web/request.ts
index a9dcce2de..7ea6a9ecd 100644
--- a/cli/js/web/request.ts
+++ b/cli/js/web/request.ts
@@ -71,7 +71,7 @@ export class Request extends body.Body implements domTypes.Request {
}
const contentType = headers.get("content-type") || "";
- super(b, contentType);
+ super(b, { contentType });
this.headers = headers;
// readonly attribute ByteString method;
diff --git a/cli/tests/unit/body_test.ts b/cli/tests/unit/body_test.ts
index 3b01190a6..ac65a1312 100644
--- a/cli/tests/unit/body_test.ts
+++ b/cli/tests/unit/body_test.ts
@@ -3,9 +3,10 @@ import { unitTest, assertEquals, assert } from "./test_util.ts";
// just a hack to get a body object
// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function buildBody(body: any): Body {
+function buildBody(body: any, headers?: Headers): Body {
const stub = new Request("", {
body: body,
+ headers,
});
return stub as Body;
}
@@ -40,10 +41,7 @@ unitTest(
);
const text = await response.text();
- const body = buildBody(text);
-
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (body as any).contentType = "multipart/form-data;boundary=boundary";
+ const body = buildBody(text, response.headers);
const formData = await body.formData();
assert(formData.has("field_1"));
@@ -60,10 +58,7 @@ unitTest(
);
const text = await response.text();
- const body = buildBody(text);
-
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (body as any).contentType = "application/x-www-form-urlencoded";
+ const body = buildBody(text, response.headers);
const formData = await body.formData();
assert(formData.has("field_1"));
diff --git a/cli/tests/unit/fetch_test.ts b/cli/tests/unit/fetch_test.ts
index f8ceebf6e..84f2c2822 100644
--- a/cli/tests/unit/fetch_test.ts
+++ b/cli/tests/unit/fetch_test.ts
@@ -851,6 +851,25 @@ unitTest(
}
);
+unitTest(
+ { perms: { net: true } },
+ async function fetchResponseContentLength(): Promise<void> {
+ const body = new Uint8Array(2 ** 16);
+ const headers = new Headers([["content-type", "application/octet-stream"]]);
+ const res = await fetch("http://localhost:4545/echo_server", {
+ body: body,
+ method: "POST",
+ headers,
+ });
+ assertEquals(Number(res.headers.get("content-length")), body.byteLength);
+
+ const blob = await res.blob();
+ // Make sure Body content-type is correctly set
+ assertEquals(blob.type, "application/octet-stream");
+ assertEquals(blob.size, body.byteLength);
+ }
+);
+
unitTest(function fetchResponseConstructorNullBody(): void {
const nullBodyStatus = [204, 205, 304];