NodeJS Requested data leaks across HTTP requests - javascript

I have a simple webserver created in Express.js. The server serves files that are dynamically created by processing data from a third-party API.
Here is my webserver code, it requests builder.js to build the file, which requests, receives, processes and returns data from a third-party API by awaiting the response of a promisified oauth request as needed. The builder will at least call the API two or more times to create a complete file ready for serving.
const express = require('express');
const app = express();
const builder = require('./builder.js');
let requestID = 0;
app.get('/', function (req, res) {
requestID++;
console.log(`tab: ${requestID}<`);
res.set('Content-Type', 'text/plain; charset=UTF-8')
res.status(200).send('Hello World');
console.log(`tab: ${requestID}>`);
});
app.get('/file', async function (req, res) {
requestID++;
console.log(`tab: ${requestID}<`);
if (req.query["id"] != undefined) {
let URLparams = new URLSearchParams(req.query);
console.log(`tab: ${requestID}, requested id: ${URLparams.get("id")}`);
let output = await builder.buildFile(URLparams);
try {
console.log(`tab: ${requestID}, requested id: ${URLparams.get("q")}, served ${getIDfromOutput(output)}`);
res.set('Content-Type', 'application/rss+xml; charset=UTF-8')
res.status(200).send(output);
} catch(e) {
console.log(`tab: ${requestID}, ${e}`);
if (e instanceof String) { res.send(JSON.stringify(JSON.parse(e), null, 3)); }
else { res.send(JSON.stringify(e, null, 3)); }
};
} else {
res.set('Content-Type', 'text/plain; charset=UTF-8')
res.status(404)
.send("404: Page not found.");
}
console.log(`tab: ${requestID}>`);
});
app.listen(3000, "localhost");
The code works as intended when making requests to the /file one at a time.
//1 tab loaded
tab: 1<
tab: 1, requested: 1331444331778101248
tab: 1, requested: 1331444331778101248, loaded 1331444331778101248
tab: 1>
However, when the endpoint is requested for multiple unique requests at the same time (opening multiple tabs at the same time or running parallel wget commands), the server either responds correctly in some cases, but it mostly responds with the same file served previously.
// 5 unique tabs loaded at the same time: 1551641441679597569, 1448115610173558787, 1370689539505860613, 1328121208022446086, 1509637745140019212
tab: 1<
tab: 1, requested: 1551641441679597569
tab: 2<
tab: 2, requested: 1448115610173558787
tab: 2, requested: 1551641441679597569, loaded 1551641441679597569
tab: 2>
tab: 3<
tab: 3, requested: 1370689539505860613
tab: 3, requested: 1448115610173558787, loaded 1448115610173558787
tab: 3>
tab: 3, requested: 1370689539505860613, loaded 1370689539505860613
tab: 3>
The result of these simultaneous requests causes tabs 1-4 load fine, but tab 5 shows the output of tab 4. The console logger can't also seem to show the issue, but it's definitely different to the normal, one-off request.
I do not want this to happen as I fear that this may happen in production, and I do not want the outputs to be leaked across requests. However, I have no idea what is causing this or how to investigate to fix this issue. The code works fine when builder.buildFile() has to make one API call to the third-party, but I am always making 2 or more calls.
As requested, here is the stripped version of the buildFile function. When serving directly from the API through builder.getData(url), the responses for each request is unique and the responses do not cross over other requests. It happens only when it goes through builer.buildFile(). I feel like the issue is with the way the promises are handles, but I am not sure.
const OAuth = require('oauth');
const { promisify } = require('util');
require('dotenv').config();
let oauth = getOAuth();
module.exports = {
buildFile: async function (urlParam) { //URLSearchParams(req.query)
let id = urlParam.get("id");
try {
let metadata = await getData(`http://API/id=${id}?type=metadata`);
let XMLFile = await fileBuilder(metadata);
return XMLFile;
} catch (e) {
console.log("Err: ");
console.log(e);
return Promise.reject(e);
}
},
getData: async function (url) {
return await getData(url);
}
}
async function fileBuilder(metadata) {
let response = await getData(`$http://API/id=${id}?type=fulldata`);
response = extendData(response); //calls await getData() once more to fill in any gaps in the initial response
let xml = ``;
/* Build XMl file as a string, appending as the function processes the data*/
return xml;
}
function getOAuth() {
return new OAuth.OAuth(
'https://API/request_token',
'https://API/access_token',
process.env.api_key,
process.env.api_secret,
'1.0A', null, 'HMAC-SHA1'
);
}
async function getData(url) {
const get = promisify(oauth.get.bind(oauth));
let body;
try {
body = await get(
url,
process.env.access_key,
process.env.access_secret
);
} catch (e) {
console.log("getData failed: \n" + JSON.stringify(e));
return Promise.reject(e);
}
return JSON.parse(body);
}

You see the mixup in the console because you are using the shared requestId after it was changed. In order to avoid this you need to fix it at the beginning of the function.
The problem you have with the wrong file being served might come from the buildFile function since I can't locate it in this code fragment.
app.get('/file', async (req, res) => {
const localReqId = requestID++;
console.log(`tab: ${localReqId}<`);
if (req.query.id !== undefined) {
const URLparams = new URLSearchParams(req.query);
console.log(`tab: ${localReqId}, requested id: ${URLparams.get('id')}`);
const output = await builder.buildFile(URLparams);
try {
console.log(`tab: ${localReqId}, requested id: ${URLparams.get('q')}, served ${getIDfromOutput(output)}`);
res.set('Content-Type', 'application/rss+xml; charset=UTF-8');
res.status(200).send(output);
} catch (e) {
console.log(`tab: ${localReqId}, ${e}`);
if (e instanceof String) {
res.send(JSON.stringify(JSON.parse(e), null, 3));
} else {
res.send(JSON.stringify(e, null, 3));
}
}
} else {
res.set('Content-Type', 'text/plain; charset=UTF-8');
res.status(404)
.send('404: Page not found.');
}
console.log(`tab: ${localReqId}>`);
});

Related

Pass data from server.js to the next js app and use it globally

app.prepare().then(() => {
createServer(async (request, result) => {
try {
const parsedUrl = parse(request.url, true);
const { pathname, query } = parsedUrl;
// ...
// here config
const routes = await serverRouterApiWorker.getRoutes();
const clonedReactRoutes = Object.assign({}, reactRoutes.routes);
for (const reactRouteName of Object.keys(clonedReactRoutes)) {
const reactRoute = clonedReactRoutes[reactRouteName];
if (routes[reactRouteName] !== undefined && pathname === routes[reactRouteName].path) {
await app.render(request, result, reactRoute, query); // Here I choose which path to render
delete clonedReactRoutes[reactRouteName];
}
}
// ...
} catch (err) {
console.error('Error occurred handling', request.url, err)
result.statusCode = 500
result.end('internal server error')
}
}).listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://${hostname}:${port}`)
})
})
I need to get this exact routes config in my next.js application. I need this data to be loaded only when a request is made to the server (reloading the page, opening the page through a browser). If the page opens without reloading the request for new data should not do.
I can't use MyApp.getInitialProps because it makes a request every time the page is opened, not just when the page is reloaded. I need something like MyApp.getServerSideProps but I see that it is not possible https://github.com/vercel/next.js/discussions/13199
I would appreciate it if you could show me an example of how to transfer data from server js to application and how to use them (I need to use them in a component)

ltijs: Deep Linking hanging when sending back the data to the LMS

I'm loosing my sleep trying to make ltijs work with Deep Linking.
I've built a simple client that registers a tool against an LMS platform. I'm able to setup the tool on the platform (Moodle and Canvas) and perform launches.
The issue I've is when trying to add "Deep Linking" to my tool. Following the directions in the ltjs docs (https://cvmcosta.me/ltijs/#/deeplinking) I've setup an "lti.onDeepLinking()" handler, that redirects to an html page where some content can be selected.
I then post this form to my own tool express server, where its data gets encoded and an auto-submit form is generated and sent back to the client (and then auto-submitted to the LMS).
This is where things "hang". I clearly see the POST request being sent over to the deep_link_redirect_url as requested by the original deep link request message, but the LMS then just "hangs" there until it eventually times out (I guess) and shows a generic error page...
I'm pretty sure I'm missing some vital piece of the puzzle here but I've no clue on what it could be..
const path = require("path");
const lti = require("ltijs").Provider;
const ltiKey = "myverylongandspecialltikeyfortesting";
toolUrl = "http://192.168.1.25:3010";
// setup provider
lti.setup(ltiKey, {
url: "mongodb://127.0.0.1:3001/lticlient",
}, {
// options
appRoute: "/lti/launch",
loginRoute: "/lti/login",
cookies: {
secure: false, // Set secure to true if the testing platform is in a different domain and https is being used
sameSite: "None" // set it to "None" if the testing platform is in a different domain and https is being used
},
devMode: true, // set it to false when in production and using https,
keysetRoute: "/lti/keys",
});
// set the lti launch callback
lti.onConnect((token, req, res) => {
console.log("IDTOKEN", token);
return res.send("LTI TOOL LAUNCHED!");
});
lti.onDeepLinking((token, req, res) => {
console.log("DEEP LINKING", token);
// Call redirect function to deep linking view
lti.redirect(res, '/deeplink')
})
// GET request to show the selection page
lti.app.get("/deeplink", async (req, res) => {
res.sendFile(path.join(__dirname, '/public/select.html'))
});
// POST submit from selection page with selected content item
lti.app.post("/deeplink", async (req, res) => {
const resource = req.body
const items = [
{
type: 'ltiResourceLink',
title: resource.product,
url: `${toolUrl}/lti/launch`,
custom: {
product: resource.product
}
}
]
const form = await lti.DeepLinking.createDeepLinkingForm(res.locals.token, items, { message: 'Successfully registered resource!' })
console.log("RETURNING SELF-SUBMITTING FORM", form);
return res.send(form);
})
const getPlatforms = () => {
return [
{
url: "http://192.168.1.239",
name: "MoodleClient1",
clientId: "client-id-provided-by-Moodle",
authenticationEndpoint: "http://192.168.1.239/mod/lti/auth.php",
accesstokenEndpoint: "http://192.168.1.239/mod/lti/token.php",
authConfig: { method: 'JWK_SET', key: "http://192.168.1.239/mod/lti/certs.php" }
}
];
}
const registerPlatforms = async () => {
const platforms = getPlatforms();
platforms.forEach(async (cfg) => {
console.log(`Registering platform ${cfg.name}`);
await lti.deletePlatform(cfg.url, cfg.clientId);
await lti.registerPlatform(cfg);
const platform = await lti.getPlatform(cfg.url, cfg.clientId);
await platform.platformActive(true)
});
}
const setup = async () => {
await lti.deploy({ port: 3010 });
registerPlatforms();
console.log("platforms registered and active");
}
setup();

Axios response not triggered and Node execution silently stops

I make an HTTP call with Axios meant to respond with 200 (tested on Postman).
But the response is not triggered in try/catch/finally logic and the execution weirdly stops.
The code looks like this:
(async () => {
const axios = require('axios');
const url = ''; // The URL responds with a HTML page
const cookies = ''; // The cookies are checked with Postman
const config = {
headers: {
Cookie: cookies
}
};
try {
let res = await axios.get(url, config);
console.log('Response received.');
console.log(res);
} catch(err) {
console.log('Error happened.');
console.log(err);
} finally {
console.log('Finally block.');
}
console.log('End of execution.');
})()
All console.log aren't called. I even tried to put debugger or other actions not related to writing on stdout but they aren't called either.
The status returned by the process is 0.
I just found the solution, my program was stopping because my request wasn't resolved.
I needed to set the connection header to keep-alive, and now it works properly.

Show console messages on Android browser

I do web development with an Android (not rooted) phone and look for a method to show the browser (Chrome or Firefox) console messages.
Neither Android Chrome nor Firefox has web inspector/console and I don't found Firefox (working) add-on.
Update: I can't connect my phone to a computer (ADB, Chrome remote tool... are unavailable).
Anybody can hint me a viable solution?
Try https://github.com/liriliri/eruda, very good approximation of Dev Tools.
All you need to do is add this snippet on top of the page:
<script src="//cdn.jsdelivr.net/npm/eruda"></script>
<script>eruda.init();</script>
Edit
A practical solution can be found at https://stackoverflow.com/a/60106504/1025638
Original (self) answer
I don't found a "just work" solution to solve my problem so I made a short tool to send the logs and errors from the browser to a backend server.
It uses a Proxy around window.console object and implements the function window.onerror to post the messages to the server.
I structured the code to use it as an expressjs middleware for reusability.
It isn't perfect and it may not be compatible with
all browsers, but it really helps if there isn't dev tools in a browser.
Anyone can test it through repl.it.
// Use this module as middleware with expressjs compatible server:
//
// In the server:
// consoleWrapperMiddleware(basePath, app)
// basePath: URL path to send browser messages
// app: expressjs application reference
// return: nothing
//
// In the html page:
// <script src="basePath" />
// basePath: URL path to send browser messages
function consoleWrapper(oldConsole, oldOnerror, serverUrl) {
function _post(log) {
const req = new XMLHttpRequest()
req.open('POST', serverUrl, true)
req.setRequestHeader('Content-Type', 'application/json')
req.send(JSON.stringify(log))
}
const console = new Proxy(oldConsole, {
get: (target, propKey, receiver) => {
const origMethod = target[propKey]
return function (...args) {
if (origMethod === undefined) {
const message = 'unknown console method: '+propKey
_post({ level: 'wrap', message: [message]})
return message
}
else {
let result = origMethod.apply(this, args)
_post({ level: origMethod.name, message: args })
return result
}
}
}
})
const onerror = function(msg, url, line, col) {
if (typeof oldOnerror === 'function')
oldOnerror(arguments)
const content = [ msg, url+':'+line+':'+col ]
_post({ level: 'js-err', message: content })
}
return [ console, onerror ]
}
function consoleWrapperMiddleware(basePath, app) {
app.get(basePath, (req, res) => {
res.status(200).send('[ window.console, window.onerror ] = '+consoleWrapper.toString()+'(window.console, window.onerror, location.protocol.concat("//").concat(location.host).concat("'+basePath+'"))')
console.log('Console wrapper sent')
})
app.post(basePath, (req, res) => {
let body = []
req.on('data', (chunk) => {
body.push(chunk)
}).on('end', () => {
body = Buffer.concat(body).toString()
const logMsg = JSON.parse(body)
console.log('['+logMsg.level+']', ...logMsg.message)
res.writeHead(200)
res.end()
})
})
console.log('Log server listening from',basePath)
}
module.exports = consoleWrapperMiddleware

Node JS, make HTTPS request synchronously from two links

I want to make a HTTPS request to an external link through Node JS. On my first call, I need to fetch user id by looping through several users. On my second call, I need to input that user id in the URL link and fetch user properties. Keep repeating this process till I go through all users. The end goal is to store data of every user in a JSON format. There is no front-end involved. Any direction/advice is much appreciated.
I can't share the actual link due to api keys. But here is the hypothetical scenario. I only show 2 users here. I have about 10,000 users in my actual data set.
Link 1
https://www.google.com/all_users
JSON Output
{
"name": "joe",
"uri": "/id/UserObject/User/1234-1234",
},
{
"name": "matt",
"uri": "/id/UserObject/User/5678-5678",
}
Link 2
https://www.google.com//id/UserObject/User/1234-1234
JSON Output
{
"name": "joe",
"uri": "/id/UserObject/User/1234-1234",
"Property Values": {
"height": "2",
"location": "canada"
},
"Other Values": {
"work": "google",
"occupation": "developer"
}
}
Nested JSON
{
"PropertySetClassChildrenResponse": {
"PropertySetClassChildren": {
"PropertySetInstances": {
"totalCount": "1",
"Elements": [
{
"name": "SystemObject",
"uri": "/type/PropertySetClasses/SystemObject"
}
]
}
}
}
}
Not tested, but this should point you in the right direction. It uses Promises and assumes that run in an ES6 environment:
const rp = require('request-promise');
const Promise = require('bluebird');
fetchAllUsers()
.then(extractUserUris)
.then(extractUserIds)
.then(buildUserDetailRequests)
.then(Promise.all) // run all the user detail requests in parallel
.then(allUserData => {
// allUserData is an array of all users' data
});
function fetchAllUsers() {
return rp('https://api.whatever.com/all_users');
}
function extractUserUris(users) {
return users.map(user => user.uri);
}
function extractUserIds(userUris) {
return userUris.map(userUri => userUri.split('/').pop());
}
function buildUserDetailRequests(userIds) {
return userIds.map(userId => rp("https://api.whatever.com/user/" + userId));
}
I'd suggest using the request package to make your HTTP requests easier.
> npm install request
Then you would obtain a list of all users with something like this:
var request = require('request');
request.get({url: "https://example.org/all_users"}, handleUsersResponse);
You'd handle the request response like this:
function(err, response, body) {
if (!err && response.statusCode == 200) {
// parse json (assuming array of users)
var users = JSON.parse(body);
// iterate through each user and obtain user info
for(var i = 0; i < users.length; i++) {
var userUri = users[i].uri;
obtainUserInfo(userUri)
}
}
}
obtainUserInfo function would be similar to the above code.
One important thing to keep in mind is that since the HTTP requests are being made asynchronously, when you make the requests in a loop, the next iteration of the loop does not wait until the work is finished before moving to the next iteration and starting the next request. So in effect, your loop would start all the HTTP requests nearly in parallel. This can easily overwhelm both your client and the server. One way to get around this is to use a worker queue to enqueue the work and ensure that only a maximum number of HTTP requests are being executed at any given time.
You don't want to do synchronous calls, it defeats the purpose of using Node. So by the Node powers invested in me by the State of Texas I hereby cast that synchronous way I thinking out of you!
Just kidding :), but let's do this the Node way.
Install these two libraries:
sudo npm install Promise
sudo npm install request
And set your code to look like:
var Promise = require('promise');
var request = require('request');
//Get your user data, and print the data in JSON:
getUserData()
.then(function(userData) {
console.log(JSON.stringify(userData));
}).catch(function(err) {
console.log('Error: ' +err);
});
/**
* Prepares an Object containing data for all users.
* #return Promise - Contains object with all user data.
*/
function getUserData() {
return new Promise(function(fulfill, reject) {
// Make the first request to get the user IDs:
var url1 = 'https://www.google.com/all_users';
get(url1)
.then(function(res) {
res = JSON.parse(res);
// Loop through the object to get what you need:
// Set a counter though so we know once we are done.
var counter = 0;
for (x=0; x<res.users.length; x++) {
var url2 = 'https://www.google.com//id/UserObject/User/';
url2 = url2 + res.users.id; //Wherever the individual ID is stored.
var returnDataArr = [];
get(url2)
.then(function(res2) {
// Get what you need from the response from the 2nd URL.
returnDataArr.push(res2);
counter++;
if (counter === res.users.length) {
fulfill({data: returnDataArr}); //Return/Fulfill an object containing an array of the user data.
}
}).catch(function(err) {
// Catch any errors from the 2nd HTTPS request:
reject('Error: ' +err);
});
}).catch(function(err) {
// Catch any errors from the 1st HTTPS request:
reject('Error: ' +err);
});
}
/**
* Your HTTPS GET Request Function
* #param url - The url to GET
* #return Promise - Promise containing the JSON response.
*/
function get(url) {
return new Promise(function(fulfill, reject) {
var options = {
url: url,
headers: {
'Header Name': 'Header Value',
'Accept': 'application/json',
'Content-Type': 'application/json'
};
request(options, function(err, res, body) {
if (err) {
reject(err);
} else {
fulfill(body);
}
});
});
}
So what this Promise does, is that it returns the value once we actually have it. In the code above, we are first getting that list of users, and then as we parse through it, we are making a new asynchronous HTTP request to get the additional data on it. Once we get the user data, we push it to an array.
Finally, once our counter hits its endpoint, we know that we have gotten all the user data, and so we call fulfill which essentially means return, and it returns an object containing an array of the user data.
Let me know if this makes sense.
The answers above helped me go further with my solution and get the desired outcome. However, I spent a lot of time trying to understand node, promises in node, making an API call, etc. Hopefully, this will help to a beginner level node developer.
NODE
Node.jsĀ® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
If you are a JavaScript developer, you would prefer to use Node as you wouldn't have to spend time learning a new language like Java or Python.
GOAL
Make a HTTPS call to an external link to fetch all server URIs. Pass in the URI as a param to create a second link to fetch all server properties. Loop through to all server uris and properties. Refer the original post on the top for the data structure. The external link also required basic auth and headers.
CODE
Install NPM modules request (https call), bluebird (promises) and lodash(utility) and express(node framework).
/
********************** MODULES/DEPENDENCIES **********************/
var express = require('express');
var request = require('request');
var Promise = require('bluebird');
var _ = require("lodash");
/********************** INITIATE APP **********************/
var app = express();
console.log("Starting node server...");
/**
* Your HTTPS GET Request Function
* #param url - The url to GET
* #return Promise - Promise containing the JSON response.
*/
function get(url) {
return new Promise(function(resolve, reject) {
// var auth = "Basic " + new Buffer(username + ':' + password).toString("base64");
var options = {
url: url,
headers: {
// 'Authorization': auth,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
console.log("Calling GET: ", url);
if ('development' == app.get('env')) {
console.log("Rejecting node tls");
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}
request(options, function(error, response, body) {
if (error) {
reject(error);
} else {
// console.log("THIS IS BODY: ", body);
resolve(body);
}
});
});
}
/********************** GET DATA FUNCTION **********************/
function getServerData() {
/********************** URI VARIABLES **********************/
var username = 'username',
password = 'password',
role = 'Read-Only',
url_host = 'https://link.com:10843';
/********************** URL 1 **********************/
var url1 = url_host + '/type/PropertySetClasses/SystemObject/Server/?maxResults=1000&username=' + username + '&password=' + password + '&role=' + role;
console.log("Getting server data...", url1);
/********************** GET REQUEST 1 **********************/
return get(url1)
.then(function(res) {
console.log("Got response!");
res = JSON.parse(res);
res = res.PropertySetClassChildrenResponse.PropertySetClassChildren.PropertySetInstances.Elements;
// console.log("THIS IS RES: ", res);
/********************** FETCH URI FROM RES NESTED OBJECT **********************/
var server_ids = _.map(res, function(server) {
return server.uri;
});
console.log("Calling server urls", server_ids);
// Loop through the object to get what you need:
// Set a counter though so we know once we are done.
return Promise.map(server_ids, function (id) {
var url2 = url_host + id + '?username=' + username + '&password=' + password + '&role=' + role;
console.log("Calling URL", url2);
return get(url2)
.then(function(res2) {
res2 = JSON.parse(res2);
var elements = res2.PropertySetInstanceResponse.PropertySetInstance.PropertyValues.Elements;
console.log("Got second response", res2, elements);
return elements;
});
})
.then(function (allUrls) {
console.log("Got all URLS", allUrls);
return allUrls;
});
})
.catch(function(err) {
console.error(err);
throw err;
});
};
app.listen(8080, function() {
console.log("Server listening and booted on: " + 8080);
app.get("/serverInfo", function (req, res) {
console.log("Calling server info");
return getServerData()
.then(function(userData) {
var userData = JSON.stringify(userData, null, "\t");
console.log("This is USERDATA Data: ", userData);
res.send(userData);
})
.catch(function(err) {
console.error(err);
res.send({
__error: err,
message: err.message
});
});
});
});

Categories

Resources