mirror of https://github.com/msgbyte/tailchat
parent
dd358ac79b
commit
c3deea8925
@ -0,0 +1,3 @@
|
|||||||
|
Fork from https://github.com/moleculerjs/moleculer-web
|
||||||
|
|
||||||
|
Hash: f375dbb4f8bff8aa16e95024e5c65463b626fa45
|
@ -0,0 +1,312 @@
|
|||||||
|
/*
|
||||||
|
* moleculer
|
||||||
|
* Copyright (c) 2021 MoleculerJS (https://github.com/moleculerjs/moleculer)
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { pathToRegexp } from 'path-to-regexp';
|
||||||
|
import Busboy from '@fastify/busboy';
|
||||||
|
import kleur from 'kleur';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { PayloadTooLarge } from './errors';
|
||||||
|
import { Errors } from 'moleculer';
|
||||||
|
const { MoleculerClientError } = Errors;
|
||||||
|
import {
|
||||||
|
removeTrailingSlashes,
|
||||||
|
addSlashes,
|
||||||
|
decodeParam,
|
||||||
|
compose,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
export class Alias {
|
||||||
|
service;
|
||||||
|
route;
|
||||||
|
type = 'call';
|
||||||
|
method = '*';
|
||||||
|
path = null;
|
||||||
|
handler = null;
|
||||||
|
action = null;
|
||||||
|
fullPath;
|
||||||
|
keys;
|
||||||
|
re;
|
||||||
|
busboyConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor of Alias
|
||||||
|
*
|
||||||
|
* @param {Service} service
|
||||||
|
* @param {Object} route
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {any} action
|
||||||
|
*/
|
||||||
|
constructor(service, route, opts, action) {
|
||||||
|
this.service = service;
|
||||||
|
this.route = route;
|
||||||
|
|
||||||
|
if (_.isString(opts)) {
|
||||||
|
// Parse alias string
|
||||||
|
if (opts.indexOf(' ') !== -1) {
|
||||||
|
const p = opts.split(/\s+/);
|
||||||
|
this.method = p[0];
|
||||||
|
this.path = p[1];
|
||||||
|
} else {
|
||||||
|
this.path = opts;
|
||||||
|
}
|
||||||
|
} else if (_.isObject(opts)) {
|
||||||
|
Object.assign(this, _.cloneDeep(opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isString(action)) {
|
||||||
|
// Parse type from action name
|
||||||
|
if (action.indexOf(':') > 0) {
|
||||||
|
const p = action.split(':');
|
||||||
|
this.type = p[0];
|
||||||
|
this.action = p[1];
|
||||||
|
} else {
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
} else if (_.isFunction(action)) {
|
||||||
|
this.handler = action;
|
||||||
|
this.action = null;
|
||||||
|
} else if (Array.isArray(action)) {
|
||||||
|
const mws = _.compact(
|
||||||
|
action.map((mw) => {
|
||||||
|
if (_.isString(mw)) this.action = mw;
|
||||||
|
else if (_.isFunction(mw)) return mw;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.handler = compose.call(service, ...mws);
|
||||||
|
} else if (action != null) {
|
||||||
|
Object.assign(this, _.cloneDeep(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.type = this.type || 'call';
|
||||||
|
|
||||||
|
this.path = removeTrailingSlashes(this.path);
|
||||||
|
|
||||||
|
this.fullPath = this.fullPath || addSlashes(this.route.path) + this.path;
|
||||||
|
if (this.fullPath !== '/' && this.fullPath.endsWith('/')) {
|
||||||
|
this.fullPath = this.fullPath.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.keys = [];
|
||||||
|
this.re = pathToRegexp(
|
||||||
|
this.fullPath,
|
||||||
|
this.keys,
|
||||||
|
route.opts.pathToRegexpOptions || {}
|
||||||
|
); // Options: https://github.com/pillarjs/path-to-regexp#usage
|
||||||
|
|
||||||
|
if (this.type == 'multipart') {
|
||||||
|
// Handle file upload in multipart form
|
||||||
|
this.handler = this.multipartHandler.bind(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} url
|
||||||
|
*/
|
||||||
|
match(url) {
|
||||||
|
const m = this.re.exec(url);
|
||||||
|
if (!m) return false;
|
||||||
|
|
||||||
|
const params = {};
|
||||||
|
|
||||||
|
let key, param;
|
||||||
|
for (let i = 0; i < this.keys.length; i++) {
|
||||||
|
key = this.keys[i];
|
||||||
|
param = m[i + 1];
|
||||||
|
if (!param) continue;
|
||||||
|
|
||||||
|
params[key.name] = decodeParam(param);
|
||||||
|
|
||||||
|
if (key.repeat) params[key.name] = params[key.name].split(key.delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} method
|
||||||
|
*/
|
||||||
|
isMethod(method) {
|
||||||
|
return this.method === '*' || this.method === method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
printPath() {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
return `${this.method} ${this.fullPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
toString() {
|
||||||
|
return (
|
||||||
|
kleur.magenta(_.padStart(this.method, 6)) +
|
||||||
|
' ' +
|
||||||
|
kleur.cyan(this.fullPath) +
|
||||||
|
kleur.grey(' => ') +
|
||||||
|
(this.handler != null && this.type !== 'multipart'
|
||||||
|
? '<Function>'
|
||||||
|
: this.action)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} req
|
||||||
|
* @param {*} res
|
||||||
|
*/
|
||||||
|
multipartHandler(req, res) {
|
||||||
|
const ctx = req.$ctx;
|
||||||
|
ctx.meta.$multipart = {};
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
let numOfFiles = 0;
|
||||||
|
let hasField = false;
|
||||||
|
|
||||||
|
const busboyOptions = _.defaultsDeep(
|
||||||
|
{ headers: req.headers },
|
||||||
|
this.busboyConfig,
|
||||||
|
this.route.opts.busboyConfig
|
||||||
|
);
|
||||||
|
const busboy = new Busboy(busboyOptions);
|
||||||
|
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
|
||||||
|
file.on('limit', () => {
|
||||||
|
// This file reached the file size limit.
|
||||||
|
if (_.isFunction(busboyOptions.onFileSizeLimit)) {
|
||||||
|
busboyOptions.onFileSizeLimit.call(this.service, file, busboy);
|
||||||
|
}
|
||||||
|
file.destroy(
|
||||||
|
new PayloadTooLarge({ fieldname, filename, encoding, mimetype })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
numOfFiles++;
|
||||||
|
promises.push(
|
||||||
|
ctx
|
||||||
|
.call(
|
||||||
|
this.action,
|
||||||
|
file,
|
||||||
|
_.defaultsDeep({}, this.route.opts.callOptions, {
|
||||||
|
meta: {
|
||||||
|
fieldname: fieldname,
|
||||||
|
filename: filename,
|
||||||
|
encoding: encoding,
|
||||||
|
mimetype: mimetype,
|
||||||
|
$params: req.$params,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
file.resume(); // Drain file stream to continue processing form
|
||||||
|
busboy.emit('error', err);
|
||||||
|
return err;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
busboy.on('field', (field, value) => {
|
||||||
|
hasField = true;
|
||||||
|
ctx.meta.$multipart[field] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
busboy.on('finish', async () => {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (!busboyOptions.empty && numOfFiles == 0)
|
||||||
|
return this.service.sendError(
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
new MoleculerClientError('File missing in the request')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Call the action if no files but multipart fields
|
||||||
|
if (numOfFiles == 0 && hasField) {
|
||||||
|
promises.push(
|
||||||
|
ctx.call(
|
||||||
|
this.action,
|
||||||
|
{},
|
||||||
|
_.defaultsDeep({}, this.route.opts.callOptions, {
|
||||||
|
meta: {
|
||||||
|
$params: req.$params,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let data = await this.service.Promise.all(promises);
|
||||||
|
const fileLimit =
|
||||||
|
busboyOptions.limits && busboyOptions.limits.files != null
|
||||||
|
? busboyOptions.limits.files
|
||||||
|
: null;
|
||||||
|
if (numOfFiles == 1 && fileLimit == 1) {
|
||||||
|
// Remove the array wrapping
|
||||||
|
data = data[0];
|
||||||
|
}
|
||||||
|
if (this.route.onAfterCall)
|
||||||
|
data = await this.route.onAfterCall.call(
|
||||||
|
this,
|
||||||
|
ctx,
|
||||||
|
this.route,
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
this.service.sendResponse(req, res, data, {});
|
||||||
|
} catch (err) {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
this.service.sendError(req, res, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
busboy.on('error', (err) => {
|
||||||
|
req.unpipe(req.busboy);
|
||||||
|
req.resume();
|
||||||
|
this.service.sendError(req, res, err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add limit event handlers
|
||||||
|
if (_.isFunction(busboyOptions.onPartsLimit)) {
|
||||||
|
busboy.on('partsLimit', () =>
|
||||||
|
busboyOptions.onPartsLimit.call(
|
||||||
|
this.service,
|
||||||
|
busboy,
|
||||||
|
this,
|
||||||
|
this.service
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isFunction(busboyOptions.onFilesLimit)) {
|
||||||
|
busboy.on('filesLimit', () =>
|
||||||
|
busboyOptions.onFilesLimit.call(
|
||||||
|
this.service,
|
||||||
|
busboy,
|
||||||
|
this,
|
||||||
|
this.service
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.isFunction(busboyOptions.onFieldsLimit)) {
|
||||||
|
busboy.on('fieldsLimit', () =>
|
||||||
|
busboyOptions.onFieldsLimit.call(
|
||||||
|
this.service,
|
||||||
|
busboy,
|
||||||
|
this,
|
||||||
|
this.service
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.pipe(busboy);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* moleculer
|
||||||
|
* Copyright (c) 2021 MoleculerJS (https://github.com/moleculerjs/moleculer)
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Errors } from 'moleculer';
|
||||||
|
|
||||||
|
const { MoleculerError, MoleculerClientError } = Errors;
|
||||||
|
|
||||||
|
export { MoleculerError, MoleculerClientError };
|
||||||
|
|
||||||
|
const ERR_NO_TOKEN = 'NO_TOKEN';
|
||||||
|
const ERR_INVALID_TOKEN = 'INVALID_TOKEN';
|
||||||
|
const ERR_UNABLE_DECODE_PARAM = 'UNABLE_DECODE_PARAM';
|
||||||
|
const ERR_ORIGIN_NOT_FOUND = 'ORIGIN_NOT_FOUND';
|
||||||
|
const ERR_ORIGIN_NOT_ALLOWED = 'ORIGIN_NOT_ALLOWED';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalid request body
|
||||||
|
*
|
||||||
|
* @class InvalidRequestBodyError
|
||||||
|
* @extends {Error}
|
||||||
|
*/
|
||||||
|
class InvalidRequestBodyError extends MoleculerError {
|
||||||
|
/**
|
||||||
|
* Creates an instance of InvalidRequestBodyError.
|
||||||
|
*
|
||||||
|
* @param {any} body
|
||||||
|
* @param {any} error
|
||||||
|
*
|
||||||
|
* @memberOf InvalidRequestBodyError
|
||||||
|
*/
|
||||||
|
constructor(body, error) {
|
||||||
|
super('Invalid request body', 400, 'INVALID_REQUEST_BODY', {
|
||||||
|
body,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalid response type
|
||||||
|
*
|
||||||
|
* @class InvalidResponseTypeError
|
||||||
|
* @extends {Error}
|
||||||
|
*/
|
||||||
|
class InvalidResponseTypeError extends MoleculerError {
|
||||||
|
/**
|
||||||
|
* Creates an instance of InvalidResponseTypeError.
|
||||||
|
*
|
||||||
|
* @param {String} dataType
|
||||||
|
*
|
||||||
|
* @memberOf InvalidResponseTypeError
|
||||||
|
*/
|
||||||
|
constructor(dataType) {
|
||||||
|
super(`Invalid response type '${dataType}'`, 500, 'INVALID_RESPONSE_TYPE', {
|
||||||
|
dataType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unauthorized HTTP error
|
||||||
|
*
|
||||||
|
* @class UnAuthorizedError
|
||||||
|
* @extends {Error}
|
||||||
|
*/
|
||||||
|
class UnAuthorizedError extends MoleculerError {
|
||||||
|
/**
|
||||||
|
* Creates an instance of UnAuthorizedError.
|
||||||
|
*
|
||||||
|
* @param {String} type
|
||||||
|
* @param {any} data
|
||||||
|
*
|
||||||
|
* @memberOf UnAuthorizedError
|
||||||
|
*/
|
||||||
|
constructor(type, data) {
|
||||||
|
super('Unauthorized', 401, type || ERR_INVALID_TOKEN, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forbidden HTTP error
|
||||||
|
*
|
||||||
|
* @class ForbiddenError
|
||||||
|
* @extends {Error}
|
||||||
|
*/
|
||||||
|
class ForbiddenError extends MoleculerError {
|
||||||
|
/**
|
||||||
|
* Creates an instance of ForbiddenError.
|
||||||
|
*
|
||||||
|
* @param {String} type
|
||||||
|
* @param {any} data
|
||||||
|
*
|
||||||
|
* @memberOf ForbiddenError
|
||||||
|
*/
|
||||||
|
constructor(type, data?) {
|
||||||
|
super('Forbidden', 403, type, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad request HTTP error
|
||||||
|
*
|
||||||
|
* @class BadRequestError
|
||||||
|
* @extends {Error}
|
||||||
|
*/
|
||||||
|
class BadRequestError extends MoleculerError {
|
||||||
|
/**
|
||||||
|
* Creates an instance of BadRequestError.
|
||||||
|
*
|
||||||
|
* @param {String} type
|
||||||
|
* @param {any} data
|
||||||
|
*
|
||||||
|
* @memberOf BadRequestError
|
||||||
|
*/
|
||||||
|
constructor(type, data) {
|
||||||
|
super('Bad request', 400, type, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not found HTTP error
|
||||||
|
*
|
||||||
|
* @class NotFoundError
|
||||||
|
* @extends {Error}
|
||||||
|
*/
|
||||||
|
class NotFoundError extends MoleculerError {
|
||||||
|
/**
|
||||||
|
* Creates an instance of NotFoundError.
|
||||||
|
*
|
||||||
|
* @param {String} type
|
||||||
|
* @param {any} data
|
||||||
|
*
|
||||||
|
* @memberOf NotFoundError
|
||||||
|
*/
|
||||||
|
constructor(type?, data?) {
|
||||||
|
super('Not found', 404, type || 'NOT_FOUND', data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload is too large HTTP error
|
||||||
|
*
|
||||||
|
* @class PayloadTooLarge
|
||||||
|
* @extends {Error}
|
||||||
|
*/
|
||||||
|
class PayloadTooLarge extends MoleculerClientError {
|
||||||
|
/**
|
||||||
|
* Creates an instance of PayloadTooLarge.
|
||||||
|
*
|
||||||
|
* @param {String} type
|
||||||
|
* @param {any} data
|
||||||
|
*
|
||||||
|
* @memberOf PayloadTooLarge
|
||||||
|
*/
|
||||||
|
constructor(data) {
|
||||||
|
super('Payload too large', 413, 'PAYLOAD_TOO_LARGE', data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rate limit exceeded HTTP error
|
||||||
|
*
|
||||||
|
* @class RateLimitExceeded
|
||||||
|
* @extends {Error}
|
||||||
|
*/
|
||||||
|
class RateLimitExceeded extends MoleculerClientError {
|
||||||
|
/**
|
||||||
|
* Creates an instance of RateLimitExceeded.
|
||||||
|
*
|
||||||
|
* @param {String} type
|
||||||
|
* @param {any} data
|
||||||
|
*
|
||||||
|
* @memberOf RateLimitExceeded
|
||||||
|
*/
|
||||||
|
constructor(type?, data?) {
|
||||||
|
super('Rate limit exceeded', 429, type, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service unavailable HTTP error
|
||||||
|
*
|
||||||
|
* @class ForbiddenError
|
||||||
|
* @extends {Error}
|
||||||
|
*/
|
||||||
|
class ServiceUnavailableError extends MoleculerError {
|
||||||
|
/**
|
||||||
|
* Creates an instance of ForbiddenError.
|
||||||
|
*
|
||||||
|
* @param {String} type
|
||||||
|
* @param {any} data
|
||||||
|
*
|
||||||
|
* @memberOf ForbiddenError
|
||||||
|
*/
|
||||||
|
constructor(type?, data?) {
|
||||||
|
super('Service unavailable', 503, type, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
InvalidRequestBodyError,
|
||||||
|
InvalidResponseTypeError,
|
||||||
|
UnAuthorizedError,
|
||||||
|
ForbiddenError,
|
||||||
|
BadRequestError,
|
||||||
|
NotFoundError,
|
||||||
|
PayloadTooLarge,
|
||||||
|
RateLimitExceeded,
|
||||||
|
ServiceUnavailableError,
|
||||||
|
ERR_NO_TOKEN,
|
||||||
|
ERR_INVALID_TOKEN,
|
||||||
|
ERR_UNABLE_DECODE_PARAM,
|
||||||
|
ERR_ORIGIN_NOT_FOUND,
|
||||||
|
ERR_ORIGIN_NOT_ALLOWED,
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Memory store for Rate limiter
|
||||||
|
*
|
||||||
|
* Inspired by https://github.com/dotcypress/micro-ratelimit/
|
||||||
|
*
|
||||||
|
* @class MemoryStore
|
||||||
|
*/
|
||||||
|
export class MemoryStore {
|
||||||
|
hits;
|
||||||
|
resetTime;
|
||||||
|
timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of MemoryStore.
|
||||||
|
*
|
||||||
|
* @param {Number} clearPeriod
|
||||||
|
* @memberof MemoryStore
|
||||||
|
*/
|
||||||
|
constructor(clearPeriod) {
|
||||||
|
this.hits = new Map();
|
||||||
|
this.resetTime = Date.now() + clearPeriod;
|
||||||
|
|
||||||
|
this.timer = setInterval(() => {
|
||||||
|
this.resetTime = Date.now() + clearPeriod;
|
||||||
|
this.reset();
|
||||||
|
}, clearPeriod);
|
||||||
|
|
||||||
|
this.timer.unref();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the counter by key
|
||||||
|
*
|
||||||
|
* @param {String} key
|
||||||
|
* @returns {Number}
|
||||||
|
* @memberof MemoryStore
|
||||||
|
*/
|
||||||
|
inc(key) {
|
||||||
|
let counter = this.hits.get(key) || 0;
|
||||||
|
counter++;
|
||||||
|
this.hits.set(key, counter);
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all counters
|
||||||
|
*
|
||||||
|
* @memberof MemoryStore
|
||||||
|
*/
|
||||||
|
reset() {
|
||||||
|
this.hits.clear();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* moleculer
|
||||||
|
* Copyright (c) 2021 MoleculerJS (https://github.com/moleculerjs/moleculer)
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import fresh from 'fresh';
|
||||||
|
import etag from 'etag';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BadRequestError,
|
||||||
|
ERR_UNABLE_DECODE_PARAM,
|
||||||
|
MoleculerError,
|
||||||
|
} from './errors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode URI encoded param
|
||||||
|
* @param {String} param
|
||||||
|
*/
|
||||||
|
function decodeParam(param) {
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(param);
|
||||||
|
} catch (_) {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
throw new BadRequestError(ERR_UNABLE_DECODE_PARAM, { param });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove slashes "/" from the left & right sides and remove double "//" slashes
|
||||||
|
function removeTrailingSlashes(s) {
|
||||||
|
if (s.startsWith('/')) s = s.slice(1);
|
||||||
|
if (s.endsWith('/')) s = s.slice(0, -1);
|
||||||
|
return s; //.replace(/\/\//g, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add slashes "/" to the left & right sides
|
||||||
|
function addSlashes(s) {
|
||||||
|
return (s.startsWith('/') ? '' : '/') + s + (s.endsWith('/') ? '' : '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize URL path (remove multiple slashes //)
|
||||||
|
function normalizePath(s) {
|
||||||
|
return s.replace(/\/{2,}/g, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose middlewares
|
||||||
|
*
|
||||||
|
* @param {...Function} mws
|
||||||
|
*/
|
||||||
|
function compose(...mws) {
|
||||||
|
const self = this as any;
|
||||||
|
return (req, res, done) => {
|
||||||
|
const next = (i, err?) => {
|
||||||
|
if (i >= mws.length) {
|
||||||
|
if (_.isFunction(done)) return done.call(self, err);
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
// Call only error middlewares (err, req, res, next)
|
||||||
|
if (mws[i].length == 4)
|
||||||
|
mws[i].call(self, err, req, res, (err) => next(i + 1, err));
|
||||||
|
else next(i + 1, err);
|
||||||
|
} else {
|
||||||
|
if (mws[i].length < 4)
|
||||||
|
mws[i].call(self, req, res, (err) => next(i + 1, err));
|
||||||
|
else next(i + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return next(0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose middlewares and return Promise
|
||||||
|
* @param {...Function} mws
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
function composeThen(req, res, ...mws) {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
compose.call(this, ...mws)(req, res, (err) => {
|
||||||
|
if (err) {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (err instanceof MoleculerError) return reject(err);
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (err instanceof Error)
|
||||||
|
return reject(
|
||||||
|
new MoleculerError(
|
||||||
|
err.message,
|
||||||
|
(err as any).code || (err as any).status,
|
||||||
|
(err as any).type
|
||||||
|
)
|
||||||
|
); // TODO err.stack
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
return reject(new MoleculerError(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate ETag from content.
|
||||||
|
*
|
||||||
|
* @param {any} body
|
||||||
|
* @param {Boolean|String|Function?} opt
|
||||||
|
*
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
function generateETag(body, opt) {
|
||||||
|
if (_.isFunction(opt)) return opt.call(this, body);
|
||||||
|
|
||||||
|
const buf = !Buffer.isBuffer(body) ? Buffer.from(body) : body;
|
||||||
|
|
||||||
|
return etag(buf, opt === true || opt === 'weak' ? { weak: true } : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the data freshness.
|
||||||
|
*
|
||||||
|
* @param {*} req
|
||||||
|
* @param {*} res
|
||||||
|
*
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
function isFresh(req, res) {
|
||||||
|
if (
|
||||||
|
(res.statusCode >= 200 && res.statusCode < 300) ||
|
||||||
|
304 === res.statusCode
|
||||||
|
) {
|
||||||
|
return fresh(req.headers, {
|
||||||
|
etag: res.getHeader('ETag'),
|
||||||
|
'last-modified': res.getHeader('Last-Modified'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
removeTrailingSlashes,
|
||||||
|
addSlashes,
|
||||||
|
normalizePath,
|
||||||
|
decodeParam,
|
||||||
|
compose,
|
||||||
|
composeThen,
|
||||||
|
generateETag,
|
||||||
|
isFresh,
|
||||||
|
};
|
Loading…
Reference in New Issue