Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 14f95d3

Browse files
committedApr 7, 2025
Reduce per-exchange memory use by ~15% with HttpBody improvements
Refactoring most notably to avoid indefinitely holdingh a lazyObservablePromise on every instance (2x per exchange) everywhere. These are expensive, and mostly not necessary. Refactored to cover the two cases: reading the data & checking decoding state (both observably) and waiting for the data as a promise (not observable).
1 parent 7d1a673 commit 14f95d3

19 files changed

+254
-172
lines changed
 

‎src/components/send/sent-response-body.tsx

+6-10
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,7 @@ export class SentResponseBodyCard extends React.Component<ExpandableCardProps &
9090
? this.selectedContentType!
9191
: (message?.contentType ?? 'text');
9292

93-
const decodedBody = message?.body.decoded;
94-
95-
if (decodedBody) {
93+
if (message?.body.isDecoded()) {
9694
// We have successfully decoded the body content, show it:
9795
return <SendBodyCardSection
9896
ariaLabel={ariaLabel}
@@ -102,7 +100,7 @@ export class SentResponseBodyCard extends React.Component<ExpandableCardProps &
102100
>
103101
<header>
104102
<ReadonlyBodyCardHeader
105-
body={decodedBody}
103+
body={message.body.decodedData}
106104
mimeType={getHeaderValue(message.headers, 'content-type')}
107105
downloadFilename={getBodyDownloadFilename(url, message.headers)}
108106

@@ -127,16 +125,14 @@ export class SentResponseBodyCard extends React.Component<ExpandableCardProps &
127125
expanded={!!expanded}
128126
cache={message.cache}
129127
>
130-
{decodedBody}
128+
{ message.body.decodedData }
131129
</ContentViewer>
132130
</SendEditorCardContent>
133131
</SendBodyCardSection>;
134-
} else if (!decodedBody && message?.body.decodingError) {
132+
} else if (message?.body.isFailed()) {
135133
// We have failed to decode the body content! Show the error & raw encoded data instead:
136-
const error = message.body.decodingError as ErrorLike;
137-
const encodedBody = Buffer.isBuffer(message.body.encoded)
138-
? message.body.encoded
139-
: undefined;
134+
const error = message.body.decodingError;
135+
const encodedBody = message.body.encodedData;
140136

141137
const encodedDataContentType = _.includes(ENCODED_DATA_CONTENT_TYPES, this.selectedContentType)
142138
? this.selectedContentType!

‎src/components/send/sent-response-status.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export const ResponseStatusSection = (props: {
6363
</Pill>
6464
<DurationPill timingEvents={props.exchange.timingEvents} />
6565
<Pill title="The size of the raw encoded response body">
66-
{ getReadableSize(response.body.encoded.byteLength) }
66+
{ getReadableSize(response.body.encodedByteLength) }
6767
</Pill>
6868
{ props.showRequestOnViewPage &&
6969
<ShowRequestButton onClick={props.showRequestOnViewPage} />

‎src/components/view/http/http-body-card.tsx

+6-10
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,7 @@ export class HttpBodyCard extends React.Component<ExpandableCardProps & {
8989
? this.selectedContentType!
9090
: message.contentType;
9191

92-
const decodedBody = message.body.decoded;
93-
94-
if (decodedBody) {
92+
if (message.body.isDecoded()) {
9593
// We have successfully decoded the body content, show it:
9694
return <CollapsibleCard
9795
ariaLabel={ariaLabel}
@@ -102,7 +100,7 @@ export class HttpBodyCard extends React.Component<ExpandableCardProps & {
102100
>
103101
<header>
104102
<ReadonlyBodyCardHeader
105-
body={decodedBody}
103+
body={message.body.decodedData}
106104
mimeType={getHeaderValue(message.headers, 'content-type')}
107105
downloadFilename={getBodyDownloadFilename(url, message.headers)}
108106

@@ -128,16 +126,14 @@ export class HttpBodyCard extends React.Component<ExpandableCardProps & {
128126
expanded={!!expanded}
129127
cache={message.cache}
130128
>
131-
{decodedBody}
129+
{ message.body.decodedData }
132130
</ContentViewer>
133131
</EditorCardContent>
134132
</CollapsibleCard>;
135-
} else if (!decodedBody && message.body.decodingError) {
133+
} else if (message.body.isFailed()) {
136134
// We have failed to decode the body content! Show the error & raw encoded data instead:
137-
const error = message.body.decodingError as ErrorLike;
138-
const encodedBody = Buffer.isBuffer(message.body.encoded)
139-
? message.body.encoded
140-
: undefined;
135+
const error = message.body.decodingError;
136+
const encodedBody = message.body.encodedData;
141137

142138
const encodedDataContentType = ENCODED_DATA_CONTENT_TYPES.includes(this.selectedContentType!)
143139
? this.selectedContentType!

‎src/components/view/http/http-performance-card.tsx

+3-4
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,11 @@ const CompressionPerformance = observer((p: { exchange: HttpExchange }) => {
215215
const message = p.exchange[messageType];
216216
const encodings = getEncodings(message);
217217

218-
if (typeof message !== 'object' || !message.body.encoded.byteLength) return null;
218+
if (typeof message !== 'object' || !message.body.encodedByteLength) return null;
219219

220-
const encodedBody = message.body.encoded;
221-
const decodedBody = message.body.decoded;
220+
const encodedBodySize = message.body.encodedByteLength;
221+
const decodedBody = message.body.decodedData;
222222
const decodedBodySize = decodedBody ? decodedBody.byteLength : 0;
223-
const encodedBodySize = encodedBody.byteLength;
224223

225224
const encodingTestResults = _.mapKeys(testEncodings(message),
226225
(_size, encoding) => getEncodingName(encoding)

‎src/model/api/jsonrpc.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export async function parseRpcApiExchange(
3636
exchange: HttpExchange
3737
): Promise<JsonRpcApiExchange> {
3838
try {
39-
const body = await exchange.request.body.decodedPromise;
39+
const body = await exchange.request.body.waitForDecoding();
4040

4141
if (!body?.length) throw new Error(`No JSON-RPC request body`);
4242

‎src/model/events/bodies.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type EncodedSizesCache = Map<typeof EncodedSizesCacheKey,
1010
>;
1111

1212
export function testEncodings(message: ExchangeMessage): EncodedBodySizes | undefined {
13-
if (!message.body.decoded) return;
13+
if (!message.body.isDecoded()) return;
1414

1515
const encodedSizesCache = <EncodedSizesCache> message.cache;
1616
const existingObservable = encodedSizesCache.get(EncodedSizesCacheKey);
@@ -20,7 +20,7 @@ export function testEncodings(message: ExchangeMessage): EncodedBodySizes | unde
2020
const sizesObservable = observable.box<EncodedBodySizes | undefined>();
2121
encodedSizesCache.set(EncodedSizesCacheKey, sizesObservable);
2222

23-
testEncodingsAsync(message.body.decoded)
23+
testEncodingsAsync(message.body.decodedData)
2424
.then(action((testResults: EncodedBodySizes) => {
2525
sizesObservable.set(testResults)
2626
}))

‎src/model/events/content-types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export function getCompatibleTypes(
183183
let types = new Set([contentType]);
184184

185185
if (body && !Buffer.isBuffer(body)) {
186-
body = body.decoded;
186+
body = body.decodedData;
187187
}
188188

189189
// Examine the first char of the body, assuming it's ascii

‎src/model/filters/search-filters.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -1160,8 +1160,8 @@ class BodySizeFilter extends Filter {
11601160
const responseBody = event.isSuccessfulExchange()
11611161
? event.response.body
11621162
: undefined;
1163-
const totalSize = requestBody.encoded.byteLength +
1164-
(responseBody?.encoded.byteLength || 0);
1163+
const totalSize = requestBody.encodedByteLength +
1164+
(responseBody?.encodedByteLength || 0);
11651165

11661166
return event.isHttp() &&
11671167
this.predicate(totalSize, this.expectedSize);
@@ -1223,10 +1223,10 @@ class BodyFilter extends Filter {
12231223
if (!event.isHttp()) return false;
12241224
if (!event.hasRequestBody() && !event.hasResponseBody()) return false; // No body, no match
12251225

1226-
// Accessing .decoded on both of these touches a lazy promise, starting decoding for all bodies:
1227-
const requestBody = event.request.body.decoded;
1226+
// Accessing .decodedData on both of these starts decoding for all bodies async:
1227+
const requestBody = event.request.body.decodedData;
12281228
const responseBody = event.isSuccessfulExchange()
1229-
? event.response.body.decoded
1229+
? event.response.body.decodedData
12301230
: undefined;
12311231

12321232
const matchesRequestBody = !!requestBody && requestBody.byteLength > 0 &&
@@ -1286,8 +1286,8 @@ class ContainsFilter extends Filter {
12861286
...Object.entries(
12871287
event.request.rawHeaders
12881288
).map(([key, value]) => `${key}: ${value}`),
1289-
// Accessing .decoded touches a lazy promise, starting decoding:
1290-
event.request.body.decoded,
1289+
// Accessing .decodedData here starts decoding async:
1290+
event.request.body.decodedData,
12911291

12921292
...(event.isSuccessfulExchange()
12931293
? [
@@ -1296,8 +1296,8 @@ class ContainsFilter extends Filter {
12961296
...Object.entries(
12971297
event.response.rawHeaders
12981298
).map(([key, value]) => `${key}: ${value}`),
1299-
// Accessing .decoded touches a lazy promise, starting decoding:
1300-
event.response.body.decoded
1299+
// Accessing .decodedData here starts decoding async:
1300+
event.response.body.decodedData
13011301
] : []
13021302
),
13031303

‎src/model/http/caching.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export function explainCacheability(exchange: HttpExchange): (
167167
const revalidationSuggestion =
168168
!hasRevalidationOptions &&
169169
// Revalidation makes no sense without a body
170-
response.body.encoded.byteLength &&
170+
response.body.encodedByteLength &&
171171
!responseCCDirectives['immutable'] ?
172172
dedent`
173173
:suggestion: This response doesn't however include any validation headers. That

‎src/model/http/exchange.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -230,12 +230,12 @@ export class HttpExchange extends HTKEventBase implements ViewableHttpExchange {
230230
}
231231

232232
hasRequestBody(): this is CompletedRequest {
233-
return this.isCompletedRequest() && this.request.body.encoded.byteLength > 0;
233+
return this.isCompletedRequest() && this.request.body.encodedByteLength > 0;
234234
}
235235

236236
hasResponseBody(): this is SuccessfulExchange {
237237
return this.isSuccessfulExchange() &&
238-
(this.response as HtkResponse).body.encoded.byteLength > 0;
238+
(this.response as HtkResponse).body.encodedByteLength > 0;
239239
}
240240

241241
@observable

‎src/model/http/har.ts

+13-16
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020

2121
import { stringToBuffer } from '../../util/buffer';
2222
import { getHeaderValues, getHeaderValue } from '../../util/headers';
23-
import { ObservablePromise } from '../../util/observable';
23+
import { ObservablePromise, observablePromise } from '../../util/observable';
2424
import { unreachableCheck } from '../../util/error';
2525

2626
import { UI_VERSION } from '../../services/service-versions';
@@ -189,13 +189,10 @@ export function generateHarRequest(
189189
waitForDecoding: boolean,
190190
options: HarGenerationOptions
191191
): ExtendedHarRequest | ObservablePromise<ExtendedHarRequest> {
192-
if (waitForDecoding && (
193-
!request.body.decodedPromise.state ||
194-
request.body.decodedPromise.state === 'pending'
195-
)) {
196-
return request.body.decodedPromise.then(() =>
192+
if (waitForDecoding && request.body.isPending()) {
193+
return observablePromise(request.body.waitForDecoding().then(() =>
197194
generateHarRequest(request, false, options)
198-
);
195+
));
199196
}
200197

201198
const requestEntry: ExtendedHarRequest = {
@@ -214,27 +211,27 @@ export function generateHarRequest(
214211
})
215212
),
216213
headersSize: -1,
217-
bodySize: request.body.encoded.byteLength
214+
bodySize: request.body.encodedByteLength
218215
};
219216

220-
if (request.body.decoded) {
221-
if (request.body.decoded.byteLength > options.bodySizeLimit) {
217+
if (request.body.isDecoded()) {
218+
if (request.body.decodedData.byteLength > options.bodySizeLimit) {
222219
requestEntry._requestBodyStatus = 'discarded:too-large';
223220
requestEntry.comment = `Body discarded during HAR generation: longer than limit of ${
224221
options.bodySizeLimit
225222
} bytes`;
226223
} else {
227224
try {
228225
requestEntry.postData = generateHarPostBody(
229-
UTF8Decoder.decode(request.body.decoded),
226+
UTF8Decoder.decode(request.body.decodedData),
230227
getHeaderValue(request.headers, 'content-type') || 'application/octet-stream'
231228
);
232229
} catch (e) {
233230
if (e instanceof TypeError) {
234231
requestEntry._requestBodyStatus = 'discarded:not-representable';
235232
requestEntry._content = {
236-
text: request.body.decoded.toString('base64'),
237-
size: request.body.decoded.byteLength,
233+
text: request.body.decodedData.toString('base64'),
234+
size: request.body.decodedData.byteLength,
238235
encoding: 'base64',
239236
}
240237
} else {
@@ -331,7 +328,7 @@ async function generateHarResponse(
331328
};
332329
}
333330

334-
const decoded = await response.body.decodedPromise;
331+
const decoded = await response.body.waitForDecoding();
335332

336333
let responseContent: { text: string, encoding?: string } | { comment: string };
337334
try {
@@ -364,13 +361,13 @@ async function generateHarResponse(
364361
{
365362
mimeType: getHeaderValue(response.headers, 'content-type') ||
366363
'application/octet-stream',
367-
size: response.body.decoded?.byteLength || 0
364+
size: decoded?.byteLength || 0
368365
},
369366
responseContent
370367
),
371368
redirectURL: "",
372369
headersSize: -1,
373-
bodySize: response.body.encoded.byteLength || 0
370+
bodySize: response.body.encodedByteLength || 0
374371
};
375372
}
376373

0 commit comments

Comments
 (0)