Clients.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import path from 'path';
  2. import { fileURLToPath } from 'url';
  3. import Cache from '../libs/cache.js';
  4. const __filename = fileURLToPath(import.meta.url);
  5. const __dirname = path.dirname(__filename);
  6. const ClientsCacheFile = path.join(__dirname, '../data/clients.cache');
  7. const CLIENT_HEADER_FIELDS = [
  8. 'X-Device',
  9. 'X-Version',
  10. 'X-Data-Type',
  11. 'X-Market-Type',
  12. 'X-Group-Sequence',
  13. 'X-Platform',
  14. ];
  15. const CLIENT_KEY_FIELDS = [
  16. 'X-Data-Type',
  17. 'X-Market-Type',
  18. 'X-Group-Sequence',
  19. 'X-Platform',
  20. ];
  21. const CLIENT_FIELD_NAMES = {
  22. 'X-Data-Type': 'dataType',
  23. 'X-Device': 'device',
  24. 'X-Group-Sequence': 'groupSequence',
  25. 'X-Market-Type': 'marketType',
  26. 'X-Platform': 'platform',
  27. 'X-Version': 'version',
  28. };
  29. const CLIENTS = {
  30. Items: {},
  31. };
  32. let saveTimer = null;
  33. const normalizeHeaderValue = (value) => {
  34. if (Array.isArray(value)) {
  35. return value.join(',');
  36. }
  37. return value == null ? '' : String(value).trim();
  38. }
  39. const getRequestHeader = (req, field) => {
  40. return normalizeHeaderValue(req.get?.(field));
  41. }
  42. const getClientHeaders = (req) => {
  43. return CLIENT_HEADER_FIELDS.reduce((headers, field) => {
  44. headers[field] = getRequestHeader(req, field);
  45. return headers;
  46. }, {});
  47. }
  48. const formatClientFields = (headers) => {
  49. return CLIENT_HEADER_FIELDS.reduce((clientFields, field) => {
  50. clientFields[CLIENT_FIELD_NAMES[field]] = headers[field];
  51. return clientFields;
  52. }, {});
  53. }
  54. const getClientKey = (headers) => {
  55. return CLIENT_KEY_FIELDS
  56. .map(field => `${field}:${headers[field] ?? ''}`)
  57. .join('|');
  58. }
  59. const scheduleSaveClientsToCache = () => {
  60. if (saveTimer) {
  61. clearTimeout(saveTimer);
  62. }
  63. saveTimer = setTimeout(saveClientsToCache, 1000);
  64. }
  65. const recordRequest = (req) => {
  66. const route = req.path;
  67. const headers = getClientHeaders(req);
  68. const clientFields = formatClientFields(headers);
  69. const key = getClientKey(headers);
  70. const now = Date.now();
  71. const current = CLIENTS.Items[key] ?? {};
  72. CLIENTS.Items[key] = {
  73. key,
  74. ...clientFields,
  75. route,
  76. firstRequestTime: current.firstRequestTime ?? now,
  77. lastRequestTime: now,
  78. requestCount: (current.requestCount ?? 0) + 1,
  79. };
  80. scheduleSaveClientsToCache();
  81. return CLIENTS.Items[key];
  82. }
  83. const getClients = () => {
  84. return Object.values(CLIENTS.Items)
  85. .sort((a, b) => (b.lastRequestTime ?? 0) - (a.lastRequestTime ?? 0));
  86. }
  87. function saveClientsToCache() {
  88. if (saveTimer) {
  89. clearTimeout(saveTimer);
  90. saveTimer = null;
  91. }
  92. Cache.setData(ClientsCacheFile, CLIENTS);
  93. }
  94. function loadClientsFromCache() {
  95. const cachedClients = Cache.getData(ClientsCacheFile, true);
  96. if (!cachedClients?.Items) {
  97. return;
  98. }
  99. CLIENTS.Items = cachedClients.Items;
  100. }
  101. loadClientsFromCache();
  102. process.on('exit', saveClientsToCache);
  103. process.on('SIGINT', () => {
  104. process.exit(0);
  105. });
  106. process.on('SIGTERM', () => {
  107. process.exit(0);
  108. });
  109. process.on('SIGUSR2', () => {
  110. process.exit(0);
  111. });
  112. const Clients = {
  113. recordRequest,
  114. getClients,
  115. };
  116. export { recordRequest, getClients };
  117. export default Clients;