diff --git a/benchmarks/api/util.mjs b/benchmarks/api/util.mjs new file mode 100644 index 00000000000..34c54015ed8 --- /dev/null +++ b/benchmarks/api/util.mjs @@ -0,0 +1,37 @@ +import { bench, group, run } from 'mitata' +import { isContentTypeText, isContentTypeApplicationJson } from '../../lib/api/util.js' + +const html = 'text/html' +const json = 'application/json; charset=UTF-8' + +group('isContentTypeText', () => { + bench(`isContentTypeText('${html}')`, () => { + return isContentTypeText(html) + }) + bench(`isContentTypeText('${json}')`, () => { + return isContentTypeText(json) + }) + bench('html.startsWith(\'text/\')', () => { + return html.startsWith('text/') + }) + bench('json.startsWith(\'text/\')', () => { + return json.startsWith('text/') + }) +}) + +group('isContentTypeApplicationJson', () => { + bench(`isContentTypeApplicationJson('${html}')`, () => { + return isContentTypeApplicationJson(html) + }) + bench(`isContentTypeApplicationJson('${json}')`, () => { + return isContentTypeApplicationJson(json) + }) + bench('html.startsWith(\'application/json\')', () => { + return html.startsWith('application/json') + }) + bench('json.startsWith(\'application/json\')', () => { + return json.startsWith('application/json') + }) +}) + +await run() diff --git a/lib/api/util.js b/lib/api/util.js index 24f69d12bd3..c5573bf3f8c 100644 --- a/lib/api/util.js +++ b/lib/api/util.js @@ -21,28 +21,66 @@ async function getResolveErrorBodyCallback ({ callback, body, contentType, statu } } + const message = `Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}` + if (statusCode === 204 || !contentType || !chunks) { - process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers)) + queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers))) return } - try { - if (contentType.startsWith('application/json')) { - const payload = JSON.parse(chunksDecode(chunks, length)) - process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload)) - return - } + const stackTraceLimit = Error.stackTraceLimit + Error.stackTraceLimit = 0 + let payload - if (contentType.startsWith('text/')) { - const payload = chunksDecode(chunks, length) - process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload)) - return + try { + if (isContentTypeApplicationJson(contentType)) { + payload = JSON.parse(chunksDecode(chunks, length)) + } else if (isContentTypeText(contentType)) { + payload = chunksDecode(chunks, length) } - } catch (err) { - // Process in a fallback if error + } catch { + // process in a callback to avoid throwing in the microtask queue + } finally { + Error.stackTraceLimit = stackTraceLimit } + queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers, payload))) +} + +const isContentTypeApplicationJson = (contentType) => { + return ( + contentType.length > 15 && + contentType[11] === '/' && + contentType[0] === 'a' && + contentType[1] === 'p' && + contentType[2] === 'p' && + contentType[3] === 'l' && + contentType[4] === 'i' && + contentType[5] === 'c' && + contentType[6] === 'a' && + contentType[7] === 't' && + contentType[8] === 'i' && + contentType[9] === 'o' && + contentType[10] === 'n' && + contentType[12] === 'j' && + contentType[13] === 's' && + contentType[14] === 'o' && + contentType[15] === 'n' + ) +} - process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers)) +const isContentTypeText = (contentType) => { + return ( + contentType.length > 4 && + contentType[4] === '/' && + contentType[0] === 't' && + contentType[1] === 'e' && + contentType[2] === 'x' && + contentType[3] === 't' + ) } -module.exports = { getResolveErrorBodyCallback } +module.exports = { + getResolveErrorBodyCallback, + isContentTypeApplicationJson, + isContentTypeText +}