-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathhttpMiddleware.js
More file actions
70 lines (63 loc) · 2.57 KB
/
Copy pathhttpMiddleware.js
File metadata and controls
70 lines (63 loc) · 2.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
'use strict';
const JSON_BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);
const API_PATH_RE = /^\/api(?:\/|$)/;
function hasJsonContentType(req) {
if (typeof req.is === 'function') {
return Boolean(req.is(['application/json', 'application/*+json']));
}
const value = String(req.headers?.['content-type'] || '').trim();
return /^application\/(?:[a-z0-9!#$&^_.+-]+\+)?json(?:\s*;|$)/i.test(value);
}
function requireJsonApi(req, res, next) {
if (!JSON_BODY_METHODS.has(req.method) || !API_PATH_RE.test(req.path || '')) return next();
if (hasJsonContentType(req)) return next();
return res.status(415).json({
success: false,
message: 'Content-Type must be application/json.',
});
}
function classifyHttpError(error) {
const type = String(error?.type || '');
const status = Number(error?.status || error?.statusCode);
if (type === 'entity.too.large' || status === 413) {
return { status: 413, code: 'BODY_TOO_LARGE', message: 'Request body is too large.' };
}
if (type === 'encoding.unsupported' || type === 'charset.unsupported' || status === 415) {
return { status: 415, code: 'UNSUPPORTED_BODY_ENCODING', message: 'Request body encoding is not supported.' };
}
if (type === 'entity.parse.failed' || type === 'entity.verify.failed' || status === 400) {
return { status: 400, code: 'INVALID_JSON', message: 'Request body is not valid JSON.' };
}
return { status: 500, code: 'INTERNAL_ERROR', message: 'Internal error. Please try again later.' };
}
function safeErrorMessage(error) {
const value = String(error?.message || error || 'unknown error')
.replace(/[\r\n\t]+/g, ' ')
.replace(/[\u0000-\u001f\u007f]/g, '')
.trim();
return value.slice(0, 256) || 'unknown error';
}
function createHttpErrorHandler({ logger = console.error, metrics = null } = {}) {
return function httpErrorHandler(error, req, res, next) {
if (res.headersSent) return next(error);
const classified = classifyHttpError(error);
metrics?.increment?.(`http_error_${classified.code.toLowerCase()}`);
if (classified.status >= 500) {
logger('[Express unhandled error]', safeErrorMessage(error));
}
return res.status(classified.status).json({
success: false,
code: classified.code,
message: classified.message,
});
};
}
module.exports = {
API_PATH_RE,
JSON_BODY_METHODS,
classifyHttpError,
createHttpErrorHandler,
hasJsonContentType,
requireJsonApi,
safeErrorMessage,
};