/**
* Common header tagging for all requests.
* Add our own identifier, and persist any external transit identifiers.
+ * Sets requestId on ctx to a new uuid.
+ * If X-Request-Id or X-Correlation-Id exist on incoming headers, sets them
+ * on outgoing headers and sets on ctx.
* @param {http.ClientRequest} req
* @param {http.ServerResponse} res
* @param {object} ctx
/**
- *
+ * Sets ctx.clientAddress and ctx.clientProtocol.
* @param {http.ClientRequest} req
* @param {http.ServerResponse} res
* @param {object} ctx
/**
* Called before every request handler.
+ * Sets tracking identifiers and client information on ctx.
* @param {http.ClientRequest} req
* @param {http.ServerResponse} res
* @param {object} ctx
/**
* Read and parse request body data.
+ * Sets ctx.parsedBody, and optionally ctx.rawBody.
* @param {http.ClientRequest} req
* @param {http.ServerResponse} res
* @param {object} ctx
/**
* Set the best content type for the response.
+ * Sets ctx.responseType, and Content-Type header.
* @param {string[]} responseTypes default first
* @param {http.ClientRequest} req
* @param {http.ServerResponse} res
/**
- * Inserts an encoding
+ * Inserts an encoding into Content-Encoding header.
* @param {http.ServerResponse} res
* @param {string} encoding
*/
'use strict';
-
const ContentType = {
TextHTML: 'text/html',
TextPlain: 'text/plain',
[EncodingType.XGzip]: '.gz',
};
-const ErrorResponse = {
- BadRequest: {
- statusCode: 400,
- errorMessage: 'Bad Request',
- },
- Unauthorized: {
- statusCode: 401,
- errorMessage: 'Unauthorized',
- },
- Forbidden: {
- statusCode: 403,
- errorMessage: 'Forbidden',
- },
- ReallyForbidden: {
- statusCode: 403,
- errorMessage: 'F̦̩̫̼͔̫͓̃ͤ̈̆̀͑o̖̟͙̫̯̗̳̽ͦ̆́ͨr̩͉̰̗͉b̬̂͘į̟̬̓d͂͗҉̟͈̜͙ͅd͎̜̺̝͇͑̒̋̾ë̴̳̺͓̦̘́ͮ̈́ǹ͈̦̫̙',
- },
- NotFound: {
- statusCode: 404,
- errorMessage: 'Not Found',
- },
- MethodNotAllowed: {
- statusCode: 405,
- errorMessage: 'Method Not Allowed',
- },
- NotAcceptable: {
- statusCode: 406,
- errorMessage: 'Not Acceptable',
- },
- Gone: {
- statusCode: 410,
- errorMessage: 'Gone',
- },
- RequestEntityTooLarge: {
- statusCode: 413,
- errorMessage: 'Request Entity Too Large',
- },
- UnsupportedMediaType: {
- statusCode: 415,
- errorMessage: 'Unsupported Media Type',
- },
- InternalServerError: {
- statusCode: 500,
- errorMessage: 'Internal Server Error',
- },
+const HTTPStatusCode = {
+
+ Continue: 100,
+ SwitchingProtocols: 101,
+ Processing: 102,
+ EarlyHints: 103,
+
+ OK: 200,
+ Created: 201,
+ Accepted: 202,
+ NonAuthoritativeInformation: 203,
+ NoContent: 204,
+ ResetContent: 205,
+ PartialContent: 206,
+ MultiStatus: 207,
+ AlreadyReported: 208,
+ IMUsed: 226,
+
+ MultipleChoices: 300,
+ MovedPermanently: 301,
+ Found: 302,
+ SeeOther: 303,
+ NotModified: 304,
+ UseProxy: 305, // Deprecated, do not use
+ SwitchProxy: 306, // No longer used
+ TemporaryRedirect: 307,
+ PermanentRedirect: 308,
+
+ BadRequest: 400,
+ Unauthorized: 401,
+ PaymentRequired: 402,
+ Forbidden: 403,
+ NotFound: 404,
+ MethodNotAllowed: 405,
+ NotAcceptable: 406,
+ ProxyAuthenticationRequired: 407,
+ RequestTimeout: 408,
+ Conflict: 409,
+ Gone: 410,
+ LengthRequired: 411,
+ PreconditionFailed: 412,
+ ContentTooLarge: 413,
+ RequestEntityTooLarge: 413, // Alternate name
+ URITooLong: 414,
+ UnsupportedMediaType: 415,
+ RangeNotSatisfiable: 416,
+ ExpectationFailed: 417,
+ ImATeapot: 418,
+ MisdirectedRequest: 421,
+ UnprocessableContent: 422,
+ Locked: 423,
+ FailedDependency: 424,
+ TooEarly: 425,
+ UpgradeRequired: 426,
+ PreconditionRequired: 428,
+ TooManyRequests: 429,
+ RequestHeaderFieldsTooLarge: 431,
+ UnavailableForLegalReasons: 451,
+
+ InternalServerError: 500,
+ NotImplemented: 501,
+ BadGateway: 502,
+ ServiceUnavailable: 503,
+ GatewayTimeout: 504,
+ HTTPVersionNotSupported: 505,
+ VariantAlsoNegotiates: 506,
+ InsufficientStorage: 507,
+ LoopDetected: 508,
+ NotExtended: 510,
+ NetworkAuthenticationRequired: 511,
+
+};
+
+const HTTPStatusMessage = {
+
+ 100: 'Continue',
+ 101: 'Switching Protocols',
+ 102: 'Processing',
+ 103: 'Early Hints',
+
+ 200: 'OK',
+ 201: 'Created',
+ 202: 'Accepted',
+ 203: 'Non-Authoritative Information',
+ 204: 'No Content',
+ 205: 'Reset Content',
+ 206: 'Partial Content',
+ 207: 'Multi-Status',
+ 208: 'Already Reported',
+ 226: 'IM USed',
+
+ 300: 'Multiple Choices',
+ 301: 'Moved Permanently',
+ 302: 'Found',
+ 303: 'See Other',
+ 304: 'Not Modified',
+ 305: 'Use Proxy',
+ 306: 'Switch Proxy',
+ 307: 'Temporary Redirect',
+ 308: 'Permanent Redirect',
+
+ 400: 'Bad Request',
+ 401: 'Unauthorized',
+ 402: 'Payment Required',
+ 403: 'Forbidden',
+ '403.zalgo': 'F̦̩̫̼͔̫͓̃ͤ̈̆̀͑o̖̟͙̫̯̗̳̽ͦ̆́ͨr̩͉̰̗͉b̬̂͘į̟̬̓d͂͗҉̟͈̜͙ͅd͎̜̺̝͇͑̒̋̾ë̴̳̺͓̦̘́ͮ̈́ǹ͈̦̫̙',
+ 404: 'Not Found',
+ 405: 'Method Not Allowed',
+ 406: 'Not Acceptable',
+ 407: 'Proxy Authentication Required',
+ 408: 'Request Timeout',
+ 409: 'Conflict',
+ 410: 'Gone',
+ 411: 'Length Required',
+ 412: 'Precondition Failed',
+ 413: 'Content Too Large',
+ 414: 'URI Too Long',
+ 415: 'Unsupported Media Type',
+ 416: 'Range Not Satisfiable',
+ 417: 'Expectation Failed',
+ 418: 'I\'m A Teapot',
+ 421: 'Misdirected Request',
+ 422: 'Unprocessable Content',
+ 423: 'Locked',
+ 424: 'Failed Dependency',
+ 425: 'Too Early',
+ 426: 'Upgrade Required',
+ 428: 'Precondition Required',
+ 429: 'Too Many Requests',
+ 431: 'Request Header Fields Too Large',
+ 451: 'Unavailable For Legal Reasons',
+
+ 500: 'Internal Server Error',
+ 501: 'Not Implemented',
+ 502: 'Bad Gateway',
+ 503: 'Service Unavailable',
+ 504: 'Gateway Timeout',
+ 505: 'HTTP Version Not Supported',
+ 506: 'Variant Also Negotiates',
+ 507: 'Insufficient Storage',
+ 508: 'Loop Detected',
+ 510: 'Not Extended',
+ 511: 'Network Authentication Required',
+
};
-const ErrorResponseDefaultProxy = new Proxy(ErrorResponse, {
+function errorResponseGenerator () {
+ return Object.fromEntries(
+ Object.entries(HTTPStatusCode).map(([name, statusCode]) => {
+ const errorMessage = HTTPStatusMessage[statusCode]; // eslint-disable-line security/detect-object-injection
+ // istanbul ignore next
+ if (!errorMessage) {
+ throw new Error(`missing HTTPStatusMessage for ${statusCode} from HTTPStatusCode ${name}`);
+ }
+ return [name, { statusCode, errorMessage }];
+ }));
+}
+
+const ErrorResponse = errorResponseGenerator();
+
+const ErrorResponseProxy = new Proxy(ErrorResponse, {
get: (target, property) => {
- // eslint-disable-next-line security/detect-object-injection
- return (property in target) ? target[property] : {
- errorMessage: `undefined error response '${property}'`,
- };
+ if (property in target) {
+ return target[property]; // eslint-disable-line security/detect-object-injection
+ } else {
+ const statusCode = HTTPStatusCode.InternalServerError;
+ const errorMessage = HTTPStatusMessage[statusCode]; // eslint-disable-line security/detect-object-injection
+ const details = [`undefined error response '${property}'`];
+ return { statusCode, errorMessage, details };
+ }
},
});
ContentType,
EncodingType,
EncodingTypeSuffix,
- ErrorResponse: ErrorResponseDefaultProxy,
+ ErrorResponse: ErrorResponseProxy,
Header,
+ HTTPStatusCode,
+ HTTPStatusMessage,
};
const assert = require('assert');
const Enum = require('../../lib/enum');
+const { mergeEnum } = require('../../lib/common');
-
-describe('enum', function () {
+describe('Enum', function () {
describe('ErrorResponse', function () {
it('covers broken', function () {
const result = Enum.ErrorResponse.notPresent;
const expected = {
- errorMessage: 'undefined error response \'notPresent\'',
+ statusCode: 500,
+ errorMessage: 'Internal Server Error',
+ details: ['undefined error response \'notPresent\''],
};
assert.deepStrictEqual(result, expected);
});
it('covers success', function () {
- const { statusCode } = Enum.ErrorResponse.ReallyForbidden;
- const expected = 403;
- assert.deepStrictEqual(statusCode, expected);
+ const result = Enum.ErrorResponse.Forbidden;
+ const expected = {
+ statusCode: 403,
+ errorMessage: 'Forbidden',
+ };
+ assert.deepStrictEqual(result, expected);
+ });
+ it('can be merged', function () {
+ const NewResponse = { statusCode: 444, errorMessage: 'stuff' };
+ const localErr = mergeEnum(Enum.ErrorResponse, { NewResponse });
+ const newResult = localErr.NewResponse;
+ assert.deepStrictEqual(newResult, NewResponse);
+ const oldResult = localErr.NotFound;
+ assert.deepStrictEqual(oldResult, { statusCode: 404, errorMessage: 'Not Found' });
+ const missingResult = localErr.Wrong;
+ assert.deepStrictEqual(missingResult, {
+ statusCode: 500,
+ errorMessage: 'Internal Server Error',
+ details: ['undefined error response \'Wrong\''],
+ });
});
}); // ErrorResponse