fix(fetch): smarter JSON.stringify for application/json requests (#10245)

This commit is contained in:
Yury Semikhatsky 2021-11-11 11:12:24 -08:00 committed by GitHub
parent e3ba3eab11
commit fbb3c88f3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 94 additions and 6 deletions

View File

@ -142,14 +142,18 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
let multipartData: channels.FormField[] | undefined;
let postDataBuffer: Buffer | undefined;
if (options.data !== undefined) {
if (isString(options.data))
postDataBuffer = Buffer.from(options.data, 'utf8');
else if (Buffer.isBuffer(options.data))
if (isString(options.data)) {
if (isJsonContentType(headers))
jsonData = options.data;
else
postDataBuffer = Buffer.from(options.data, 'utf8');
} else if (Buffer.isBuffer(options.data)) {
postDataBuffer = options.data;
else if (typeof options.data === 'object')
} else if (typeof options.data === 'object' || typeof options.data === 'number' || typeof options.data === 'boolean') {
jsonData = options.data;
else
} else {
throw new Error(`Unexpected 'data' type`);
}
} else if (options.form) {
formData = objectToArray(options.form);
} else if (options.multipart) {
@ -301,3 +305,13 @@ async function readStreamToJson(stream: fs.ReadStream): Promise<ServerFilePayloa
buffer: buffer.toString('base64'),
};
}
function isJsonContentType(headers?: HeadersArray): boolean {
if (!headers)
return false;
for (const { name, value } of headers) {
if (name.toLocaleLowerCase() === 'content-type')
return value === 'application/json';
}
return false;
}

View File

@ -488,10 +488,24 @@ function parseCookie(header: string): types.NetworkCookie | null {
return cookie;
}
function isJsonParsable(value: any) {
if (typeof value !== 'string')
return false;
try {
JSON.parse(value);
return true;
} catch (e) {
if (e instanceof SyntaxError)
return false;
else
throw e;
}
}
function serializePostData(params: channels.APIRequestContextFetchParams, headers: { [name: string]: string }): Buffer | undefined {
assert((params.postData ? 1 : 0) + (params.jsonData ? 1 : 0) + (params.formData ? 1 : 0) + (params.multipartData ? 1 : 0) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
if (params.jsonData) {
const json = JSON.stringify(params.jsonData);
const json = isJsonParsable(params.jsonData) ? params.jsonData : JSON.stringify(params.jsonData);
headers['content-type'] ??= 'application/json';
return Buffer.from(json, 'utf8');
} else if (params.formData) {

View File

@ -234,3 +234,63 @@ it('should remove content-length from reidrected post requests', async ({ playwr
expect(req2.headers['content-length']).toBe(undefined);
await request.dispose();
});
const serialization = [
['object', { 'foo': 'bar' }],
['array', ['foo', 'bar', 2021]],
['string', 'foo'],
['bool', true],
['number', 2021],
];
for (const [type, value] of serialization) {
const stringifiedValue = JSON.stringify(value);
it(`should json stringify ${type} body when content-type is application/json`, async ({ playwright, server }) => {
const request = await playwright.request.newContext();
const [req] = await Promise.all([
server.waitForRequest('/empty.html'),
request.post(server.EMPTY_PAGE, {
headers: {
'content-type': 'application/json'
},
data: value
})
]);
const body = await req.postBody;
expect(body.toString()).toEqual(stringifiedValue);
await request.dispose();
});
it(`should not double stringify ${type} body when content-type is application/json`, async ({ playwright, server }) => {
const request = await playwright.request.newContext();
const [req] = await Promise.all([
server.waitForRequest('/empty.html'),
request.post(server.EMPTY_PAGE, {
headers: {
'content-type': 'application/json'
},
data: stringifiedValue
})
]);
const body = await req.postBody;
expect(body.toString()).toEqual(stringifiedValue);
await request.dispose();
});
}
it(`should accept already serialized data as Buffer when content-type is application/json`, async ({ playwright, server }) => {
const request = await playwright.request.newContext();
const value = JSON.stringify(JSON.stringify({ 'foo': 'bar' }));
const [req] = await Promise.all([
server.waitForRequest('/empty.html'),
request.post(server.EMPTY_PAGE, {
headers: {
'content-type': 'application/json'
},
data: Buffer.from(value, 'utf8')
})
]);
const body = await req.postBody;
expect(body.toString()).toEqual(value);
await request.dispose();
});