diff options
author | Marcos Casagrande <marcoscvp90@gmail.com> | 2020-07-08 04:25:34 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-07 22:25:34 -0400 |
commit | e4899b6ba417a801ce3d9128c9769e855487682f (patch) | |
tree | e610fc41996679ed9e863cddee028a0a22c36fa8 | |
parent | cb98a594522e44fca885ca94ffdcc181ad902603 (diff) |
perf(cli/body): improve .arrayBuffer() speed (#6669)
-rw-r--r-- | cli/js/web/body.ts | 34 | ||||
-rw-r--r-- | cli/js/web/fetch.ts | 3 | ||||
-rw-r--r-- | cli/js/web/request.ts | 2 | ||||
-rw-r--r-- | cli/tests/unit/body_test.ts | 13 | ||||
-rw-r--r-- | cli/tests/unit/fetch_test.ts | 19 |
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]; |