Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/http/core/request.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,24 @@ describe('UwsRequest', () => {
);
});

it('should optimize empty bodies by using the exact same memory reference', async () => {
const req1 = new UwsRequest(mockUwsReq, mockUwsRes);
const req2 = new UwsRequest(mockUwsReq, mockUwsRes);

mockUwsReq.getMethod.mockReturnValue('GET');
req1._initBodyParser(1024);
req2._initBodyParser(1024);

onDataCallback(toArrayBuffer(Buffer.from('')), true);

const res1 = await req1.json();
const res2 = await req2.json();

// Referential equality proves zero new objects were allocated
expect(res1).toBe(res2);
expect(Object.isFrozen(res1)).toBe(true);
});

it('should cache raw buffer', async () => {
setHeaders(['content-length', '5']);

Expand Down
11 changes: 9 additions & 2 deletions src/http/core/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ import { MultipartFormHandler } from '../body/multipart-handler';
*/
const BUFFER_WATERMARK = 128 * 1024; // 128KB

/**
* This is a single shared constant which will be used inside the request class
* The object is created and frozen exactly once when the file is first loaded. Subsequent requests just point to this same memory address.
* Since no new objects are being created in that branch, the Garbage Collector has nothing to clean up, which improves the overall latency of the server.
*/
const EMPTY_FROZEN_OBJECT = Object.freeze({});

/**
* Headers that should NOT be duplicated per HTTP spec
* @see https://www.rfc-editor.org/rfc/rfc7230#section-3.2.2
Expand Down Expand Up @@ -1083,9 +1090,9 @@ export class UwsRequest extends Readable {
// Handle empty body - return frozen empty object for GET/HEAD/DELETE, throw for all other methods
if (text === '') {
if (this.method === 'GET' || this.method === 'HEAD' || this.method === 'DELETE') {
// Freeze the empty object to prevent accidental mutations
// Use the shared constant to freeze the empty object instead of creating a new one
// This will throw TypeError in strict mode if mutation is attempted
this.cachedJson = Object.freeze({}) as T;
this.cachedJson = EMPTY_FROZEN_OBJECT as T;
return this.cachedJson as T;
}
// Throw for POST/PUT/PATCH and other methods (OPTIONS, SEARCH, PROPFIND, etc.)
Expand Down
Loading