0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

perf(cli/body): improve .arrayBuffer() speed (#6669)

This commit is contained in:
Marcos Casagrande 2020-07-08 04:25:34 +02:00 committed by GitHub
parent cb98a59452
commit e4899b6ba4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 48 additions and 23 deletions

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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"));

View file

@ -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];