|
1 | | -const process = require("process"); |
2 | | -const url = require("url"); |
3 | | -const path = require("path"); |
4 | | -const chance = require("chance").Chance(); |
| 1 | +import process from "process"; |
| 2 | +import logger from "./src/logger.js"; |
| 3 | +import { startServer } from "./src/app.js"; |
5 | 4 |
|
6 | 5 | const config = JSON.parse(process.env.APP_CONFIG); |
7 | | - |
8 | 6 | const customCodeDir = process.env.CUSTOM_CODE_DIR; |
9 | 7 |
|
10 | | -const logDir = process.env.LOG_DIRECTORY ? process.env.LOG_DIRECTORY : "."; |
11 | | - |
12 | | -const express = require("express"); |
13 | | -const morgan = require("morgan"); |
14 | | -const log4js = require("log4js"); |
15 | | -const http = require("http"); |
16 | | -const cronmatch = require("cronmatch"); |
17 | | -var bodyParser = require("body-parser"); |
18 | | -const sleep = require("sleep"); |
19 | | -const rp = require("request-promise"); |
20 | | - |
21 | | -log4js.configure({ |
22 | | - appenders: { |
23 | | - FILE: { |
24 | | - type: "file", |
25 | | - filename: `${logDir}/node.log`, |
26 | | - layout: { |
27 | | - type: "pattern", |
28 | | - pattern: |
29 | | - "%d{yyyy-MM-dd hh:mm:ss,SSS} [%z] [%X{AD.requestGUID}] %p %c - %m", |
30 | | - }, |
31 | | - }, |
32 | | - CONSOLE: { |
33 | | - type: "stdout", |
34 | | - layout: { |
35 | | - type: "pattern", |
36 | | - pattern: |
37 | | - "%d{yyyy-MM-dd hh:mm:ss,SSS} [%z] [%X{AD.requestGUID}] %p %c - %m", |
38 | | - }, |
39 | | - }, |
40 | | - }, |
41 | | - categories: { default: { appenders: ["CONSOLE", "FILE"], level: "info" } }, |
42 | | -}); |
43 | | - |
44 | | -var logger = log4js.getLogger(); |
45 | | -logger.level = "debug"; |
46 | | - |
47 | | -const app = express(); |
48 | | - |
49 | | -app.use(bodyParser.json()); // for parsing application/json |
50 | | -app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded |
51 | | -app.use( |
52 | | - morgan( |
53 | | - ':remote-addr - ":method :url HTTP/:http-version" :status :res[content-length] ":user-agent" - :response-time ms', |
54 | | - { |
55 | | - stream: { |
56 | | - write: function (str) { |
57 | | - logger.debug(str.trim("\n")); |
58 | | - }, |
59 | | - }, |
60 | | - }, |
61 | | - ), |
62 | | -); |
63 | | - |
64 | | -var port = parseInt(process.argv[2]); |
65 | | - |
66 | | -const endpoints = config.endpoints.http; |
67 | | - |
68 | | -const useRp = |
69 | | - config.hasOwnProperty("options") && |
70 | | - config.options.hasOwnProperty("httpLibrary") && |
71 | | - config.options.httpLibrary == "request-promise"; |
72 | | - |
73 | | -Object.keys(endpoints).forEach(function (key) { |
74 | | - if (!key.startsWith("/")) { |
75 | | - endpoints["/" + key] = endpoints[key]; |
76 | | - delete endpoints[key]; |
77 | | - } |
78 | | -}); |
79 | | - |
| 8 | +let port = parseInt(process.argv[2]); |
80 | 9 | if (isNaN(port)) { |
81 | 10 | port = 8080; |
82 | 11 | } |
83 | 12 |
|
84 | | -function buildResponse(timeout) { |
85 | | - const start = process.hrtime(); |
86 | | - var elapsed = process.hrtime(start); |
87 | | - var response = ""; |
88 | | - while (elapsed[0] * 1000000000 + elapsed[1] < timeout * 1000000) { |
89 | | - response += " "; |
90 | | - elapsed = process.hrtime(start); |
91 | | - } |
92 | | - return response.length + " slow response"; |
93 | | -} |
94 | | - |
95 | | -function loadFromCache(timeout, txn) { |
96 | | - const start = process.hrtime(); |
97 | | - var elapsed = process.hrtime(start); |
98 | | - var response = ""; |
99 | | - while (elapsed[0] * 1000000000 + elapsed[1] < timeout * 1000000) { |
100 | | - var exit = txn.startExitCall({ |
101 | | - exitType: "EXIT_CACHE", |
102 | | - label: "Redis Cache", |
103 | | - backendName: "Redis", |
104 | | - identifyingProperties: { |
105 | | - "SERVER POOL": "redis:6380", |
106 | | - }, |
107 | | - }); |
108 | | - |
109 | | - elapsed = process.hrtime(start); |
110 | | - response += " "; |
111 | | - |
112 | | - txn.endExitCall(exit); |
113 | | - } |
114 | | - return response.length + " send data to cache"; |
115 | | -} |
116 | | - |
117 | | -async function processData(resolve, reject, req, data) { |
118 | | - if (!data.id) { |
119 | | - reject("Data not processed: No id provided"); |
120 | | - } |
121 | | - |
122 | | - if (data.chance) { |
123 | | - var fna = data.chance.split(","); |
124 | | - var fn = fna.shift(); |
125 | | - var attributes = fna.reduce((c, a) => { |
126 | | - var [k, v] = a.split(":"); |
127 | | - c[k] = isNaN(parseInt(v)) ? v : parseInt(v); |
128 | | - return c; |
129 | | - }, {}); |
130 | | - data.value = chance[fn](attributes); |
131 | | - } |
132 | | - |
133 | | - if (!data.value) { |
134 | | - reject("Data not processed: No value provided"); |
135 | | - } |
136 | | - |
137 | | - var value = data.value; |
138 | | - var id = data.id; |
139 | | - |
140 | | - if (Array.isArray(value)) { |
141 | | - value = value[Math.floor(Math.random() * value.length)]; |
142 | | - } |
143 | | - reject("No data added: Transaction not found."); |
144 | | -} |
145 | | - |
146 | | -function logMessage(level, message) { |
147 | | - if (["trace", "debug", "info", "warn", "error", "fatal"].includes(level)) { |
148 | | - logger[level](message); |
149 | | - } else { |
150 | | - logger.info(message); |
151 | | - } |
152 | | - return "Logged (" + level + "): " + message; |
153 | | -} |
154 | | - |
155 | | -function executeCustomScript(script, req, resolve, reject) { |
156 | | - var r = require(path.join(customCodeDir, script))({ |
157 | | - logger: logger, |
158 | | - req: req, |
159 | | - cronmatch: cronmatch, |
160 | | - sleep: sleep.msleep, |
161 | | - chance: chance, |
162 | | - }); |
163 | | - if (r === false) { |
164 | | - reject(`Script ${script} was not executed successfully`); |
165 | | - } else if ( |
166 | | - typeof r === "object" && |
167 | | - r.hasOwnProperty("code") && |
168 | | - r.hasOwnProperty("code") |
169 | | - ) { |
170 | | - reject({ code: r.code, message: r.message }); |
171 | | - } else if (typeof r === "string") { |
172 | | - resolve(r); |
173 | | - } else { |
174 | | - resolve(`Script ${script} was executed successfully`); |
175 | | - } |
176 | | -} |
177 | | - |
178 | | -function callRemoteService( |
179 | | - call, |
180 | | - useRp, |
181 | | - catchExceptions, |
182 | | - remoteTimeout, |
183 | | - req, |
184 | | - resolve, |
185 | | - reject, |
186 | | -) { |
187 | | - if (useRp) { |
188 | | - rp.get({ |
189 | | - uri: url.parse(call), |
190 | | - json: true, |
191 | | - timeout: remoteTimeout, |
192 | | - }) |
193 | | - .then(function (body) { |
194 | | - resolve(body); |
195 | | - }) |
196 | | - .catch(function (err) { |
197 | | - if (catchExceptions) { |
198 | | - resolve(err); |
199 | | - } else { |
200 | | - reject(err); |
201 | | - } |
202 | | - }); |
203 | | - } else { |
204 | | - var headers = { |
205 | | - "Content-Type": "application/json", |
206 | | - }; |
207 | | - // removed logic to decide what happens w/ w/o agent |
208 | | - headers = req.headers; |
209 | | - var opts = Object.assign(url.parse(call), { |
210 | | - headers: headers, |
211 | | - }); |
212 | | - const r = http |
213 | | - .get(opts, function (res, req) { |
214 | | - const body = []; |
215 | | - res.on("data", (chunk) => body.push(chunk)); |
216 | | - res.on("end", () => resolve(body.join(""))); |
217 | | - }) |
218 | | - .on("error", function (err) { |
219 | | - if (catchExceptions) { |
220 | | - resolve(err); |
221 | | - } else { |
222 | | - reject(err); |
223 | | - } |
224 | | - }); |
225 | | - r.setTimeout(remoteTimeout, function () { |
226 | | - reject({ code: 500, message: "Read timed out" }); |
227 | | - }); |
228 | | - } |
229 | | -} |
230 | | - |
231 | | -function processCall(call, req) { |
232 | | - return new Promise(function (resolve, reject) { |
233 | | - console.log(call); |
234 | | - var remoteTimeout = 1073741824; |
235 | | - |
236 | | - var catchExceptions = true; |
237 | | - |
238 | | - // If call is an array, select one element as call |
239 | | - if (Array.isArray(call)) { |
240 | | - call = call[Math.floor(Math.random() * call.length)]; |
241 | | - } |
242 | | - // If call is an object, check for probability |
243 | | - if (typeof call === "object") { |
244 | | - if ( |
245 | | - call.hasOwnProperty("probability") && |
246 | | - call.probability <= Math.random() |
247 | | - ) { |
248 | | - resolve(`${call.call} was not probable`); |
249 | | - return; |
250 | | - } |
251 | | - if ( |
252 | | - call.hasOwnProperty("schedule") && |
253 | | - !cronmatch.match(call.schedule, new Date()) |
254 | | - ) { |
255 | | - resolve(`${call.call} was not scheduled`); |
256 | | - return; |
257 | | - } |
258 | | - if (call.hasOwnProperty("remoteTimeout")) { |
259 | | - remoteTimeout = call.remoteTimeout; |
260 | | - } |
261 | | - if (call.hasOwnProperty("catchExceptions")) { |
262 | | - catchExceptions = call.catchExceptions; |
263 | | - } |
264 | | - if (call.hasOwnProperty("call") && call.call === "data") { |
265 | | - return processData(resolve, reject, req, call); |
266 | | - } |
267 | | - call = call.call; |
268 | | - } |
269 | | - if (call.startsWith("error")) { |
270 | | - const [_, code, message] = call.split(","); |
271 | | - reject({ code, message }); |
272 | | - } else if (call.startsWith("sleep")) { |
273 | | - const [_, timeout] = call.split(","); |
274 | | - setTimeout(function () { |
275 | | - resolve(`Slept for ${timeout}`); |
276 | | - }, timeout); |
277 | | - } else if (call.startsWith("slow")) { |
278 | | - const [_, timeout] = call.split(","); |
279 | | - resolve(buildResponse(timeout)); |
280 | | - } else if (call.startsWith("http://")) { |
281 | | - callRemoteService( |
282 | | - call, |
283 | | - useRp, |
284 | | - catchExceptions, |
285 | | - remoteTimeout, |
286 | | - req, |
287 | | - resolve, |
288 | | - reject, |
289 | | - ); |
290 | | - } else if (call.startsWith("image")) { |
291 | | - const [_, src] = call.split(","); |
292 | | - resolve(`<img src='${src}' />`); |
293 | | - } else if (call.startsWith("script")) { |
294 | | - const [_, src] = call.split(","); |
295 | | - resolve(`<script src='${src}?output=javascript'></script>`); |
296 | | - } else if (call.startsWith("ajax")) { |
297 | | - const [_, src] = call.split(","); |
298 | | - resolve( |
299 | | - `<script>var o = new XMLHttpRequest();o.open('GET', '${src}');o.send();</script>`, |
300 | | - ); |
301 | | - } else if (call.startsWith("cache")) { |
302 | | - const [_, timeout] = call.split(","); |
303 | | - resolve(loadFromCache(timeout, txn)); |
304 | | - } else if (call.startsWith("log")) { |
305 | | - const logging = call.split(","); |
306 | | - if (logging.length > 2) { |
307 | | - resolve(logMessage(logging[1], logging[2])); |
308 | | - } else { |
309 | | - resolve(logMessage("info", logging[1])); |
310 | | - } |
311 | | - } else if (call.startsWith("code")) { |
312 | | - const [_, script] = call.split(","); |
313 | | - executeCustomScript(script, req, resolve, reject); |
314 | | - } else { |
315 | | - // No other methods are currently implemented |
316 | | - resolve(`${call} is not supported`); |
317 | | - } |
318 | | - }); |
319 | | -} |
320 | | - |
321 | | -async function processRequest(req, res, params) { |
322 | | - const path = url.parse(req.url).pathname; |
323 | | - logger.info("Request Headers:", req.headers); |
324 | | - if (endpoints.hasOwnProperty(path)) { |
325 | | - try { |
326 | | - const results = []; |
327 | | - for (let i = 0; i < endpoints[path].length; i++) { |
328 | | - const call = endpoints[path][i]; |
329 | | - results.push(await processCall(call, req)); |
330 | | - } |
331 | | - var contype = req.headers["content-type"]; |
332 | | - |
333 | | - if (req.query.output && req.query.output === "javascript") { |
334 | | - res.send(results); |
335 | | - } else { |
336 | | - res.send(results); |
337 | | - } |
338 | | - } catch (reason) { |
339 | | - logger.error(reason.message); |
340 | | - res |
341 | | - .status(typeof reason.code === "number" ? reason.code : 500) |
342 | | - .send(reason.message); |
343 | | - } |
344 | | - } else { |
345 | | - res.status(404).send("404"); |
346 | | - } |
347 | | -} |
348 | | - |
349 | | -app.get("/**", function (req, res) { |
350 | | - processRequest(req, res, req.query); |
351 | | -}); |
352 | | - |
353 | | -app.post("/**", function (req, res) { |
354 | | - processRequest(req, res, req.body); |
355 | | -}); |
356 | | - |
357 | | -var server = app.listen(port, () => |
358 | | - console.log(`Running ${config.name} (type: ${config.type}) on port ${port}`), |
359 | | -); |
360 | | -console.log(`Configuration:`); |
361 | | -console.log(JSON.stringify(config)); |
362 | | -console.log(`Endpoints:`); |
363 | | -console.log(JSON.stringify(endpoints)); |
364 | | - |
365 | | -if (config.hasOwnProperty("options")) { |
366 | | - server.on("connection", (socket) => { |
367 | | - if (config.options.hasOwnProperty("connectionDelay")) { |
368 | | - sleep.msleep(config.options.connectionDelay); |
369 | | - } |
370 | | - if ( |
371 | | - config.options.hasOwnProperty("lossRate") && |
372 | | - parseFloat(config.options.lossRate) >= Math.random() |
373 | | - ) { |
374 | | - socket.end(); |
375 | | - throw new Error("An error occurred"); |
376 | | - } |
377 | | - }); |
378 | | -} |
| 13 | +startServer(config, logger, customCodeDir, port); |
0 commit comments