Skip to content

Commit 1cc2a2d

Browse files
mxschmitttnolet
andauthored
feat(har): add bodySize, transportSize, headersSize (microsoft#7470)
Co-authored-by: tnolet <tim@checklyhq.com>
1 parent 07d4458 commit 1cc2a2d

8 files changed

Lines changed: 201 additions & 34 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*.swp
88
*.pyc
99
.vscode
10+
.idea
1011
yarn.lock
1112
/src/generated/*
1213
lib/

src/server/chromium/crNetworkManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ export class CRNetworkManager {
310310
responseStart: -1,
311311
};
312312
}
313-
const response = new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers), timing, getResponseBody);
313+
const response = new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers), timing, getResponseBody, responsePayload.protocol);
314314
if (responsePayload?.remoteIPAddress && typeof responsePayload?.remotePort === 'number') {
315315
response._serverAddrFinished({
316316
ipAddress: responsePayload.remoteIPAddress,
@@ -361,7 +361,7 @@ export class CRNetworkManager {
361361
// event from protocol. @see https://crbug.com/883475
362362
const response = request.request._existingResponse();
363363
if (response)
364-
response._requestFinished(helper.secondsToRoundishMillis(event.timestamp - request._timestamp));
364+
response._requestFinished(helper.secondsToRoundishMillis(event.timestamp - request._timestamp), undefined, event.encodedDataLength);
365365
this._requestIdToRequest.delete(request._requestId);
366366
if (request._interceptionId)
367367
this._attemptedAuthentications.delete(request._interceptionId);

src/server/firefox/ffNetworkManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export class FFNetworkManager {
118118
response._requestFinished(this._relativeTiming(event.responseEndTime), 'Response body is unavailable for redirect responses');
119119
} else {
120120
this._requests.delete(request._id);
121-
response._requestFinished(this._relativeTiming(event.responseEndTime));
121+
response._requestFinished(this._relativeTiming(event.responseEndTime), undefined, event.transferSize);
122122
}
123123
this._page._frameManager.requestFinished(request.request);
124124
}

src/server/network.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,10 @@ export class Response extends SdkObject {
311311
private _serverAddrPromiseCallback: (arg?: RemoteAddr) => void = () => {};
312312
private _securityDetailsPromise: Promise<SecurityDetails|undefined>;
313313
private _securityDetailsPromiseCallback: (arg?: SecurityDetails) => void = () => {};
314+
_httpVersion: string | undefined;
315+
_transferSize: number | undefined;
314316

315-
constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray, timing: ResourceTiming, getResponseBodyCallback: GetResponseBodyCallback) {
317+
constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray, timing: ResourceTiming, getResponseBodyCallback: GetResponseBodyCallback, httpVersion?: string) {
316318
super(request.frame(), 'response');
317319
this._request = request;
318320
this._timing = timing;
@@ -333,6 +335,7 @@ export class Response extends SdkObject {
333335
this._finishedPromiseCallback = f;
334336
});
335337
this._request._setResponse(this);
338+
this._httpVersion = httpVersion;
336339
}
337340

338341
_serverAddrFinished(addr?: RemoteAddr) {
@@ -343,11 +346,16 @@ export class Response extends SdkObject {
343346
this._securityDetailsPromiseCallback(securityDetails);
344347
}
345348

346-
_requestFinished(responseEndTiming: number, error?: string) {
349+
_requestFinished(responseEndTiming: number, error?: string, transferSize?: number) {
347350
this._request._responseEndTiming = Math.max(responseEndTiming, this._timing.responseStart);
351+
this._transferSize = transferSize;
348352
this._finishedPromiseCallback({ error });
349353
}
350354

355+
_setHttpVersion(httpVersion: string) {
356+
this._httpVersion = httpVersion;
357+
}
358+
351359
url(): string {
352360
return this._url;
353361
}

src/server/supplements/har/har.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export type Response = {
8181
redirectURL: string;
8282
headersSize: number;
8383
bodySize: number;
84+
_transferSize: number;
8485
};
8586

8687
export type Cookie = {

src/server/supplements/har/harTracer.ts

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { URL } from 'url';
1718
import fs from 'fs';
1819
import { BrowserContext } from '../../browserContext';
1920
import { helper } from '../../helper';
2021
import * as network from '../../network';
2122
import { Page } from '../../page';
2223
import * as har from './har';
24+
import * as types from '../../types';
25+
26+
const FALLBACK_HTTP_VERSION = 'HTTP/1.1';
2327

2428
type HarOptions = {
2529
path: string;
@@ -51,6 +55,7 @@ export class HarTracer {
5155
};
5256
context.on(BrowserContext.Events.Page, (page: Page) => this._ensurePageEntry(page));
5357
context.on(BrowserContext.Events.Request, (request: network.Request) => this._onRequest(request));
58+
context.on(BrowserContext.Events.RequestFinished, (request: network.Request) => this._onRequestFinished(request).catch(() => {}));
5459
context.on(BrowserContext.Events.Response, (response: network.Response) => this._onResponse(response));
5560
}
5661

@@ -128,27 +133,28 @@ export class HarTracer {
128133
request: {
129134
method: request.method(),
130135
url: request.url(),
131-
httpVersion: 'HTTP/1.1',
136+
httpVersion: FALLBACK_HTTP_VERSION,
132137
cookies: [],
133138
headers: [],
134139
queryString: [...url.searchParams].map(e => ({ name: e[0], value: e[1] })),
135-
postData: undefined,
140+
postData: postDataForHar(request),
136141
headersSize: -1,
137-
bodySize: -1,
142+
bodySize: calculateRequestBodySize(request) || 0,
138143
},
139144
response: {
140145
status: -1,
141146
statusText: '',
142-
httpVersion: 'HTTP/1.1',
147+
httpVersion: FALLBACK_HTTP_VERSION,
143148
cookies: [],
144149
headers: [],
145150
content: {
146151
size: -1,
147-
mimeType: request.headerValue('content-type') || 'application/octet-stream',
152+
mimeType: request.headerValue('content-type') || 'x-unknown',
148153
},
149154
headersSize: -1,
150155
bodySize: -1,
151-
redirectURL: ''
156+
redirectURL: '',
157+
_transferSize: -1
152158
},
153159
cache: {
154160
beforeRequest: null,
@@ -168,29 +174,63 @@ export class HarTracer {
168174
this._entries.set(request, harEntry);
169175
}
170176

177+
private async _onRequestFinished(request: network.Request) {
178+
const page = request.frame()._page;
179+
const harEntry = this._entries.get(request)!;
180+
const response = await request.response();
181+
182+
if (!response)
183+
return;
184+
185+
const httpVersion = normaliseHttpVersion(response._httpVersion);
186+
const transferSize = response._transferSize || -1;
187+
const headersSize = calculateResponseHeadersSize(httpVersion, response.status(), response.statusText(), response.headers());
188+
const bodySize = transferSize !== -1 ? transferSize - headersSize : -1;
189+
190+
harEntry.request.httpVersion = httpVersion;
191+
harEntry.response.bodySize = bodySize;
192+
harEntry.response.headersSize = headersSize;
193+
harEntry.response._transferSize = transferSize;
194+
harEntry.request.headersSize = calculateRequestHeadersSize(request.method(), request.url(), httpVersion, request.headers());
195+
196+
const promise = response.body().then(buffer => {
197+
const content = harEntry.response.content;
198+
content.size = buffer.length;
199+
content.compression = harEntry.response.bodySize !== -1 ? buffer.length - harEntry.response.bodySize : 0;
200+
201+
if (!this._options.omitContent && buffer && buffer.length > 0) {
202+
content.text = buffer.toString('base64');
203+
content.encoding = 'base64';
204+
}
205+
}).catch(() => {});
206+
this._addBarrier(page, promise);
207+
}
208+
171209
private _onResponse(response: network.Response) {
172210
const page = response.frame()._page;
173211
const pageEntry = this._ensurePageEntry(page);
174212
const harEntry = this._entries.get(response.request())!;
175213
// Rewrite provisional headers with actual
176214
const request = response.request();
215+
177216
harEntry.request.headers = request.headers().map(header => ({ name: header.name, value: header.value }));
178217
harEntry.request.cookies = cookiesForHar(request.headerValue('cookie'), ';');
179-
harEntry.request.postData = postDataForHar(request) || undefined;
218+
harEntry.request.postData = postDataForHar(request);
180219

181220
harEntry.response = {
182221
status: response.status(),
183222
statusText: response.statusText(),
184-
httpVersion: 'HTTP/1.1',
223+
httpVersion: normaliseHttpVersion(response._httpVersion),
185224
cookies: cookiesForHar(response.headerValue('set-cookie'), '\n'),
186225
headers: response.headers().map(header => ({ name: header.name, value: header.value })),
187226
content: {
188227
size: -1,
189-
mimeType: response.headerValue('content-type') || 'application/octet-stream',
228+
mimeType: response.headerValue('content-type') || 'x-unknown',
190229
},
191230
headersSize: -1,
192231
bodySize: -1,
193-
redirectURL: ''
232+
redirectURL: '',
233+
_transferSize: -1
194234
};
195235
const timing = response.timing();
196236
if (pageEntry.startedDateTime.valueOf() > timing.startTime)
@@ -220,14 +260,6 @@ export class HarTracer {
220260
if (details)
221261
harEntry._securityDetails = details;
222262
}));
223-
224-
if (!this._options.omitContent && response.status() === 200) {
225-
const promise = response.body().then(buffer => {
226-
harEntry.response.content.text = buffer.toString('base64');
227-
harEntry.response.content.encoding = 'base64';
228-
}).catch(() => {});
229-
this._addBarrier(page, promise);
230-
}
231263
}
232264

233265
async flush() {
@@ -246,10 +278,10 @@ export class HarTracer {
246278
}
247279
}
248280

249-
function postDataForHar(request: network.Request): har.PostData | null {
281+
function postDataForHar(request: network.Request): har.PostData | undefined {
250282
const postData = request.postDataBuffer();
251283
if (!postData)
252-
return null;
284+
return;
253285

254286
const contentType = request.headerValue('content-type') || 'application/octet-stream';
255287
const result: har.PostData = {
@@ -305,3 +337,33 @@ function parseCookie(c: string): har.Cookie {
305337
}
306338
return cookie;
307339
}
340+
341+
function calculateResponseHeadersSize(protocol: string, status: number, statusText: string , headers: types.HeadersArray) {
342+
let rawHeaders = `${protocol} ${status} ${statusText}\r\n`;
343+
for (const header of headers)
344+
rawHeaders += `${header.name}: ${header.value}\r\n`;
345+
rawHeaders += '\r\n';
346+
return rawHeaders.length;
347+
}
348+
349+
function calculateRequestHeadersSize(method: string, url: string, httpVersion: string, headers: types.HeadersArray) {
350+
let rawHeaders = `${method} ${(new URL(url)).pathname} ${httpVersion}\r\n`;
351+
for (const header of headers)
352+
rawHeaders += `${header.name}: ${header.value}\r\n`;
353+
return rawHeaders.length;
354+
}
355+
356+
function normaliseHttpVersion(httpVersion?: string) {
357+
if (!httpVersion)
358+
return FALLBACK_HTTP_VERSION;
359+
if (httpVersion === 'http/1.1')
360+
return 'HTTP/1.1';
361+
return httpVersion;
362+
}
363+
364+
function calculateRequestBodySize(request: network.Request): number|undefined {
365+
const postData = request.postDataBuffer();
366+
if (!postData)
367+
return;
368+
return new TextEncoder().encode(postData.toString('utf8')).length;
369+
}

src/server/webkit/wkPage.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1044,7 +1044,11 @@ export class WKPage implements PageDelegate {
10441044
validFrom: responseReceivedPayload?.response.security?.certificate?.validFrom,
10451045
validTo: responseReceivedPayload?.response.security?.certificate?.validUntil,
10461046
});
1047-
response._requestFinished(helper.secondsToRoundishMillis(event.timestamp - request._timestamp));
1047+
const { responseBodyBytesReceived, responseHeaderBytesReceived } = event.metrics || {};
1048+
const transferSize = responseBodyBytesReceived ? responseBodyBytesReceived + (responseHeaderBytesReceived || 0) : undefined;
1049+
if (event.metrics?.protocol)
1050+
response._setHttpVersion(event.metrics.protocol);
1051+
response._requestFinished(helper.secondsToRoundishMillis(event.timestamp - request._timestamp), undefined, transferSize);
10481052
}
10491053

10501054
this._requestIdToResponseReceivedPayloadEvent.delete(request._requestId);

0 commit comments

Comments
 (0)