Skip to content

Commit 0152717

Browse files
authored
Modernize Node.js service (#138)
1 parent 7289ece commit 0152717

File tree

14 files changed

+496
-385
lines changed

14 files changed

+496
-385
lines changed

src/services/nodejs/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1+
coverage
12
node_modules
23
npm-debug.log
34
package-lock.json
4-
node.log
5+
node.log

src/services/nodejs/index.js

Lines changed: 5 additions & 370 deletions
Original file line numberDiff line numberDiff line change
@@ -1,378 +1,13 @@
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";
54

65
const config = JSON.parse(process.env.APP_CONFIG);
7-
86
const customCodeDir = process.env.CUSTOM_CODE_DIR;
97

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]);
809
if (isNaN(port)) {
8110
port = 8080;
8211
}
8312

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

Comments
 (0)