How to troubleshoot socket hangup in Node app - node.js

So, I have a node app running in 2 servers. When a call is made to the app from either a browser or Postman, a load-balancer will direct the call to one of these 2 servers. I did not setup the load-balancer, so I don't currently have knowledge of how exactly it is set up.
The Node app, basically, will get a call with some data, pull a profile from Redis, then send the required data to Google Analytics. It will then return a 1x1 pixel gif back as a response, if successful, and a 400 status and a message if there is something wrong with the call like missing data or the profile key doesn't exist in Redis. I'm using Express to handle the calls and I'm using the Redis module to handle communication between the app and Redis.
Intermittently, we are seeing failed calls (no response) in the browser (Chrome and Firefox), but they will succeed in Postman. When the calls fail in the browser, I'm able to see all my logs from the app and no errors. The hits are also sometimes going to GA as we expect them to.
On occasion, I can see a failed call in Postman and the error says 'socket hang up'.
My question, really, is where should I look to find whats causing this? I'm not a network guy, and I'm kinda stumped on this.
I checked to make sure that my responses are closed correctly. I'm
using res.send() on all of my calls.
I've tested the app locally with postman and a
browser, with no issues.
I have my timeout in Postman set to 0 for
infinite, I have ssl verification turned off and I'm not using a
proxy.
I have no errors popping up in my app
We also have a ruby app that this one is replacing that does not exhibit these behaviors and I'm not sure if that's due to Ruby handling things differently than Express, or my bad programming.
Is it possible that communication between the app and Redis is getting slow and causing the socket hang up error? Or do anyone think this is more of anetworking issue?
I'm obviously not expecting anyone to be able solve this issue for me, I'm really just trying to learn and find out where I need to be looking to find out whats causing these issues.
Here is an example of one of the calls I'm making in case I'm messing this all up in the way I'm handling calls:
How I'm setting up my Redis connection:
var redis = require('redis');
var client = redis.createClient();
const { promisify } = require('util');
const hgetallAsync = promisify(client.hgetall).bind(client);
const hsetAsync = promisify(client.hmset).bind(client);
const existsAsync = promisify(client.exists).bind(client);
const deleteAsync = promisify(client.del).bind(client);
And how I'm using these to makes calls. This is a bit convoluted, but the reason is because the redis module makes Async calls and I was trying to get them to work "synchronously":
/**
* Record a pageview in GA
*
* #var {bool} exists - This returns a value of 1 if the profile exists in Redis and 0 if it does not
* #var {*} getall - This returns the profile, from Redis associated with the profile id
* #var {*} reqobj - Contains referrer, user_language, and user_agent
* #var {*} params - This is the query string from the url. Example query string: ?a=1&b=2&c=3
* #var {array} pageViewArray - [profile_id, pageView]
*/
router.get('/example.gif', function (req, res, next) {
var clientId = Before(req, res);
if (DatasetCheck(req, res)) {
var profileId = GetProfileIdFromCookie(req);
var json = {};
var params = req.query;
if (profileId) {
var exists = existsAsync(profileId);
var getall = hgetallAsync(profileId);
var validate = validation.ValidExampleRequest(clientId, req.query);
if (validate == true) {
var reqobj = RequestEnvironment(req);
var mergedobj = { ...reqobj, ...params };
var pageViewArray = PageView(mergedobj);
if (pageViewArray != false) {
exists.then(function (exists) {
if (exists > 0) {
getall.then(async function (getall) {
reqobj = RequestEnvironment(req);
var mergedobj = { ...reqobj, ...params };
PageViewPart2(mergedobj, getall, pageArray[1]);
}).then(function () {
//res.send(result);
RespondWithPixel(res);
});
} else {
logger.error("get example.gif ==> Profile does not exist");
json.status = "get example.gif ==> Profile does not exist";
res.status(400).send(JSON.stringify(json));
}
});
} else {
logger.error("get example.gif ==> Invalid hit");
json.status = "get example.gif ==> Invalid hit";
res.status(400).send(JSON.stringify(json));
}
} else {
logger.error("get example.gif ==> Invalid hit request");
json.status = "get example.gif ==> called with an empty hostname: " + params.hostname + " or path: " + params.path;
res.status(400).send(JSON.stringify(json));
}
} else {
logger.info("get example.gif ==> profileId is invalid")
json.status = "get example.gif ==> profileId is invalid or missing";
res.status(400).send(JSON.stringify(json));
}
}
});

Related

How can I get an express server to send the data retrieved by a separate provider to a client?

I'm currently writing a small program in Node.js for an express server. I am using providers to facilitate the separation of concerns. What I'm having trouble with is figuring out how to send a return value from the provider function back to the express server and then to the client. I'm probably missing something with the asynchronous code but can't seem to figure out what. I'd be grateful if you could guide me in the right direction! Sorry if this is an obvious question, I'm very new to programming.
This is the code skeleton for the main.js:
server.get("/someurl*", (req, res) => {
let name = req.query.name;
let id = req.query.id;
if (
(isValid(name) == true) &&
(isValid(id) == true)
) {
let provider = new getSomething();
provider.getMethod(id);
res.send(provider.getMethod(id));
};
});
This is the code skeleton for provider.js:
class getSomething extends abstractClass {
getMethod(id) {
this.id = id;
// Acquiring data from database based on id
…
if (err)
return (‘error’);
return data;
};
};
What I do not understand is how can I send the data back to the express server (main.js) and then send that data back to the client with res.send. The way I'm doing it above doesn't work as it returns an undefined value. I cannot do res.send directly from the provider as it should only be responsible for retrieving data from the database and the server instance is defined in main.js.
Thank you very much in advance!
In your provider class I would ammend the getMethod to be something more like this:
getMethod(id) {
this.id = id;
//do something. update this.data
if (err) this.error = err;
//optional for method chaining: return this;
}
Class methods can be used to modify the properties of the class, which you can then send to the client.
then in your endpoint:
server.get(“/someurl*”, (req, res) => {
const name = req.query.name; //use consts for values that you dont want to change
const id = req.query.id;
if ((isValid(name) == true) && (isValid(id) == true)) {
const provider = new getSomething();
provider.getMethod(id);
if (provider.error) return res.status(500).json({error: provider.error});
return res.status(200).json({data: provider.data});
};
return res.status(500).error({error: 'query data not valid'});
});
this is extracting the data you require from the query object, then if everything is valid (im asuming some other function you have for validation) will instantiate a new instance of your class, run the method, and if theres an error send this back to the client otherwise send back data to the client.
If the data is not valid it also sends back a response to the client. In any case you want to send something back to the client.

How to use the full request URL in AWS Lambda to execute logic only on certain pages

I have a website running on www.mywebsite.com. The files are hosted in an S3 bucket in combination with cloudFront. Recently, I have added a new part to the site, which is supposed to be only for private access, so I wanted to put some form of protection on there. The rest of the site, however, should remain public. My goal is for the site to be accessible for everyone, but as soon as someone gets to the new part, they should not see any source files, and be prompted for a username/password combination.
The URL of the new part would be for example www.mywebsite.com/private/index.html ,...
I found that an AWS Lambda function (with node.js) is good for this, and it kind of works. I have managed to authenticate everything in the entire website, but I can't figure out how to get it to work on only the pages that contain for example '/private/*' in the full URL name. The lambda function I wrote looks like this:
'use strict';
exports.handler = (event, context, callback) => {
// Get request and request headers
const request = event.Records[0].cf.request;
const headers = request.headers;
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
// Continue request processing if authentication passed
callback(null, request);
return;
}
// Configure authentication
const authUser = 'USER';
const authPass = 'PASS';
// Construct the Basic Auth string
const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
// Require Basic authentication
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
};
The part that doesn't work is the following part:
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
// Continue request processing if authentication passed
callback(null, request);
return;
}
My guess is that the request.uri does not contain what I expected it to contain, but I can't seem to figure out what does contain what I need.
My guess is that the request.uri does not contain what I expected it to contain, but I can't seem to figure out what does contain what I need.
If you're using a Lambda#Edge function (appears you are). Then you can view the Request Event structure here: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html#lambda-event-structure-request
You can see the actual value of the request URI field by using console.log and checking the respective logs in Cloudwatch.
The problem might be this line:
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
If you're strictly looking to check if a JavaScript string contains another string in it, you probably want to do this instead:
if (!request.uri.toLowerCase().indexOf("/private/") !== -1) {
Or better yet, using more modern JS:
if (!request.uri.toLowerCase().includes("/private/")) {

Server returns 404 in POST request

I have a project I build with React and Strapi headless CMS (using nodejs). The backend, which is handled using Strapi, resides in port 443. When I send a GET request to any url in backend using http://site:443 I get a valid response, but a POST request always returns an error 404.
I've used an external tool https://reqbin.com/ to test it and got the same results.
The interesting thing is that even though I get 404 response, the route is being handled and executed, and yet gives a 404 response back.
Why could this be happening?
Thanks in advance
Here is the code upon reaching this route:
/**
* Create a/an orders record.
*
* #return {Object}
*/
create: async (ctx) => {
let params = ctx.request.body;
if (params.Sum) { // only if we got a good POST response
// check secret code
let a = params.UniqueID;
var res = a.substr(a.length - 12, a.length);
if (res !== 'secret') {
ctx.throw(500, 'SECURITY BLOCK', { expose: true });
return null;
}
var username = a.substr(0, a.length - 12);
// create validity
var date = new Date();
if (params.Sum === '250') { // one month membership
date.setTime( date.getTime() + 31 * 86400000 );
} else if (params.Sum === '390') { // one month membership
date.setTime( date.getTime() + 180 * 86400000 );
}
// create order in database
let today = new Date();
// extract username
var n = params.CustomerName.indexOf("_");
var name = params.CustomerName.substr(0, n);
var instId = params.CustomerName.substr(n+1, params.CustomerName.length);
const order = await strapi.services.orders.add({
userId: username,
username: name,
institutionId: instId,
sum: params.Sum,
transactionDate: today,
validity: date,
cardNum: params.CardNum,
cardName:params.CardName,
});
let axiosArr = {
institutions: [{_id: instId}],
validity: date,
secret: 'secret'
}
axios.put(apiUrl+'/users/'+username, axiosArr).then(() => {return order})
// return order
Most of the code is irrelevant to the question, I was thinking maybe the reason that the response is 404 is because I'm not returning the order object right away, but only after the axios.put?
Unfortunately I cannot test it right now
If the route is well executed but it returns a 404, it's because the response body is empty.
In your code your commented the return order.
Add in your code something like ctx.send({order}) or un comment your return
You will no longer have a 404.
I'm not familiar with strapi but it doesn't sound like an auth issue to me. Are you sure these routes are set up to accept POST requests and not just GET requests? It's common to return 404s if a user is missing a permission needed to access a resource, to not let the person know that resource exists but without any sample code I can't really say for sure what's happening in your case. Could you link more code?

Cannot read property 'getPayload' of undefined

I am implementing OAuth Google Sign in using backend (written in node.js, express framework at Heroku). Front end is Android and it sends the token id to the server just fine. And server receives the token id correctly.
Here is the code (which is ripped off straight from Google Documents)
var auth = new GoogleAuth;
var client = new auth.OAuth2(CLIENT_ID, '', '');
client.verifyIdToken(
token,
CLIENT_ID,
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3],
function(e, login) {
var payload = login.getPayload();
var userid = payload['sub'];
// If request specified a G Suite domain:
//var domain = payload['hd'];
});
But at times login in undefined. Its so strange that this problem occurs 1/10 rather than for every try so that I am not able to track the source of issue. For every other 9/10 it works just fine.
Any suggestions on how to solve this?
The problem in your code is that you are not checking if your callback get's any error.
The standard way in node.js to use a callback function is using two parameters - error is the first, the actual (success) returned data is the second, and the convention is that if an error exists - you should address it, and you're not gauranteed to get the data, and if everything went well- error will be null and you'll get your data.
So in your code, you are not checking that there's an error (and like you say, not always there's one).
Should be something like:
function(e, login) {
if (e) {
// handle error here
return; // don't continue, you don't have login
}
// if we got here, login is defined
var payload = login.getPayload();
var userid = payload['sub'];
// If request specified a G Suite domain:
//var domain = payload['hd'];
});
The first parameter to the callback function is an error that needs to handled.
function(error, login) {
if (error) return console.error(error)
var payload = login.getPayload();
var userid = payload['sub'];
// If request specified a G Suite domain:
//var domain = payload['hd'];
});

check on server side if youtube video exist

How to check if youtube video exists on node.js app server side:
var youtubeId = "adase268_";
// pseudo code
youtubeVideoExist = function (youtubeId){
return true; // if youtube video exists
}
You don't need to use the youtube API per-se, you can look for the thumbnail image:
Valid video = 200 - OK:
http://img.youtube.com/vi/gC4j-V585Ug/0.jpg
Invalid video = 404 - Not found:
http://img.youtube.com/vi/gC4j-V58xxx/0.jpg
I thought I could make this work from the browser since you can load images from a third-party site without security problems. But testing it, it's failing to report the 404 as an error, probably because the content body is still a valid image. Since you're using node, you should be able to look at the HTTP response code directly.
I can't think of an approach that doesn't involve making a separate HTTP request to the video link to see if it exists or not unless you know beforehand of a set of video IDs that are inactive,dead, or wrong.
Here's an example of something that might work for you. I can't readily tell if you're using this as a standalone script or as part of a web server. The example below assumes the latter, assuming you call a web server on /video?123videoId and have it respond or do something depending on whether or not the video with that ID exists. It uses Node's request library, which you can install with npm install request:
var request = require('request');
// Your route here. Example on what route may look like if called on /video?id=123videoId
app.get('/video', function(req, response, callback){
var videoId = 'adase268_'; // Could change to something like request.params['id']
request.get('https://www.youtube.com/watch?v='+videoId, function(error, response, body){
if(response.statusCode === 404){
// Video doesn't exist. Do what you need to do here.
}
else{
// Video exists.
// Can handle other HTTP response codes here if you like.
}
});
});
// You could refactor the above to take out the 'request.get()', wrap it in a function
// that takes a callback and re-use in multiple routes, depending on your problem.
#rodrigomartell is on the right track, in that your check function will need to make an HTTP call; however, just checking the youtube.com URL won't work in most cases. You'll get back a 404 if the videoID is a malformed ID (i.e. less than 11 characters or using characters not valid in their scheme), but if it's a properly formed videoID that just happens to not correspond to a video, you'll still get back a 200. It would be better to use an API request, like this (note that it might be easier to use the request-json library instead of just the request library):
request = require('request-json');
var client = request.newClient('https://www.googleapis.com/youtube/v3/');
youtubeVideoExist = function (youtubeId){
var apikey ='YOUR_API_KEY'; // register for a javascript API key at the Google Developer's Console ... https://console.developers.google.com/
client.get('videos/?part=id&id='+youtubeId+'&key='+apikey, function(err, res, body) {
if (body.items.length) {
return true; // if youtube video exists
}
else {
return false;
}
});
};
Using youtube-feeds module. Works fast (~200ms) and no need API_KEY
youtube = require("youtube-feeds");
existsFunc = function(youtubeId, callback) {
youtube.video(youtubeId, function(err, result) {
var exists;
exists = result.id === youtubeId;
console.log("youtubeId");
console.log(youtubeId);
console.log("exists");
console.log(exists);
callback (exists);
});
};
var notExistentYoutubeId = "y0srjasdkfjcKC4eY"
existsFunc (notExistentYoutubeId, console.log)
var existentYoutubeId = "y0srjcKC4eY"
existsFunc (existentYoutubeId, console.log)
output:
❯ node /pathToFileWithCodeAbove/FileWithCodeAbove.js
youtubeId
y0srjcKC4eY
exists
true
true
youtubeId
y0srjasdkfjcKC4eY
exists
false
false
All you need is to look for the thumbnail image. In NodeJS it would be something like
var http = require('http');
function isValidYoutubeID(youtubeID) {
var options = {
method: 'HEAD',
host: 'img.youtube.com',
path: '/vi/' + youtubeID + '/0.jpg'
};
var req = http.request(options, function(res) {
if (res.statusCode == 200){
console.log("Valid Youtube ID");
} else {
console.log("Invalid Youtube ID");
}
});
req.end();
}
API_KEY is not needed. It is quite fast because there is only header check for statusCode 200/404 and image is not loaded.

Resources