Skip to content

Commit d1fbd19

Browse files
committed
feat: add request.body async getters
1 parent 14e5dce commit d1fbd19

File tree

22 files changed

+307
-68
lines changed

22 files changed

+307
-68
lines changed

docs/src/api/class-request.md

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,32 @@ request is issued to a redirected url.
2323

2424
An object with all the request HTTP headers associated with this request. The header names are lower-cased.
2525

26+
## async method: Request.body
27+
* since: v1.58
28+
- returns: <[null]|[string]>
29+
30+
The request body, if present.
31+
32+
## async method: Request.bodyBuffer
33+
* since: v1.58
34+
- returns: <[null]|[Buffer]>
35+
36+
The request body in a binary form. Returns null if the request has no body.
37+
38+
## async method: Request.bodyJSON
39+
* since: v1.58
40+
* langs: js, python
41+
- returns: <[null]|[Serializable]>
42+
43+
Returns the request body as a parsed JSON object. If the request `Content-Type` is `application/x-www-form-urlencoded`, this method returns a key/value object parsed from the form data. Otherwise, it parses the body as JSON.
44+
45+
## async method: Request.bodyJSON
46+
* since: v1.58
47+
* langs: csharp
48+
- returns: <[null]|[JsonElement]>
49+
50+
Returns the request body as a parsed JSON object. If the request `Content-Type` is `application/x-www-form-urlencoded`, this method returns a key/value object parsed from the form data. Otherwise, it parses the body as JSON.
51+
2652
## method: Request.failure
2753
* since: v1.8
2854
- returns: <[null]|[string]>
@@ -149,35 +175,33 @@ Request's method (GET, POST, etc.)
149175

150176
## method: Request.postData
151177
* since: v1.8
178+
* discouraged: Use [`method: Request.body`] instead.
152179
- returns: <[null]|[string]>
153180

154-
Request's post body, if any.
181+
The request body, if present.
155182

156183
## method: Request.postDataBuffer
157184
* since: v1.8
185+
* discouraged: Use [`method: Request.bodyBuffer`] instead.
158186
- returns: <[null]|[Buffer]>
159187

160-
Request's post body in a binary form, if any.
188+
The request body in a binary form. Returns null if the request has no body.
161189

162190
## method: Request.postDataJSON
163191
* since: v1.8
164192
* langs: js, python
193+
* discouraged: Use [`method: Request.bodyJSON`] instead.
165194
- returns: <[null]|[Serializable]>
166195

167-
Returns parsed request's body for `form-urlencoded` and JSON as a fallback if any.
168-
169-
When the response is `application/x-www-form-urlencoded` then a key/value object of the values will be returned.
170-
Otherwise it will be parsed as JSON.
196+
Returns the request body as a parsed JSON object. If the request `Content-Type` is `application/x-www-form-urlencoded`, this method returns a key/value object parsed from the form data. Otherwise, it parses the body as JSON.
171197

172198
## method: Request.postDataJSON
173199
* since: v1.12
174200
* langs: csharp
201+
* discouraged: Use [`method: Request.bodyJSON`] instead.
175202
- returns: <[null]|[JsonElement]>
176203

177-
Returns parsed request's body for `form-urlencoded` and JSON as a fallback if any.
178-
179-
When the response is `application/x-www-form-urlencoded` then a key/value object of the values will be returned.
180-
Otherwise it will be parsed as JSON.
204+
Returns the request body as a parsed JSON object. If the request `Content-Type` is `application/x-www-form-urlencoded`, this method returns a key/value object parsed from the form data. Otherwise, it parses the body as JSON.
181205

182206
## method: Request.redirectedFrom
183207
* since: v1.8

packages/playwright-client/types/types.d.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20616,6 +20616,23 @@ export interface Request {
2061620616
*/
2061720617
allHeaders(): Promise<{ [key: string]: string; }>;
2061820618

20619+
/**
20620+
* The request body, if present.
20621+
*/
20622+
body(): Promise<null|string>;
20623+
20624+
/**
20625+
* The request body in a binary form. Returns null if the request has no body.
20626+
*/
20627+
bodyBuffer(): Promise<null|Buffer>;
20628+
20629+
/**
20630+
* Returns the request body as a parsed JSON object. If the request `Content-Type` is
20631+
* `application/x-www-form-urlencoded`, this method returns a key/value object parsed from the form data. Otherwise,
20632+
* it parses the body as JSON.
20633+
*/
20634+
bodyJSON(): Promise<null|Serializable>;
20635+
2061920636
/**
2062020637
* The method returns `null` unless this request has failed, as reported by `requestfailed` event.
2062120638
*
@@ -20713,20 +20730,25 @@ export interface Request {
2071320730
method(): string;
2071420731

2071520732
/**
20716-
* Request's post body, if any.
20733+
* **NOTE** Use [request.body()](https://playwright.dev/docs/api/class-request#request-body) instead.
20734+
*
20735+
* The request body, if present.
2071720736
*/
2071820737
postData(): null|string;
2071920738

2072020739
/**
20721-
* Request's post body in a binary form, if any.
20740+
* **NOTE** Use [request.bodyBuffer()](https://playwright.dev/docs/api/class-request#request-body-buffer) instead.
20741+
*
20742+
* The request body in a binary form. Returns null if the request has no body.
2072220743
*/
2072320744
postDataBuffer(): null|Buffer;
2072420745

2072520746
/**
20726-
* Returns parsed request's body for `form-urlencoded` and JSON as a fallback if any.
20747+
* **NOTE** Use [request.bodyJSON()](https://playwright.dev/docs/api/class-request#request-body-json) instead.
2072720748
*
20728-
* When the response is `application/x-www-form-urlencoded` then a key/value object of the values will be returned.
20729-
* Otherwise it will be parsed as JSON.
20749+
* Returns the request body as a parsed JSON object. If the request `Content-Type` is
20750+
* `application/x-www-form-urlencoded`, this method returns a key/value object parsed from the form data. Otherwise,
20751+
* it parses the body as JSON.
2073020752
*/
2073120753
postDataJSON(): null|Serializable;
2073220754

packages/playwright-core/src/client/network.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap
134134
return this._fallbackOverrides.method || this._initializer.method;
135135
}
136136

137+
async body(): Promise<string | null> {
138+
return (this._fallbackOverrides.postDataBuffer || (await this._channel.body()).body)?.toString('utf-8') || null;
139+
}
140+
141+
async bodyBuffer(): Promise<Buffer | null> {
142+
return this._fallbackOverrides.postDataBuffer || (await this._channel.body()).body || null;
143+
}
144+
145+
async bodyJSON(): Promise<Object | null> {
146+
return this._postDataJSON(await this.body());
147+
}
148+
137149
postData(): string | null {
138150
return (this._fallbackOverrides.postDataBuffer || this._initializer.postData)?.toString('utf-8') || null;
139151
}
@@ -144,6 +156,10 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap
144156

145157
postDataJSON(): Object | null {
146158
const postData = this.postData();
159+
return this._postDataJSON(postData);
160+
}
161+
162+
private _postDataJSON(postData: string | null): Object | null {
147163
if (!postData)
148164
return null;
149165

packages/playwright-core/src/protocol/validator.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2249,6 +2249,10 @@ scheme.RequestInitializer = tObject({
22492249
hasResponse: tBoolean,
22502250
});
22512251
scheme.RequestResponseEvent = tOptional(tObject({}));
2252+
scheme.RequestBodyParams = tOptional(tObject({}));
2253+
scheme.RequestBodyResult = tObject({
2254+
body: tOptional(tBinary),
2255+
});
22522256
scheme.RequestResponseParams = tOptional(tObject({}));
22532257
scheme.RequestResponseResult = tObject({
22542258
response: tOptional(tChannel(['Response'])),

packages/playwright-core/src/server/bidi/bidiBrowser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class BidiBrowser extends Browser {
7575
});
7676

7777
await browser._browserSession.send('network.addDataCollector', {
78-
dataTypes: [bidi.Network.DataType.Response],
78+
dataTypes: [bidi.Network.DataType.Request, bidi.Network.DataType.Response],
7979
maxEncodedDataSize: 20_000_000, // same default as in CDP: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/inspector/inspector_network_agent.cc;l=134;drc=4128411589187a396829a827f59a655bed876aa7
8080
});
8181

packages/playwright-core/src/server/bidi/bidiNetworkManager.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ export class BidiNetworkManager {
7979
route = new BidiRouteImpl(this._session, param.request.request);
8080
}
8181
}
82-
const request = new BidiRequest(frame, redirectedFrom, param, route);
82+
const getRequestBody = param.request.bodySize ? () => getNetworkData(this._session, param.request, bidi.Network.DataType.Request) : null;
83+
const request = new BidiRequest(frame, redirectedFrom, param, getRequestBody, route);
8384
this._requests.set(request._id, request);
8485
this._page.frameManager.requestStarted(request.request, route);
8586
}
@@ -88,11 +89,7 @@ export class BidiNetworkManager {
8889
const request = this._requests.get(params.request.request);
8990
if (!request)
9091
return;
91-
const getResponseBody = async () => {
92-
const { bytes } = await this._session.send('network.getData', { request: params.request.request, dataType: bidi.Network.DataType.Response });
93-
const encoding = bytes.type === 'base64' ? 'base64' : 'utf8';
94-
return Buffer.from(bytes.value, encoding);
95-
};
92+
const getResponseBody = () => getNetworkData(this._session, params.request, bidi.Network.DataType.Response);
9693
const timings = params.request.timings;
9794
const startTime = timings.requestTime;
9895
function relativeToStart(time: number): number {
@@ -236,14 +233,12 @@ class BidiRequest {
236233
// store the first and only Route in the chain (if any).
237234
_originalRequestRoute: BidiRouteImpl | undefined;
238235

239-
constructor(frame: frames.Frame, redirectedFrom: BidiRequest | null, payload: bidi.Network.BeforeRequestSentParameters, route: BidiRouteImpl | undefined) {
236+
constructor(frame: frames.Frame, redirectedFrom: BidiRequest | null, payload: bidi.Network.BeforeRequestSentParameters, getBody: (() => Promise<Buffer>) | null, route: BidiRouteImpl | undefined) {
240237
this._id = payload.request.request;
241238
if (redirectedFrom)
242239
redirectedFrom._redirectedTo = this;
243-
// TODO: missing in the spec?
244-
const postDataBuffer = null;
245240
this.request = new network.Request(frame._page.browserContext, frame, null, redirectedFrom ? redirectedFrom.request : null, payload.navigation ?? undefined, payload.request.url,
246-
resourceTypeFromBidi(payload.request.destination, payload.request.initiatorType, payload.initiator?.type), payload.request.method, postDataBuffer, fromBidiHeaders(payload.request.headers));
241+
resourceTypeFromBidi(payload.request.destination, payload.request.initiatorType, payload.initiator?.type), payload.request.method, null, fromBidiHeaders(payload.request.headers), getBody);
247242
// "raw" headers are the same as "provisional" headers in Bidi.
248243
this.request.setRawRequestHeaders(null);
249244
this.request._setBodySize(payload.request.bodySize || 0);
@@ -390,3 +385,9 @@ function resourceTypeFromBidi(requestDestination: string, requestInitiatorType:
390385
default: return 'other';
391386
}
392387
}
388+
389+
async function getNetworkData(session: BidiSession, request: bidi.Network.RequestData, dataType: bidi.Network.DataType) {
390+
const { bytes } = await session.send('network.getData', { request: request.request, dataType });
391+
const encoding = bytes.type === 'base64' ? 'base64' : 'utf8';
392+
return Buffer.from(bytes.value, encoding);
393+
}

packages/playwright-core/src/server/chromium/crNetworkManager.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -598,14 +598,23 @@ class InterceptableRequest {
598598
headers,
599599
method,
600600
url,
601+
hasPostData = false,
601602
postDataEntries = null,
602603
} = requestPausedEvent ? requestPausedEvent.request : requestWillBeSentEvent.request;
603604
let postDataBuffer = null;
605+
let getPostData: (() => Promise<Buffer>) | null = null;
604606
const entries = postDataEntries?.filter(entry => entry.bytes);
605-
if (entries && entries.length)
607+
if (entries && entries.length) {
606608
postDataBuffer = Buffer.concat(entries.map(entry => Buffer.from(entry.bytes!, 'base64')));
609+
} else if (hasPostData) {
610+
getPostData = async () => {
611+
const requestId = requestPausedEvent ? requestPausedEvent.requestId : requestWillBeSentEvent.requestId;
612+
const { postData } = await session.send('Network.getRequestPostData', { requestId });
613+
return Buffer.from(postData);
614+
};
615+
}
607616

608-
this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, toResourceType(requestWillBeSentEvent.type || 'Other'), method, postDataBuffer, headersOverride || headersObjectToArray(headers));
617+
this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, toResourceType(requestWillBeSentEvent.type || 'Other'), method, postDataBuffer, headersOverride || headersObjectToArray(headers), getPostData);
609618
}
610619
}
611620

packages/playwright-core/src/server/dispatchers/networkDispatchers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ export class RequestDispatcher extends Dispatcher<Request, channels.RequestChann
6666
this.addObjectListener(Request.Events.Response, () => this._dispatchEvent('response', {}));
6767
}
6868

69+
async body(params: channels.RequestBodyParams, progress: Progress): Promise<channels.RequestBodyResult> {
70+
const body = await this._object.body();
71+
return { body: body === null ? undefined : body };
72+
}
73+
6974
async rawRequestHeaders(params: channels.RequestRawRequestHeadersParams, progress: Progress): Promise<channels.RequestRawRequestHeadersResult> {
7075
return { headers: await progress.race(this._object.rawRequestHeaders()) };
7176
}

packages/playwright-core/src/server/network.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ export class Request extends SdkObject {
179179
private _resourceType: ResourceType;
180180
private _method: string;
181181
private _postData: Buffer | null;
182+
private _getPostData: (() => Promise<Buffer>) | null;
182183
readonly _headers: HeadersArray;
183184
readonly _frame: frames.Frame | null = null;
184185
readonly _serviceWorker: pages.Worker | null = null;
@@ -195,7 +196,7 @@ export class Request extends SdkObject {
195196
};
196197

197198
constructor(context: contexts.BrowserContext, frame: frames.Frame | null, serviceWorker: pages.Worker | null, redirectedFrom: Request | null, documentId: string | undefined,
198-
url: string, resourceType: ResourceType, method: string, postData: Buffer | null, headers: HeadersArray) {
199+
url: string, resourceType: ResourceType, method: string, postData: Buffer | null, headers: HeadersArray, getPostData: (() => Promise<Buffer>) | null = null) {
199200
super(frame || context, 'request');
200201
assert(!url.startsWith('data:'), 'Data urls should not fire requests');
201202
this._context = context;
@@ -209,6 +210,7 @@ export class Request extends SdkObject {
209210
this._resourceType = resourceType;
210211
this._method = method;
211212
this._postData = postData;
213+
this._getPostData = getPostData;
212214
this._headers = headers;
213215
this._isFavicon = url.endsWith('/favicon.ico') || !!redirectedFrom?._isFavicon;
214216
}
@@ -243,6 +245,14 @@ export class Request extends SdkObject {
243245
return this._overrides?.postData || this._postData;
244246
}
245247

248+
async body(): Promise<Buffer | null> {
249+
if (this._overrides?.postData)
250+
return this._overrides.postData;
251+
if (this._getPostData)
252+
return await this._getPostData();
253+
return this._postData;
254+
}
255+
246256
headers(): HeadersArray {
247257
return this._overrides?.headers || this._headers;
248258
}

packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ export const methodMetainfo = new Map<string, { internal?: boolean, title?: stri
239239
['ElementHandle.uncheck', { title: 'Uncheck', slowMo: true, snapshot: true, pausesBeforeInput: true, }],
240240
['ElementHandle.waitForElementState', { title: 'Wait for state', snapshot: true, pausesBeforeAction: true, }],
241241
['ElementHandle.waitForSelector', { title: 'Wait for selector', snapshot: true, }],
242+
['Request.body', { title: 'Get request body', group: 'getter', }],
242243
['Request.response', { internal: true, }],
243244
['Request.rawRequestHeaders', { internal: true, }],
244245
['Route.redirectNavigationRequest', { internal: true, }],

0 commit comments

Comments
 (0)