I have as an assignment to do a nodeJS HTTP server with some form of metrics on it.
For this assignment, I am not allowed to use any form of external libraries & as the metric of the request I'll store the response time.
For route handling ill use a simple switch statement comparing the URL of the request
const http = require("http");
var metrics = []
http.createServer((req, res) => {
switch(req.url){
case "/route"
var start = Date.now();
// some code to process the request, maybe some requests to the database, maybe retrieve some files etc
res.end();
metrics.push(Date.now() - start);
break;
}
}).listen(8080,()=>console.log("Server running!"))
The thing is, if I want to do this app with one or a low number of requests this method would be ok,
however, in any other normal scenarios, this would be awful for any further changes to those metrics & much more.
I'm thinking of trying to solve this with some sort of event listeners that I would call at the beginning & the end of my request.
Something that would store information about the request at the beginning & launch an event at the end to stop processing the metrics.
server.on('end', (data)=>{
//end metrics somehow
})
Although it seems a little hard to implement, especially as I don't really want to overwrite a nodeJS event to add some data on it ( for instance I may want to add an id of the request, or a timestamp )
Is there a way to properly do this with nodeJS HTTP?
You can handle it by finish event of res object. The event will be called when you call res.end() (or something like that).
My recommendation, to metrics an API service, you will need more information than response times.
const http = require("http");
var metrics = []
http.createServer((req, res) => {
const startPoint = Date.now();
res.on("finish", () => {
if (req.url === "/metrics") {
return; // no metrics for metrics route
}
metrics.push({
path: req.url,
method: req.method,
status: res.statusCode,
dateTime: startPoint,
times: Date.now() - startPoint,
});
});
switch (req.url) {
case "/route":
// some code to process the request, maybe some requests to the database, maybe retrieve some files etc
setTimeout(() => {
res.end();
}, 1000);
break;
case "/metrics":
res.setHeader('Content-Type', 'application/json');
res.write(JSON.stringify(metrics));
res.end()
break;
default:
res.statusCode = 404;
res.end();
break;
}
}).listen(8080, () => console.log("Server running!"))
As you can see, when you call GET http://localhost:8080/metrics api, the response will look like this:
[
{
"path": "/test",
"method": "GET",
"status": 404,
"dateTime": 1613631702906,
"times": 1
},
{
"path": "/route",
"method": "GET",
"status": 200,
"dateTime": 1613631702906,
"times": 1004
}
]
create your own middleware on each routes, if you can't use framework like express or koa
Note: this is the simplest example, but it gives a clue of research
class Middleware {
use(func) {
this.next_func = (
(stack) =>
(next) =>
stack(
func.bind(this, next.bind(this)),
)
)(this.next_func);
}
next_func = (next) => next();
}
Related
I have a to-do list app that updates a string in a mongodb database with every change in state of the to-do list - that string is parsed on reload to render the state. It works great, except when I trigger 5 or 6 state changes quickly in sequence, it hangs the page. As example, if I delete 5 tasks over the course of a couple seconds. I assume the problem is handling all those post requests, but maybe it's on the updating mongodb side? Is there a way to handle a lot of post request like that in a some sort of queue?
Client side:
function sendData(obj) {
fetch('/jsondata', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(obj),
}).catch(function (error) {
console.log(error);
});
console.log('db updated');
}
Here's the mongo side that runs when the POST request is requested from client...if it helps:
app.post('/jsondata', function (req, res) {
updateUserCache(currentUserEmail, JSON.stringify(req.body));
});
async function updateUserCache(email, newState) {
const foundUser = await user.findOne({
email: email,
});
foundUser.cachedState = newState;
const newDate = await Date.now();
foundUser.date = newDate;
await foundUser.save();
console.log('user cache has been updated');
}
It's hanging because you're never sending back a response from your backend code, and at some point the browser will stop making new connections to it.
So make sure you end the requests properly:
app.post('/jsondata', async function (req, res) {
await updateUserCache(currentUserEmail, JSON.stringify(req.body));
res.end();
});
I want my http respond script to respond with data from my SQL server. So I can use AJAX to update HTML with data from my SQL server. And I cant find a way to do this. I'm just learning about async and I have a feeling that if I can save the output of my async function to a global var then it will work. Any help would save my headache.
My simple Listen script is:
var test = "hello!"
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(test);
res.end();
}).listen(8080);
and my sql code is:
const util = require('util');
var mysql = require('mysql');
var con = mysql.createConnection({
host: "XXXXX",
user: "XXXXX",
password: "XXXXX",
database: "XXX"
});
var DBresult=null;
function getdb(){
const query = util.promisify(con.query).bind(con);
(async () => {
try {
const rows = await query('SELECT * FROM mylist');
DBresult=rows;
} finally {
con.end();
}
})()
}
Do NOT use any globals or shared, higher scoped variables for your asynchronous result in a server. Never do that. That is an anti-pattern for good reason because that can create intermittent concurrency problems because more than one request can be in process at the same time on your server so these would cause conflicting access to those variables, creating intermittent, very-hard-to-debug problems. I repeat again, NEVER.
You didn't describe an exact situation you are trying to write code for, but here's an example.
Instead, you use the result inside the context that it arrives from your asynchronous call. If you can use await, that generally makes the coding cleaner.
Here's a simple example:
const con = mysql.createConnection({
host: "XXXXX",
user: "XXXXX",
password: "XXXXX",
database: "XXX"
});
const query = util.promisify(con.query).bind(con);
http.createServer(async function(req, res) {
if (req.path === "/query" && req.method === "GET") {
try {
const rows = await query('SELECT * FROM mylist');
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(rows));
} catch(e) {
console.log(e);
res.statusCode = 500;
res.end();
}
} else {
// some other respone
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write("hello");
res.end();
}
}).listen(8080);
Things to note here:
Checking both path and method before handling the request.
Making callback function async so it can use await.
Making sure any promise rejection from await is caught by try/catch and an error response is sent if there's an error.
Sending result as JSON and setting appropriate content-type.
You may be using the plain http module as a learning experience, but you will very quickly find that using the simple Express framework will save you lots of programming time and make things lots easier.
If you want to use async functions, it would be something like this, take care of "async" in function for the createServer
var http = require('http');
var server = http.createServer(async (req, res) => {
async function mgetdata() {
// Async code goes here
return 'Hello, world!';
}
// Wait for the async function to complete and get its return value
const response = await mgetdata();
// Send the response
res.end(response);
});
I have been given a task — I'm trying to make routes/endpoints using just Node to better demostrate what is happening under the covers with Express. But I have to use streams.
I have two routes GET /songs and GET/refresh-songs with the following requirements:
We need the following two endpoints:
GET /songs
GET /refresh-songs
All endpoints should return JSON with the correct headers
/songs should...
stream data from a src/data/songs.json file
not crash the server if that file or directory doesn't exist
bonus points for triggering refresh code in this case
bonus points for compressing the response
/refresh-songs should...
return immediately
it should not hold the response while songs are being refreshed
return a 202 status code with a status JSON response
continue getting songs from iTunes
frontend should have
UI/button to trigger this endpoint
This is what I have so far in my server.js file where my endpoints will live.
const { createServer } = require('http');
const { parse: parseUrl } = require('url');
const { createGzip } = require('zlib');
const { songs, refreshSongs } = require('./songs');
const fs = require('fs');
const stream = fs.createReadStream('./src/data/songs.jso')
const PORT = 4000;
const server = createServer(({ headers, method, url }, res) => {
const baseResHeaders = {
// CORS stuff is gone. :(
'content-type' : 'application/json'
};
// Routing ¯\_(ツ)_ /¯
//
var path = url.parseUrl(url).pathname;
function onRequest(request, response){
response.writeHead(200, 'Content-Type': baseResHeaders['content-type']);
stream.on('data', function(err, data){
if (err) {
response.writeHead(404);
response.write('File not found');
refreshSongs();
} else {
response.write(createGzip(data))
}
response.end()
})
}
switch (path) {
case '/songs':
onRequest()
break;
case '/refresh-songs':
onRequest()
break;
}
server.on('listening', () => {
console.log(`Server running at http://localhost:${PORT}/`);
});
I am wondering if I am wiring the onRequest method I created correctly and if the switch statement is correctly going to intercept those URL's
I'm looking for an easy solution to front-end and back-end communication.
I want to write simple JS front-end client where a user can put a number between 1 an 10 000 to guess the number that server has generated.
So the client job is to send number that user is guessing. The server should test if secretNumber is higher or lower then that provided by the user and it should send back that info.
For now, my server only sends that secret number. I'm getting it inside my client console, so the connection is working.
My question is how should I modify my server code to read the number value from request, test it and then send the right response (example -> your number is higher than the secretNumber)?
This is my server:
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors());
app.use((request, response, next) => {
console.log(request.headers);
next();
});
app.use((request, response, next) => {
request.secretNumber = Math.floor(Math.random() * 10000) + 1;
next();
});
app.get('/', (request, response) => {
response.json({
secretNumber: request.secretNumber
});
});
app.listen(3001, () => console.log("Listening on 3001"));
Here is my front-end JS code (I'm using axios):
export function guessNumber(guessValue) {
return dispatch => {
dispatch({ type: GUESS_NUMBER });
axios
.post('/guess', {
isNumber: guessValue,
})
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
};
}
And I was here looking for answer, but maybe I'm to inexperiened and I need some real example...
First you need to persist the secretNumber between requests. At the moment you are generating a new value on each request.
Assuming just one client using the backend concurrently, you can do this by generating the secretNumber when the server starts and keep it in memory (assign it to a variable).
Then you can simply use route params to capture the client's guess:
app.get('/guess/:guess', (request, response) => {
const guess = params.guess;
// compare guess with secretNumber and respond accordingly
});
Alternatively you can use the request's body (https://expressjs.com/en/4x/api.html#req.body) instead of route params.
I'm long-polling node.js route with JQuery's ajax request (xhr). This sends a GET request to my Express server and listens for message bus events. I'm setting a timeout on that GET request (as my proxy would kill long requests). So after the timeout, an abort event should be sent by the client to the server.
I want to catch that abort/close/finish event and remove the relevant message bus listener/subscriber.
But I struggle. I tried req.on("close"..) and the on-finished npm module, but that didn't work for me. I'm also not much more clever after reading the http documentation of node: https://nodejs.org/api/http.html.
Any ideas how to tackle this beast? Or better ways to remove listeners to prevent memory leaks?
Server side essentials:
// server.js
var express = require("express");
var EventEmitter = require("events").EventEmitter;
var messageBus = new EventEmitter();
messageBus.setMaxListeners(20);
var REST_PORT = (process.env.PORT || 5000);
var app = express();
app.get("/events", (req, res) => {
var listener = function(res) {
messageBus.once("message", function(data) {
res.status(200).json(data);
});
};
req.on("abort", function() { //I tried also "aborted", "close", "closed", "finish", "finished"..no luck
messageBus.removeListener("message", listener);
});
listener(res);
console.log("Total listeners to 'message' events:", messageBus.listeners("message").length);
});
// other messageBus.emit logic ..
app.listen(REST_PORT, () => {
console.log("Application ready on port " + REST_PORT);
});
Client side essentials:
//client.js
$.ajax({
method: "GET",
async: true,
url: "/events",
success: function(data) {
callback(data);
},
complete: function(request, status, err) {
if (status == "timeout" || status == "success") {
console.log("LOG: Normal long-polling timeout or successful poll, continuing.");
longPoll();
} else {
console.warn("WARN: Server probably offline, retrying in 2 sec.");
setTimeout(function() {
longPoll();
}, 2000);
}
},
timeout: 30000
});
Thank you!
If this helps someone, I finally decided to implement the long-polling differently and killing the client request at the server side after certain timeout. This works for me nicely and after reflection, is probably better mechanism than trusting the client to close the requests correctly.
setTimeout(() => {
if (!responded) {
messageBus.removeListener("message", listener);
res.status(204).end();
}
}, 30000);
I recommend aborting your custom long-polling system altogether and using one of the existing messaging/socket-type systems. There are many that are fully formed. socket.io is the most popular still and works well, but some alternatives like these might be better https://www.reddit.com/r/node/comments/4ktqae/socketio_alternatives/