| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- import {Socket} from 'net';
- import {TLSSocket} from 'tls';
- import {IncomingMessage} from 'http';
- import queryString from 'querystring';
- import {Errorf} from '@e22m4u/js-format';
- import {isReadableStream} from './is-readable-stream.js';
- import {createCookieString} from './create-cookie-string.js';
- import {BUFFER_ENCODING_LIST} from './fetch-request-body.js';
- /**
- * @typedef {{
- * host?: string;
- * method?: string;
- * secure?: boolean;
- * path?: string;
- * query?: object;
- * cookie?: object;
- * headers?: object;
- * body?: string;
- * stream?: import('stream').Readable;
- * encoding?: import('buffer').BufferEncoding;
- * }} RequestPatch
- */
- /**
- * Create request mock.
- *
- * @param {RequestPatch} patch
- * @returns {import('http').IncomingMessage}
- */
- export function createRequestMock(patch) {
- if ((patch != null && typeof patch !== 'object') || Array.isArray(patch)) {
- throw new Errorf(
- 'The first parameter of "createRequestMock" ' +
- 'should be an Object, but %v given.',
- patch,
- );
- }
- patch = patch || {};
- if (patch.host != null && typeof patch.host !== 'string')
- throw new Errorf(
- 'The parameter "host" of "createRequestMock" ' +
- 'should be a String, but %v given.',
- patch.host,
- );
- if (patch.method != null && typeof patch.method !== 'string')
- throw new Errorf(
- 'The parameter "method" of "createRequestMock" ' +
- 'should be a String, but %v given.',
- patch.method,
- );
- if (patch.secure != null && typeof patch.secure !== 'boolean')
- throw new Errorf(
- 'The parameter "secure" of "createRequestMock" ' +
- 'should be a Boolean, but %v given.',
- patch.secure,
- );
- if (patch.path != null && typeof patch.path !== 'string')
- throw new Errorf(
- 'The parameter "path" of "createRequestMock" ' +
- 'should be a String, but %v given.',
- patch.path,
- );
- if (
- (patch.query != null &&
- typeof patch.query !== 'object' &&
- typeof patch.query !== 'string') ||
- Array.isArray(patch.query)
- ) {
- throw new Errorf(
- 'The parameter "query" of "createRequestMock" ' +
- 'should be a String or Object, but %v given.',
- patch.query,
- );
- }
- if (
- (patch.cookie != null &&
- typeof patch.cookie !== 'string' &&
- typeof patch.cookie !== 'object') ||
- Array.isArray(patch.cookie)
- ) {
- throw new Errorf(
- 'The parameter "cookie" of "createRequestMock" ' +
- 'should be a String or Object, but %v given.',
- patch.cookie,
- );
- }
- if (
- (patch.headers != null && typeof patch.headers !== 'object') ||
- Array.isArray(patch.headers)
- ) {
- throw new Errorf(
- 'The parameter "headers" of "createRequestMock" ' +
- 'should be an Object, but %v given.',
- patch.headers,
- );
- }
- if (patch.stream != null && !isReadableStream(patch.stream))
- throw new Errorf(
- 'The parameter "stream" of "createRequestMock" ' +
- 'should be a Stream, but %v given.',
- patch.stream,
- );
- if (patch.encoding != null) {
- if (typeof patch.encoding !== 'string')
- throw new Errorf(
- 'The parameter "encoding" of "createRequestMock" ' +
- 'should be a String, but %v given.',
- patch.encoding,
- );
- if (!BUFFER_ENCODING_LIST.includes(patch.encoding))
- throw new Errorf('Buffer encoding %v is not supported.', patch.encoding);
- }
- // если передан поток, выполняется
- // проверка на несовместимые опции
- if (patch.stream) {
- if (patch.secure != null)
- throw new Errorf(
- 'The "createRequestMock" does not allow specifying the ' +
- '"stream" and "secure" options simultaneously.',
- );
- if (patch.body != null)
- throw new Errorf(
- 'The "createRequestMock" does not allow specifying the ' +
- '"stream" and "body" options simultaneously.',
- );
- if (patch.encoding != null)
- throw new Errorf(
- 'The "createRequestMock" does not allow specifying the ' +
- '"stream" and "encoding" options simultaneously.',
- );
- }
- // если передан поток, он будет использован
- // в качестве объекта запроса, в противном
- // случае создается новый
- const req =
- patch.stream ||
- createRequestStream(patch.secure, patch.body, patch.encoding);
- req.url = createRequestUrl(patch.path || '/', patch.query);
- req.headers = createRequestHeaders(
- patch.host,
- patch.secure,
- patch.body,
- patch.cookie,
- patch.encoding,
- patch.headers,
- );
- req.method = (patch.method || 'get').toUpperCase();
- return req;
- }
- /**
- * Create request stream.
- *
- * @param {boolean|null|undefined} secure
- * @param {*} body
- * @param {import('buffer').BufferEncoding|null|undefined} encoding
- * @returns {import('http').IncomingMessage}
- */
- function createRequestStream(secure, body, encoding) {
- if (encoding != null && typeof encoding !== 'string')
- throw new Errorf(
- 'The parameter "encoding" of "createRequestStream" ' +
- 'should be a String, but %v given.',
- encoding,
- );
- encoding = encoding || 'utf-8';
- // для безопасного подключения
- // использует обертка TLSSocket
- let socket = new Socket();
- if (secure) socket = new TLSSocket(socket);
- const req = new IncomingMessage(socket);
- // тело запроса должно являться
- // строкой или бинарными данными
- if (body != null) {
- if (typeof body === 'string') {
- req.push(body, encoding);
- } else if (Buffer.isBuffer(body)) {
- req.push(body);
- } else {
- req.push(JSON.stringify(body));
- }
- }
- // передача "null" определяет
- // конец данных
- req.push(null);
- return req;
- }
- /**
- * Create request url.
- *
- * @param {string} path
- * @param {string|object|null|undefined} query
- * @returns {string}
- */
- function createRequestUrl(path, query) {
- if (typeof path !== 'string')
- throw new Errorf(
- 'The parameter "path" of "createRequestUrl" ' +
- 'should be a String, but %v given.',
- path,
- );
- if (
- (query != null && typeof query !== 'string' && typeof query !== 'object') ||
- Array.isArray(query)
- ) {
- throw new Errorf(
- 'The parameter "query" of "createRequestUrl" ' +
- 'should be a String or Object, but %v given.',
- query,
- );
- }
- let url = ('/' + path).replace('//', '/');
- if (typeof query === 'object') {
- const qs = queryString.stringify(query);
- if (qs) url += `?${qs}`;
- } else if (typeof query === 'string') {
- url += `?${query.replace(/^\?/, '')}`;
- }
- return url;
- }
- /**
- * Create request headers.
- *
- * @param {string|null|undefined} host
- * @param {boolean|null|undefined} secure
- * @param {*} body
- * @param {string|object|null|undefined} cookie
- * @param {import('buffer').BufferEncoding|null|undefined} encoding
- * @param {object|null|undefined} headers
- * @returns {object}
- */
- function createRequestHeaders(host, secure, body, cookie, encoding, headers) {
- if (host != null && typeof host !== 'string')
- throw new Errorf(
- 'The parameter "host" of "createRequestHeaders" ' +
- 'a non-empty String, but %v given.',
- host,
- );
- host = host || 'localhost';
- if (secure != null && typeof secure !== 'boolean')
- throw new Errorf(
- 'The parameter "secure" of "createRequestHeaders" ' +
- 'should be a String, but %v given.',
- secure,
- );
- secure = Boolean(secure);
- if (
- (cookie != null &&
- typeof cookie !== 'object' &&
- typeof cookie !== 'string') ||
- Array.isArray(cookie)
- ) {
- throw new Errorf(
- 'The parameter "cookie" of "createRequestHeaders" ' +
- 'should be a String or Object, but %v given.',
- cookie,
- );
- }
- if (
- (headers != null && typeof headers !== 'object') ||
- Array.isArray(headers)
- ) {
- throw new Errorf(
- 'The parameter "headers" of "createRequestHeaders" ' +
- 'should be an Object, but %v given.',
- headers,
- );
- }
- headers = headers || {};
- if (encoding != null && typeof encoding !== 'string')
- throw new Errorf(
- 'The parameter "encoding" of "createRequestHeaders" ' +
- 'should be a String, but %v given.',
- encoding,
- );
- encoding = encoding || 'utf-8';
- const obj = {...headers};
- obj['host'] = host;
- if (secure) obj['x-forwarded-proto'] = 'https';
- // формирование заголовка Cookie
- // из строки или объекта
- if (cookie != null) {
- if (typeof cookie === 'string') {
- obj['cookie'] = obj['cookie'] ? obj['cookie'] : '';
- obj['cookie'] += cookie;
- } else if (typeof cookie === 'object') {
- obj['cookie'] = obj['cookie'] ? obj['cookie'] : '';
- obj['cookie'] += createCookieString(cookie);
- }
- }
- // установка заголовка "content-type"
- // в зависимости от тела запроса
- if (obj['content-type'] == null) {
- if (typeof body === 'string') {
- obj['content-type'] = 'text/plain';
- } else if (Buffer.isBuffer(body)) {
- obj['content-type'] = 'application/octet-stream';
- } else if (
- typeof body === 'object' ||
- typeof body === 'boolean' ||
- typeof body === 'number'
- ) {
- obj['content-type'] = 'application/json';
- }
- }
- // подсчет количества байт тела
- // для заголовка "content-length"
- if (body != null && obj['content-length'] == null) {
- if (typeof body === 'string') {
- const length = Buffer.byteLength(body, encoding);
- obj['content-length'] = String(length);
- } else if (Buffer.isBuffer(body)) {
- const length = Buffer.byteLength(body);
- obj['content-length'] = String(length);
- } else if (
- typeof body === 'object' ||
- typeof body === 'boolean' ||
- typeof body === 'number'
- ) {
- const json = JSON.stringify(body);
- const length = Buffer.byteLength(json, encoding);
- obj['content-length'] = String(length);
- }
- }
- return obj;
- }
|