I'm using proxy.web to forward client requests.
When destination server is up, my code works as expected.
When destination server is down, ECONNREFUSED error is catch and printed to console.log. I would like to send that error back to the client, and tried using the sample provided here. Unfortunately, the error response does not arrive to the client (tried both chrome and firefox). Please find below code. Why does the response not sent to the client ?
var proxyServer = http.createServer(function(req, res) {
if(req.path === 'forbidden') {
return res.end('nope');
}
var url_parts = url.parse(req.url);
var extname = path.extname(url_parts.pathname);
if (extname || url_parts.pathname.length <= 1){
proxy.web(req, res, {
target: 'http://localhost:'+config.fileServer.port
});
}
else{
proxy.web(req, res, {
target: config.recognitionServer.url
}, function(e) {
console.log(e.message);
if (!res.headersSent) {
res.writeHead(500, { 'content-type': 'application/json' });
}
res.end(JSON.stringify({ error: 'proxy_error',
reason: e.message
}));
});
}
}).listen(config.proxyServer.port, function () {
console.log('Proxy server is listening on port '
+ config.proxyServer.port);
});
A good approach is this:
return res.status(500).send({
error: true,
message: 'your-error-message'
});
Your code rewritten:
proxy.web(req, res, {
target: config.recognitionServer.url
}, function (e) {
console.log(e.message);
return res.status(500).send({
error: true,
message: e.message
});
});
Problem was solved on client side :)
Client code is JS using XMLHttpRequest (tested on FF and Chrome). The error response arrives to "onload" event handler, not to "onerror".
The "onload" handler function needs to check response status. If error status (500), continue with error handler.
Related
I'm using a node.js backend and I got some problems with the error handling.
In the backend I use express for routing. I get a ajax post from the frontend with a array and some data in it. This data should be saved in the database. If there is an error by adding the data to the DB I get the error message in the backend but I also want so send a message to the frontend. I was try and erroring around but in the frontend I always get 'succesfull'.
This is my code till now.
Backend:
router.post('/tagging', function(req, res) {
var taggedData = req.body;
var actions = taggedData.map(element => {
addTaggedData.addTaggedData(element)
.then(function(result) {
return result;
})
.catch(function(err) {
if (err.code == "ER_NO_SUCH_TABLE") {
console.log("Tagged data contains unknown project name");
res.send("ER_NO_SUCH_TABLE");
} else {
res.send(err);
}
})
});
Promise.all(actions)
.then(
res.send("Successful")
)
.catch(function(err) {
if (err.code == "ER_NO_SUCH_TABLE") {
console.log("Tagged data contains unknown project name");
res.send("ER_NO_SUCH_TABLE");
} else {
res.send(err);
}
});
})
Frontend ajax call:
function postTaggedData(taggedData) {
$.ajax({
url: server_connection.url + '/tagging',
type: 'POST',
encoding: "UTF-8",
contentType: 'application/json',
data: JSON.stringify(taggedData),
success: function(data) {
if (data === "Successful") {
console.log("Tagged Data successfully send to server");
}else if(data == "ER_NO_SUCH_TABLE"){
alert("Unknown project");
} else {
alert(data);
}
},
error: function(xhr, status, error) {
if(error == "Internal Server Error"){
alert("There is an error with the server");
}else if(error == "ER_NO_SUCH_TABLE"){
alert("Unknown project");
}else{
alert("There was an error while sending the Tagged Data to the server");
console.log(xhr, "Status: ", status, error);
}
}
})
}
Even though you're sending error as a response, express doesn't know it's an error, so it sends it with status code 200 which means OK, so front-end thinks, response was successful.
Try setting the non-ok status and then sending an error like this: res.status(404).send(err). Where 404 is a status code for "Not Found"
You can find more about status codes here
You can find more about express error handling here
I am using npm request module to forward an incoming request to another server as:
app.get("/somepath", function(req, res) {
var url = proxySetting.global.url + req.url;
req.pipe(request(url)).pipe(res);
});
Here: proxySetting.global.url == http://localhost:4000
Now when i forward incoming request like this to target server, if the target server(localhost:4000) is down or the request is hanged on target server.
There will be an error like ECONNREFUSED or hangup error.
Tried catching these error using domain module like below
var d = domain.create();
d.on("error", function(err) {
console.log("Error occoured while forwarding request");
console.log(err);
res.status(500).send("Error occoured while forwarding request");
});
d.run(function() {
req.pipe(request(url)).pipe(res);
});
Tried to catch error event in several combination
var request = require("request");
module.exports = function(proxySetting) {
return function(req, res, next) {
var url = proxySetting.global.url + req.url;
url = url.replace(/([^:]\/)\/+/g, "$1") // replacing multiple slashes with one
console.log("Forwarding request to: " + url);
function errorHandler(err) {
console.log("Error occoured while forwarding request");
console.log(err);
res.status(500).send("Error occoured while forwarding request");
}
req.on("error", errorHandler);
res.on("error", errorHandler);
req.pipe(request(url).on("error",errorHandler)).pipe(res);
};
};
but still the exception is thrown to the process and server crashed
One way i am doing now is
process.on('uncaughtException', function(err) {
console.log("Some unhandled error occoured");
console.log(err);
console.log("Stopping server");
process.exit(1);
});
But i think catch uncaughtException and handle is not a proper solution
It seems, you are not acting on response object in "errorHandler", if you see your code block (given below), res is out of scope.
function errorHandler(err) {
console.log("Error occoured while forwarding request");
console.log(err);
res.status(500).send("Error occoured while forwarding request");
}
req.on("error", errorHandler);
res.on("error", errorHandler);
req.pipe(request(url).on("error", errorHandler)) .pipe(res);
But, if you act on response object in error handle, there will not be any uncaughtException.
I have created a solution, if proxy server is down, it will act on response object on error handle, thus it will not hit uncaughtException event.
Here is the solution.
var response ;
app.get('/', function(req, res){
var url = "http://localhost:3001" + req.url;
response = res; // I am setting res to global variable here
req.pipe(request(url).on("error",errorHandler)).pipe(res);
});
function errorHandler(err) {
response.status(500).send("Error occoured while forwarding request");
}
process.on('uncaughtException', function(err) {
console.log("Some unhandled error occoured");
console.log(err);
console.log("Stopping server");
process.exit(1);
});
Here uncaughtException event won't be invoked, I have checked in solution to a repo, you can give it a try. Repo location - here .
Use "Run1.sh" for successful case, "Run2.sh" for proxy server down case.
I'm facing a issue in a Node.JS application I'm writing.
I'm trying to get a JSON string as POST and saving that JSON to mongodb.
Inserts are working fine but I'm not able to handle error scenarios from snippet which is responsible for interacting with mongodb.
E.g. in the snippet below, I'm getting expected 404 response at "Error" comments #1, 3, 4 in server.js, when respective conditions are fulfilled, but 404 response at comment #2 is not coming.
However, I am getting errors printed in console from insertintomongo.js at comment #5 and 6 and that error object is also successfully sent back to server.js.
I'm not able to figure out why response is not set to 404 even when I know that code has gone into correct if condition (as shown by console output).
Snippet of JS handling requests (server.js)
var imongo=require("./insertintomongo.js");
http.createServer(function (request, response) {
if (uri == "/ssd/add" && request.method == 'POST') {
console.log(request.method + " to " + request.url);
var fullBody = '';
request.on('data', function(chunk) {
fullBody += chunk.toString();
});
request.on('end', function() {
try {
JSON.parse(fullBody);
} catch (e) {
// Error 1
console.log('Invalid JSON. Error message: ' +e);
response.writeHead(404, "Input data is not in JSON format" , {'Content-Type': 'text/html'});
response.end('<html><head><title>Input data is not in JSON format</title></head><body><h1>Input data is not in JSON format</h1></body></html>');
}
var inputJSON = JSON.parse(fullBody);
if ( inputJSON.deployment_id != undefined ){
inputJSON._id=inputJSON.deployment_id;
imongo(inputJSON,function(err){
if(err){
// Error 2
console.log('Error 2: ' + err);
response.writeHead(404, "Error saving input data" , {'Content-Type': 'text/html'});
response.end('Error inside callback');
} else {
console.log("All good");
};
});
} else {
// Error 3
console.log("Incorrect json provided");
response.writeHead(404, "Incorrect json provided", {'Content-Type': 'text/html'});
response.end('<html><head><title>Incorrect json provided</title></head><body><h1>Incorrect json provided.</h1><h3>"deployment_id" field is missing.</h3></body></html>');
};
// OK 1
response.writeHead(200, "OK", {'Content-Type': 'text/html'});
response.end();
});
} else {
// Error 4
response.writeHead(405, "Method not supported", {'Content-Type': 'text/html'});
response.end('<html><head><title>Method not supported</title></head><body><h1>Method not supported.</h1></body></html>');
}
}).listen(8081);
insertintomongo.js which is included in above JS
var mongodb = require('mongodb');
var MongoClient = mongodb.MongoClient;
var url = 'mongodb://localhost:27017/mytest';
function saveToMongo(input,cb){
MongoClient.connect(url, function (err, db) {
if (err) {
// Error 5
console.log('Unable to connect to the mongoDB server. Error:', err);
cb(err);
} else {
var collection = db.collection('users');
collection.insert([input], function (err, result) {
if (err) {
// Error 6
cb(err);
} else {
console.log('Inserted '+ result.result.ok+ ' documents into collection. The documents inserted with "_id" are:' + JSON.stringify(result.ops));
cb(null);
}
db.close();
});
}
});
};
module.exports=saveToMongo;
Here's Console output
Server runing at 8081
POST to /ssd/add
// This is coming from inserttomongo.js
Error 5: Unable to connect to the mongoDB server. Error: { [MongoError: connect ECONNREFUSED 127.0.0.1:27018]
name: 'MongoError',
message: 'connect ECONNREFUSED 127.0.0.1:27018' }
// This is coming from server.js
Error 2: MongoError: connect ECONNREFUSED 127.0.0.1:27018
OK, figured out the issue. I had to remove these lines from previous place and put it in else condition of "Error 2" comment.
// OK 1
response.writeHead(200, "OK", {'Content-Type': 'text/html'});
response.end();
This is because call to imongo is async and response was being set 200 even before the imongo was finished.
Very basic mistake :(
I'm new to Sails.js and I was trying to make a filter to authorize using a Bearer token which come from a higher server, a gatekeeper which is responsable to do the OAuth2 authentication from GitHub API. The services streams works well. I'm already aware of Passport.js but I'm trying to implement this on my own. I came with a policy which looks like:
module.exports = function (req, res, next) {
var httpsExec = require('https');
if (req.headers.authorization) {
var parts = req.headers.authorization.split(' ');
if (parts.length == 2) {
var tokenType = parts[0]
, credentials = parts[1];
if (/^Bearer$/i.test(tokenType) || /^bearer$/i.test(tokenType)) {
httpsExec.request({
host: 'api.github.com',
post: 443,
path: '/user',
method: 'GET',
headers: {'Authorization': 'token ' + credentials, 'User-Agent': 'curly'}
}, function (response) {
var responseData = '';
response.setEncoding('utf8');
response.on('data', function (chunk) {
responseData += chunk;
});
response.once('error', function (err) {
next(err);
});
response.on('end', function () {
try {
req.options.user = JSON.parse(responseData);
next();
} catch (e) {
res.send(401, {error: e});
}
});
}).end();
} else {
console.err("The token is not a Bearer");
res.send(401)
}
}
} else {
res.send(401, {error: "Full authentication is necessary to access this resource"})
}
};
The policy is called once I hit the controller route but it throws a _http_outgoing.js:335
throw new Error('Can\'t set headers after they are sent.');
^
Error: Can't set headers after they are sent.
And the process is terminate.
The problem I think is the next() and the returns I tried everywhere I think, to put the next() call, but still gives me this error, if I remove then I lock the request on the policy.
EDIT
I did a simple sample of policy where I just set some property on req.options.values and happened the same problem, so maybe could be an issue with req.options.requestData = JSON.parse(responseData); ? How else could I set a property to send to controller ?
response.once('error', function (err) {
next(err);
});
response.on('end', function () {
try {
req.options.user = JSON.parse(responseData);
next();
} catch (e) {
res.send(401, {error: e});
}
});
both are getting executed.to check console.log("something") in error to see if there is error.
This happens when you're trying to modify the request and response together or modify one of them twice.
In your code, I think the callback is being called twice and you are also modifying the response at the same time. Check the lines where you're calling callback "next()". You'll find your issue.
I'm trying to create a proxy with node-http-proxy in Node.js that checks whether a request is authorized in a mongodb.
Basically, I created a middleware module for the node-http-proxy that I use like this:
httpProxy.createServer(
require('./example-middleware')(),
9005, 'localhost'
).listen(8005)
What the middleware module does is using mongojs to connect to mongodb and run a query to see if the user is authorized to access the resource:
module.exports = function(){
// Do something when first loaded!
console.log("Middleware loaded!");
return function (req, res, next) {
var record = extractCredentials(req);
var query = -- Db query --
//Debug:
log("Query result", query);
db.authList.find(query).sort({
"url.len": -1
}, function(err, docs){
console.log(docs);
// Return the creator for the longest matching path:
if(docs.length > 0) {
console.log("User confirmed!");
next();
} else {
console.log("User not confirmed!");
res.writeHead(403, {
'Content-Type': 'text/plain'
});
res.write('You are not allowed to access this resource...');
res.end();
}
});
}
}
Now the problem is that as soon as I add the asynchronous call to mongodb using mongojs the proxy hangs and never send the response back.
To clarify: on a "User not confirmed" everything works fine and the 403 is returned. On a "user confirmed" however I see the log but the browser then hangs forever and the request isn't proxied.
Now, if I remove the "user confirmed" and next() part outside of a callback it does work:
module.exports = function(){
// Do something when first loaded!
console.log("Middleware loaded!");
return function (req, res, next) {
var record = extractCredentials(req);
var query = --- query ---
console.log("User confirmed!");
next();
}
but I can't do that since the mongojs query is meant (rightfully I guess) to be executed asynchronously, the callback being triggered only when the db replied...
I also tried the version without using a middleware:
http.createServer(function (req, res) {
// run the async query here!
proxy.proxyRequest(req, res, {
host: 'localhost',
port: 9000
});
}).listen(8001);
But that did not help either...
Any clue? Note that I'm new to node.js so I suspect a misunderstanding on my side...
Found the answer, actually the catch is that the request needs to be buffered:
httpProxy.createServer(function (req, res, proxy) {
// ignore favicon
if (req.url === '/favicon.ico') {
res.writeHead(200, {
'Content-Type': 'image/x-icon'
} );
res.end();
console.log('favicon requested');
return;
}
var credentials = extractCredentials(req);
console.log(credentials);
var buffer = httpProxy.buffer(req);
checkRequest(credentials, function(user){
if(user == ...) {
console.log("Access granted!");
proxy.proxyRequest(req, res, {
host: 'localhost',
port: 9005,
buffer: buffer
});
} else {
console.log("Access denied!");
res.writeHead(403, {
"Content-Type": "text/plain"
});
res.write("You are not allowed to access this resource...");
res.end();
}
});
}).listen(8005);
Two problems:
You're not calling next(); in the else case of your sort callback.
The second parameter to your sort callback is a Cursor, not an array of documents. As such, docs.length > 0 is never true and the code always follows the else path.