fetch-request-body.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import HttpErrors from 'http-errors';
  2. import {createError} from './create-error.js';
  3. import {parseContentType} from './parse-content-type.js';
  4. import {Errorf} from '@e22m4u/js-format';
  5. import {IncomingMessage} from 'http';
  6. /**
  7. * Buffer encoding.
  8. *
  9. * @type {import('buffer').BufferEncoding[]}
  10. */
  11. export const BUFFER_ENCODING_LIST = [
  12. 'ascii',
  13. 'utf8',
  14. 'utf-8',
  15. 'utf16le',
  16. 'utf-16le',
  17. 'ucs2',
  18. 'ucs-2',
  19. 'base64',
  20. 'base64url',
  21. 'latin1',
  22. 'binary',
  23. 'hex',
  24. ];
  25. /**
  26. * Fetch request body.
  27. *
  28. * @param {IncomingMessage} req
  29. * @param {number} bodyBytesLimit
  30. * @returns {Promise<string|undefined>}
  31. */
  32. export function fetchRequestBody(req, bodyBytesLimit = 0) {
  33. if (!(req instanceof IncomingMessage))
  34. throw new Errorf(
  35. 'The first parameter of "fetchRequestBody" should be ' +
  36. 'an IncomingMessage instance, but %v given.',
  37. req,
  38. );
  39. if (typeof bodyBytesLimit !== 'number')
  40. throw new Errorf(
  41. 'The parameter "bodyBytesLimit" of "fetchRequestBody" ' +
  42. 'should be a number, but %v given.',
  43. bodyBytesLimit,
  44. );
  45. return new Promise((resolve, reject) => {
  46. // сравнение внутреннего ограничения
  47. // размера тела запроса с заголовком
  48. // "content-length"
  49. const contentLength = parseInt(req.headers['content-length'] || '0', 10);
  50. if (bodyBytesLimit && contentLength && contentLength > bodyBytesLimit)
  51. throw createError(
  52. HttpErrors.PayloadTooLarge,
  53. 'Request body limit is %s bytes, but %s bytes given.',
  54. bodyBytesLimit,
  55. contentLength,
  56. );
  57. // определение кодировки
  58. // по заголовку "content-type"
  59. let encoding = 'utf-8';
  60. const contentType = req.headers['content-type'] || '';
  61. if (contentType) {
  62. const parsedContentType = parseContentType(contentType);
  63. if (parsedContentType && parsedContentType.charset) {
  64. encoding = parsedContentType.charset.toLowerCase();
  65. if (!BUFFER_ENCODING_LIST.includes(encoding))
  66. throw createError(
  67. HttpErrors.UnsupportedMediaType,
  68. 'Request encoding %v is not supported.',
  69. encoding,
  70. );
  71. }
  72. }
  73. // подготовка массива загружаемых байтов
  74. // и счетчика для отслеживания их объема
  75. const data = [];
  76. let receivedLength = 0;
  77. // обработчик проверяет объем загружаемых
  78. // данных и складывает их в массив
  79. const onData = chunk => {
  80. receivedLength += chunk.length;
  81. if (bodyBytesLimit && receivedLength > bodyBytesLimit) {
  82. req.removeAllListeners();
  83. const error = createError(
  84. HttpErrors.PayloadTooLarge,
  85. 'Request body limit is %v bytes, but %v bytes given.',
  86. bodyBytesLimit,
  87. receivedLength,
  88. );
  89. reject(error);
  90. return;
  91. }
  92. data.push(chunk);
  93. };
  94. // кода данные полностью загружены, нужно удалить
  95. // обработчики событий, и сравнить полученный объем
  96. // данных с заявленным в заголовке "content-length"
  97. const onEnd = () => {
  98. req.removeAllListeners();
  99. if (contentLength && contentLength !== receivedLength) {
  100. const error = createError(
  101. HttpErrors.BadRequest,
  102. 'Received bytes do not match the "content-length" header.',
  103. );
  104. reject(error);
  105. return;
  106. }
  107. // объединение массива байтов в буфер,
  108. // кодирование результата в строку,
  109. // и передача полученных данных
  110. // в ожидающий Promise
  111. const buffer = Buffer.concat(data);
  112. const body = Buffer.from(buffer, encoding).toString();
  113. resolve(body || undefined);
  114. };
  115. // при ошибке загрузки тела запроса,
  116. // удаляются обработчики событий,
  117. // и отклоняется ожидающий Promise
  118. // ошибкой с кодом 400
  119. const onError = error => {
  120. req.removeAllListeners();
  121. reject(HttpErrors(400, error));
  122. };
  123. // добавление обработчиков прослушивающих
  124. // события входящего запроса и возобновление
  125. // потока данных
  126. req.on('data', onData);
  127. req.on('end', onEnd);
  128. req.on('error', onError);
  129. req.resume();
  130. });
  131. }