From 03bcd22957b6000a3c0073f83b80f90efb1a2a89 Mon Sep 17 00:00:00 2001 From: Gurwinder Singh Date: Tue, 12 Jul 2022 07:50:57 +0530 Subject: [PATCH 1/7] feat: send response body from worker as arraybuffer (zero copy) --- src/mockServiceWorker.js | 28 ++++++++++++++++--------- src/setupWorker/glossary.ts | 4 ++-- src/utils/request/parseWorkerRequest.ts | 7 +------ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/mockServiceWorker.js b/src/mockServiceWorker.js index 699f43aef..661ad1f9a 100644 --- a/src/mockServiceWorker.js +++ b/src/mockServiceWorker.js @@ -147,7 +147,9 @@ async function handleRequest(event, requestId) { if (client && activeClientIds.has(client.id)) { ;(async function () { const clonedResponse = response.clone() - sendToClient(client, { + const body = + clonedResponse.body === null ? null : await clonedResponse.arrayBuffer() + const message = { type: 'RESPONSE', payload: { requestId, @@ -155,12 +157,12 @@ async function handleRequest(event, requestId) { ok: clonedResponse.ok, status: clonedResponse.status, statusText: clonedResponse.statusText, - body: - clonedResponse.body === null ? null : await clonedResponse.text(), + body, headers: Object.fromEntries(clonedResponse.headers.entries()), redirected: clonedResponse.redirected, }, - }) + } + sendToClient(client, message, body) })() } @@ -200,7 +202,7 @@ async function getResponse(event, client, requestId) { function passthrough() { // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the cilent). + // (i.e. its body has been read and sent to the client). const headers = Object.fromEntries(clonedRequest.headers.entries()) // Remove MSW-specific request headers so the bypassed requests @@ -239,7 +241,8 @@ async function getResponse(event, client, requestId) { ) // Notify the client that a request has been intercepted. - const clientMessage = await sendToClient(client, { + const body = await request.arrayBuffer() + const message = { type: 'REQUEST', payload: { id: requestId, @@ -254,11 +257,12 @@ async function getResponse(event, client, requestId) { redirect: request.redirect, referrer: request.referrer, referrerPolicy: request.referrerPolicy, - body: await request.text(), + body, bodyUsed: request.bodyUsed, keepalive: request.keepalive, }, - }) + } + const clientMessage = await sendToClient(client, message, body) switch (clientMessage.type) { case 'MOCK_RESPONSE': { @@ -304,7 +308,7 @@ This exception has been gracefully handled as a 500 response, however, it's stro return passthrough() } -function sendToClient(client, message) { +function sendToClient(client, message, body) { return new Promise((resolve, reject) => { const channel = new MessageChannel() @@ -316,7 +320,11 @@ function sendToClient(client, message) { resolve(event.data) } - client.postMessage(message, [channel.port2]) + const transfer = [channel.port2] + if (body) { + transfer.push(body) + } + client.postMessage(message, transfer) }) } diff --git a/src/setupWorker/glossary.ts b/src/setupWorker/glossary.ts index 417f1dbed..419b2e7f5 100644 --- a/src/setupWorker/glossary.ts +++ b/src/setupWorker/glossary.ts @@ -38,9 +38,9 @@ export interface ServiceWorkerIncomingRequest extends RequestWithoutMethods { id: string /** - * Text response body. + * Response body. */ - body?: string + body?: ArrayBuffer } export type ServiceWorkerIncomingResponse = Pick< diff --git a/src/utils/request/parseWorkerRequest.ts b/src/utils/request/parseWorkerRequest.ts index cc8b45fd7..71b4a1658 100644 --- a/src/utils/request/parseWorkerRequest.ts +++ b/src/utils/request/parseWorkerRequest.ts @@ -1,4 +1,3 @@ -import { encodeBuffer } from '@mswjs/interceptors' import { Headers } from 'headers-polyfill' import { ServiceWorkerIncomingRequest } from '../../setupWorker/glossary' import { MockedRequest } from './MockedRequest' @@ -13,9 +12,5 @@ export function parseWorkerRequest( const url = new URL(rawRequest.url) const headers = new Headers(rawRequest.headers) - return new MockedRequest(url, { - ...rawRequest, - body: encodeBuffer(rawRequest.body || ''), - headers, - }) + return new MockedRequest(url, { ...rawRequest, headers }) } From b4ee3a6fba326f39737e62392ad380199362614b Mon Sep 17 00:00:00 2001 From: Gurwinder Singh Date: Tue, 12 Jul 2022 07:51:16 +0530 Subject: [PATCH 2/7] fix(cleanup): dead code --- src/utils/request/pruneGetRequestBody.test.ts | 38 ------------------- src/utils/request/pruneGetRequestBody.ts | 21 ---------- 2 files changed, 59 deletions(-) delete mode 100644 src/utils/request/pruneGetRequestBody.test.ts delete mode 100644 src/utils/request/pruneGetRequestBody.ts diff --git a/src/utils/request/pruneGetRequestBody.test.ts b/src/utils/request/pruneGetRequestBody.test.ts deleted file mode 100644 index 1c08e25a4..000000000 --- a/src/utils/request/pruneGetRequestBody.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @jest-environment jsdom - */ -import { pruneGetRequestBody } from './pruneGetRequestBody' - -test('sets empty GET request body to undefined', () => { - const body = pruneGetRequestBody({ - method: 'GET', - body: '', - }) - - expect(body).toBeUndefined() -}) - -test('preserves non-empty GET request body', () => { - const body = pruneGetRequestBody({ - method: 'GET', - body: 'text-body', - }) - - expect(body).toBe('text-body') -}) - -test('ignores requests of the other method than GET', () => { - expect( - pruneGetRequestBody({ - method: 'HEAD', - body: JSON.stringify({ a: 1 }), - }), - ).toBe(JSON.stringify({ a: 1 })) - - expect( - pruneGetRequestBody({ - method: 'POST', - body: 'text-body', - }), - ).toBe('text-body') -}) diff --git a/src/utils/request/pruneGetRequestBody.ts b/src/utils/request/pruneGetRequestBody.ts deleted file mode 100644 index d6f1e1ce7..000000000 --- a/src/utils/request/pruneGetRequestBody.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ServiceWorkerIncomingRequest } from '../../setupWorker/glossary' -import { isStringEqual } from '../internal/isStringEqual' - -type Input = Pick - -/** - * Ensures that an empty GET request body is always represented as `undefined`. - */ -export function pruneGetRequestBody( - request: Input, -): ServiceWorkerIncomingRequest['body'] { - if ( - request.method && - isStringEqual(request.method, 'GET') && - request.body === '' - ) { - return undefined - } - - return request.body -} From a29db0e1db73b383b74eb83b400740d708234c92 Mon Sep 17 00:00:00 2001 From: Gurwinder Singh Date: Tue, 12 Jul 2022 19:01:42 +0530 Subject: [PATCH 3/7] fix: review comments --- src/mockServiceWorker.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/mockServiceWorker.js b/src/mockServiceWorker.js index 661ad1f9a..0629e2ed9 100644 --- a/src/mockServiceWorker.js +++ b/src/mockServiceWorker.js @@ -147,9 +147,7 @@ async function handleRequest(event, requestId) { if (client && activeClientIds.has(client.id)) { ;(async function () { const clonedResponse = response.clone() - const body = - clonedResponse.body === null ? null : await clonedResponse.arrayBuffer() - const message = { + sendToClient(client, { type: 'RESPONSE', payload: { requestId, @@ -157,12 +155,14 @@ async function handleRequest(event, requestId) { ok: clonedResponse.ok, status: clonedResponse.status, statusText: clonedResponse.statusText, - body, + body: + clonedResponse.body === null + ? null + : await clonedResponse.arrayBuffer(), headers: Object.fromEntries(clonedResponse.headers.entries()), redirected: clonedResponse.redirected, }, - } - sendToClient(client, message, body) + }) })() } @@ -241,8 +241,7 @@ async function getResponse(event, client, requestId) { ) // Notify the client that a request has been intercepted. - const body = await request.arrayBuffer() - const message = { + const clientMessage = await sendToClient(client, { type: 'REQUEST', payload: { id: requestId, @@ -257,12 +256,11 @@ async function getResponse(event, client, requestId) { redirect: request.redirect, referrer: request.referrer, referrerPolicy: request.referrerPolicy, - body, + body: await request.arrayBuffer(), bodyUsed: request.bodyUsed, keepalive: request.keepalive, }, - } - const clientMessage = await sendToClient(client, message, body) + }) switch (clientMessage.type) { case 'MOCK_RESPONSE': { @@ -308,7 +306,7 @@ This exception has been gracefully handled as a 500 response, however, it's stro return passthrough() } -function sendToClient(client, message, body) { +function sendToClient(client, message) { return new Promise((resolve, reject) => { const channel = new MessageChannel() @@ -321,8 +319,8 @@ function sendToClient(client, message, body) { } const transfer = [channel.port2] - if (body) { - transfer.push(body) + if (typeof message.payload !== 'undefined' && message.payload.body) { + transfer.push(message.payload.body) } client.postMessage(message, transfer) }) From 8df915912eab40391c972e0ff97a008e1f584f91 Mon Sep 17 00:00:00 2001 From: Gurwinder Singh Date: Tue, 12 Jul 2022 20:47:49 +0530 Subject: [PATCH 4/7] fix: review --- src/mockServiceWorker.js | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/mockServiceWorker.js b/src/mockServiceWorker.js index 0629e2ed9..591ac010e 100644 --- a/src/mockServiceWorker.js +++ b/src/mockServiceWorker.js @@ -147,7 +147,8 @@ async function handleRequest(event, requestId) { if (client && activeClientIds.has(client.id)) { ;(async function () { const clonedResponse = response.clone() - sendToClient(client, { + const body = await clonedResponse.arrayBuffer() + const message = { type: 'RESPONSE', payload: { requestId, @@ -155,14 +156,12 @@ async function handleRequest(event, requestId) { ok: clonedResponse.ok, status: clonedResponse.status, statusText: clonedResponse.statusText, - body: - clonedResponse.body === null - ? null - : await clonedResponse.arrayBuffer(), + body, headers: Object.fromEntries(clonedResponse.headers.entries()), redirected: clonedResponse.redirected, }, - }) + } + sendToClient(client, message, [body]) })() } @@ -241,7 +240,7 @@ async function getResponse(event, client, requestId) { ) // Notify the client that a request has been intercepted. - const clientMessage = await sendToClient(client, { + const message = { type: 'REQUEST', payload: { id: requestId, @@ -260,7 +259,10 @@ async function getResponse(event, client, requestId) { bodyUsed: request.bodyUsed, keepalive: request.keepalive, }, - }) + } + const clientMessage = await sendToClient(client, message, [ + message.payload.body, + ]) switch (clientMessage.type) { case 'MOCK_RESPONSE': { @@ -306,7 +308,7 @@ This exception has been gracefully handled as a 500 response, however, it's stro return passthrough() } -function sendToClient(client, message) { +function sendToClient(client, message, transfer = []) { return new Promise((resolve, reject) => { const channel = new MessageChannel() @@ -318,11 +320,7 @@ function sendToClient(client, message) { resolve(event.data) } - const transfer = [channel.port2] - if (typeof message.payload !== 'undefined' && message.payload.body) { - transfer.push(message.payload.body) - } - client.postMessage(message, transfer) + client.postMessage(message, [channel.port2, ...transfer]) }) } @@ -334,7 +332,8 @@ function sleep(timeMs) { async function respondWithMock(response) { await sleep(response.delay) - return new Response(response.body, response) + const body = response.status == 204 ? null : response.body + return new Response(body, response) } function respondWithMockStream(operationChannel, mockResponse) { From 6ad04b300ae1fd2c87a63fc62025d696dab655ba Mon Sep 17 00:00:00 2001 From: Gurwinder Singh Date: Tue, 12 Jul 2022 22:40:44 +0530 Subject: [PATCH 5/7] fix: empty body should send null --- src/mockServiceWorker.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mockServiceWorker.js b/src/mockServiceWorker.js index 591ac010e..f3a748436 100644 --- a/src/mockServiceWorker.js +++ b/src/mockServiceWorker.js @@ -147,7 +147,8 @@ async function handleRequest(event, requestId) { if (client && activeClientIds.has(client.id)) { ;(async function () { const clonedResponse = response.clone() - const body = await clonedResponse.arrayBuffer() + const body = + clonedResponse.body == null ? null : await clonedResponse.arrayBuffer() const message = { type: 'RESPONSE', payload: { @@ -161,7 +162,7 @@ async function handleRequest(event, requestId) { redirected: clonedResponse.redirected, }, } - sendToClient(client, message, [body]) + sendToClient(client, message, body ? [body] : []) })() } @@ -332,8 +333,7 @@ function sleep(timeMs) { async function respondWithMock(response) { await sleep(response.delay) - const body = response.status == 204 ? null : response.body - return new Response(body, response) + return new Response(response.body, response) } function respondWithMockStream(operationChannel, mockResponse) { From 473b74068fe1c7d1d5c67e684dbf5400b64f2b42 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Sat, 16 Jul 2022 11:16:08 +0200 Subject: [PATCH 6/7] chore(worker): concat transport chunks vs spreading --- src/mockServiceWorker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mockServiceWorker.js b/src/mockServiceWorker.js index f3a748436..4a708fe13 100644 --- a/src/mockServiceWorker.js +++ b/src/mockServiceWorker.js @@ -162,7 +162,7 @@ async function handleRequest(event, requestId) { redirected: clonedResponse.redirected, }, } - sendToClient(client, message, body ? [body] : []) + sendToClient(client, message, body && [body]) })() } @@ -309,7 +309,7 @@ This exception has been gracefully handled as a 500 response, however, it's stro return passthrough() } -function sendToClient(client, message, transfer = []) { +function sendToClient(client, message, transfer) { return new Promise((resolve, reject) => { const channel = new MessageChannel() @@ -321,7 +321,7 @@ function sendToClient(client, message, transfer = []) { resolve(event.data) } - client.postMessage(message, [channel.port2, ...transfer]) + client.postMessage(message, [channel.port2].concat(transfer || [])) }) } From 28c2f36d133ff15d544465c1adbcd236da0f2e97 Mon Sep 17 00:00:00 2001 From: Gurwinder Singh Date: Sat, 16 Jul 2022 21:39:15 +0530 Subject: [PATCH 7/7] fix: type --- src/mockServiceWorker.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mockServiceWorker.js b/src/mockServiceWorker.js index 4a708fe13..536b15178 100644 --- a/src/mockServiceWorker.js +++ b/src/mockServiceWorker.js @@ -148,7 +148,9 @@ async function handleRequest(event, requestId) { ;(async function () { const clonedResponse = response.clone() const body = - clonedResponse.body == null ? null : await clonedResponse.arrayBuffer() + clonedResponse.body === null + ? undefined + : await clonedResponse.arrayBuffer() const message = { type: 'RESPONSE', payload: {