http-micro
Version:
Micro-framework on top of node's http module
179 lines (178 loc) • 6.77 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const rawBody = require("raw-body");
const qs = require("querystring");
const typeis = require("type-is");
const contentType = require("content-type");
const mimeTypes = require("mime-types");
const error_utils_1 = require("./error-utils");
const defaultLimit = 1024 * 1024 / 2; // 512Kb
function rawBodyParserFactory() {
return function rawParser(req, callback, opts) {
if (handleRequestBodyAbsence(req, callback))
return;
let limit, contentLength, encoding;
if (opts) {
limit = opts.limit;
contentLength = opts.length;
encoding = opts.encoding;
}
limit = limit || defaultLimit;
contentLength = contentLength || Number(req.headers["content-length"]);
if (encoding === undefined) {
let contentTypeHeader = req.headers["content-type"];
// Ensure that further attempts are skipped, as contentType
// will throw on invalid header. Since rawParser could
// potentially be passed on it's own to get a buffer back.
if (contentTypeHeader) {
try {
let ct = contentType.parse(contentTypeHeader);
encoding = ct.parameters["charset"];
if (!encoding) {
// No valid encoding was found, but content-type
// header is valid. So, pick up the default
// encoding for the mime.
let mimeCharset = mimeTypes.charset(ct.type);
encoding = mimeCharset ? mimeCharset : undefined;
}
}
catch (err) {
throw error_utils_1.intoHttpError(err, 400);
}
}
}
if (encoding === undefined && opts)
encoding = opts.defaultEncoding;
rawBody(req, {
limit: limit,
length: contentLength,
encoding,
}, callback);
};
}
exports.rawBodyParserFactory = rawBodyParserFactory;
function jsonBodyParserFactory(opts, baseParser) {
let rawParser = baseParser || rawBodyParserFactory();
let reviver = opts ? opts.reviver : undefined;
return function jsonParser(req, callback, baseParserOpts) {
rawParser(req, function (err, body) {
if (err) {
return callback(err);
}
let res;
try {
if (typeof body !== "string")
throw new Error("buffered raw body is not a string to parse as json");
res = JSON.parse(body, reviver);
}
catch (e) {
return callback(e);
}
callback(null, res);
}, baseParserOpts);
};
}
exports.jsonBodyParserFactory = jsonBodyParserFactory;
function formBodyParserFactory(opts, baseParser) {
let rawParser = baseParser || rawBodyParserFactory();
let qsParse, sep, eq, options;
if (opts) {
qsParse = opts.parser;
sep = opts.sep;
eq = opts.eq;
options = opts.options;
}
qsParse = qsParse || qs.parse;
return function formBodyParser(req, callback, baseParserOpts) {
let baseOpts = baseParserOpts;
// TODO: Not very happy with the implementation here, for passing override opts to
// the raw parser. It works, however, could be better designed.
if (baseOpts == null || baseOpts.defaultEncoding === undefined) {
// It's important to pass in the default encoding as true (translates to 'utf-8'),
// since, mime-types don't resolve the default charset for
// `application/x-www-form-urlencoded`
// TODO: Default charset Latin-1?
baseOpts = Object.assign({}, baseOpts, { defaultEncoding: true });
}
rawParser(req, function (err, body) {
if (err) {
return callback(err);
}
let res;
try {
if (typeof body !== "string")
throw new Error("buffered raw body is not a string to parse as url encoded form");
// TODO: How to handle charset?
res = qsParse(body, sep, eq, options);
}
catch (e) {
return callback(e);
}
callback(null, res);
}, baseOpts);
};
}
exports.formBodyParserFactory = formBodyParserFactory;
function anyBodyParserFactory(opts, baseParser) {
let rawParser = baseParser || rawBodyParserFactory();
let jsonParser = jsonBodyParserFactory(opts, rawParser);
let formParser = formBodyParserFactory(opts, rawParser);
let types = ["json", "urlencoded"];
return function anyBodyParser(req, callback, baseParserOpts) {
if (handleRequestBodyAbsence(req, callback))
return;
let t = typeis(req, types);
switch (t) {
case types[0]: {
jsonParser(req, callback, baseParserOpts);
break;
}
case types[1]: {
formParser(req, callback, baseParserOpts);
break;
}
default: {
rawParser(req, callback, baseParserOpts);
}
}
};
}
exports.anyBodyParserFactory = anyBodyParserFactory;
function createAsyncParser(parser) {
return function parse(req, opts) {
return new Promise((resolve, reject) => {
parser(req, (err, body) => {
if (err) {
reject(error_utils_1.intoHttpError(err, 400));
}
else {
resolve(body);
}
}, opts);
});
};
}
exports.createAsyncParser = createAsyncParser;
function parseBody(req, opts, parser = anyBodyParserFactory()) {
let finalParser = createAsyncParser(parser);
return finalParser(req, opts);
}
exports.parseBody = parseBody;
function handleRequestBodyAbsence(req, callback) {
if (!hasBody(req)) {
callback(null, null);
return true;
}
return false;
}
exports.handleRequestBodyAbsence = handleRequestBodyAbsence;
function hasBody(req) {
let headers = req.headers;
if (headers["transfer-encoding"] !== undefined)
return true;
let contentLength = headers["content-length"];
if (contentLength && Number(contentLength) > 0)
return true;
return false;
}
exports.hasBody = hasBody;