I want to customise the response sent to the users when a timeout error is fired. More specifically, I want to redirect them to a static page explaining why a timeout error has been fired.
I want to write something like :
var express = require('express')
var timeout = require('connect-timeout')
var app = express();
var port = process.env.PORT || 8080;
app.use(timeout(10,{"respond":true}));
app.use(haltOnTimedout);
// GET ROUTES HERE
app.listen(port, function() {
console.log('Our app is running on port '+ port);
});
function haltOnTimedout(req,res,next) {
if (req.timedout) {
res.redirect('/timedout.html');
} else {
next();
}
};
The code above is not working as intended : even if a timeout is fired, it does not redirect the user to the static webpage timedout.html, instead it throws the error ServiceUnavailableError: Response timeout
Is it possible to do something like this or am I missing something ?
I finally find the answer and post it here if someone get the same question.
I placed the following line of code at the very end of my server.js code :
app.use(haltOnTimedout);
function haltOnTimedout(err,req,res,next) {
if (req.timedout === true) {
if (res.headersSent) {
next(err);
} else {
res.redirect('/timedout.html');
}
} else {
next();
}
};
and add if (!req.timedout) { res.send(something);} in all routes in order to avoid double send of the headers.
Related
When I refresh the page, the page crashes every time, I have tried replacing 'end' with 'write', have I just not learnt enough code yet to prevent this? It seems like in the tutorial I am following that this should be working whenever I refresh. Initially starting the webpage is no problem though
const http = require('http');
//connection settings
//port is a end point of communication
const port = 3000;
// hostname: IP associated with any deivce on a network
const hostname = '127.0.0.1';
const respond = (request, response) => {
//response.setHeader('Content-Type', 'text/plain');
//writeHead (stațus code, {headers})
//response.writeHead(200, { 'Content-Type': 'text/html' })
if (request.url === '/about') {
response.end("about")
}
if (request.url === '/') {
response.end("home page");
}
response.end("error page")
/*
socket.on('error', function(e){
console.log(e);
});
was searching up a solution but this^ did not work
*/
};
const url = require('url');
const server = http.createServer(respond);
server.listen(port, hostname, () => { console.log('Server listening on port 3000') })
enter image description here
It's the response.end("error page"). That's running unconditionally on every request, even when one of your if statements is true.
Perhaps you meant to do an if...else if...else? This would fix your problem.
if (request.url === '/about') {
response.end('about');
} else if (request.url === '/') {
response.end('home page');
} else {
response.end('error page');
}
I'm developing a node.js RESTful server for home use on a RPI with Raspbian, and for testing, I've created a test HTML page that makes various XMLHttpRequests to it.
When developing I'm using a test nodemon server on my dev machine (a desktop machine, not the RPI), running on localhost:4000, and the test HTML page points to it.
Whenever I'm ready to commit the changes, I push them to the server (the RPI), even the test page. It should connect to the server on localhost.
Something bizzare happens whenever I'm testing the page on the server: localhost is not recognized in the XMLHttpRequest.open method, but if I put the address of the server machine in the network (not 127.0.0.1, but 192.168.1.X for example), it works.
The Command netstat -vltn shows that the node server is listening on port 4000, I've enabled CORS, I've already tried to write 127.0.0.1 instead of localhost, and I've even modified the app.listen function to listen to 0.0.0.0, like this:
app.listen(port, '0.0.0.0', function () {
console.log('RESTful API server started on: ' + port);
});
but still, every request from the test page hosted on the server, to localhost:4000 doesn't work.
My problem here is that, if I need to push the test page on the server, I need to manually change the IP address for the XMLHttpRequest each time, instead of just keeping localhost. Is there a way to enable the use of localhost?
EDIT: I'm adding some client code to flesh out the problem.
testpage.html (the one that should work both on the dev machine and the RPI)
<html>
<head>
<script type="text/javascript">
function sendData() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
// Typical action to be performed when the document is ready:
document.getElementById("demo").innerHTML = xhttp.responseText;
}
};
xhttp.onerror = function(e) {
console.log('error', e);
};
xhttp.open("POST", "http://127.0.0.1:4000/auth/loginTest", true);
xhttp.setRequestHeader("Content-Type", "application/json");
//I've omitted the part where I'm prepping a json with username/password data
xhttp.send(jsonString);
}
</script>
</head> <!--I'm skipping the rest of the code as there's only a div that
catches the json info sent by the server -->
server.js (the one that gets started on the RPI with node server.js)
var express = require('express');
var cors = require('cors');
var bodyParser = require('body-parser');
var port = process.env.PORT || 4000;
var auth = require(/*path to Auth middleware*/);
var app = express();
app.use(bodyParser.json({ type: 'application/json' }));
app.options('*', cors());
app.use(cors());
app.use('/auth', auth);
process
.on('unhandledRejection', (reason, p) => {
console.error(reason, 'Unhandled Rejection at Promise', p);
})
.on('uncaughtException', err => {
console.error(err, 'Uncaught Exception thrown');
process.exit(1);
});
app.listen(port, function () {
console.log('RESTful API server started on: ' + port);
});
Try changing this code:
app.listen(port, '0.0.0.0', function () {
console.log('RESTful API server started on: ' + port);
});
to this:
app.listen(port, function () {
console.log('RESTful API server started on: ' + port);
});
This will allow your app to listen on both IPv4 and IPv6. It is possible that localhost is resolving to the IPv6 address and your code is only listening on the IPv4 address. I know that a MAC uses IPv6 for localhost.
The other thing to try is stop using the word localhost on the client and use 127.0.0.1 and see if that makes any difference.
UPDATE:
Below is my server code that I generated from yours and it seems to work:
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const port = process.env.PORT || 4000;
function sendPage(req, res, next) {
console.log('sending page');
res.send(`<html>
<head>
<script>
function sendData() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
// Typical action to be performed when the document is ready:
document.getElementById("demo").innerHTML = xhttp.responseText;
}
};
xhttp.onerror = function(e) {
console.log('error', e);
};
xhttp.open("POST", "http://127.0.0.1:4000/auth/loginTest", true);
xhttp.setRequestHeader("Content-Type", "application/json");
//I've omitted the part where I'm prepping a json with username/password data
xhttp.send('{"user":"dog","pw":"woof"}');
}
</script>
</head>
<body onload="sendData()">
<h1>Test page</h1>
<div id="demo"></div>
<hr/>
</body>
</html>`);
}
function auth() {
console.log('auth called');
var router = express.Router();
router.post('/loginTest', (req, res, next) => {
console.log('auth was called');
console.log(req.body);
res.json({error: false, data:'hi'});
});
return router;
}
const app = express();
app.use(bodyParser.json({ type: 'application/json' }));
app.options('*', cors());
app.use(cors());
app.get('/', sendPage);
app.use('/auth', auth());
process
.on('unhandledRejection', (reason, p) => {
console.error(reason, 'Unhandled Rejection at Promise', p);
})
.on('uncaughtException', err => {
console.error(err, 'Uncaught Exception thrown');
process.exit(1);
});
app.listen(port, function () {
console.log('RESTful API server started on: ' + port);
});
If this doesn't come close to matching your code let me know where I got it wrong.
Sorry for the delay. I forgot to post the solution.
My approach will never work, because when the test page is loaded, it will try to execute a script on http://localhost:4000, which is fine if the page is loaded from the machine where the server is listening, but obviously won't work if it's on another machine (hence the ERR_CONNECTION_REFUSED error).
If I load this page from the server machine it will try to execute the script on my machine, which is an invalid request.
So I've solved it by simply substitute http://localhost:4000 in the request with the actual IP of the machine, e.g http://Write.Real.Address.Here:4000.
I'm developing a REST API server using express.js but I would like to allow the developer to issue command trough the command line.
It is a development server that read a config.json, create endpoint and store data in-memory.
I would like to give the developer the ability to reload the base data without restarting the express server.
Any idea?
I just thought of what seems to me like a more elegant solution to your problem that you may be interested in: having the server automatically reload the config.json file whenever it changes. This is trivial to implement in Node.js:
const fs = require("fs"),
path = require("path");
var filePath = path.join(__dirname, "config.json");
fs.watch(filePath, function(event) {
if (event === "change") {
var file = require(filePath); // require loads JSON as a JS object
// do something with the newly loaded file here
console.log("Detected change, config file automatically reloaded.");
}
});
Perhaps a better option would be to add a route to the app like so (since the server will be blocking, and therefore not allow any command-line input on the same process):
"use strict";
const http = require("http"),
path = require("path"),
express = require("express"),
PORT = 8000;
var app = express();
function loadConfigFile(callback) {
var configFile = false;
try { // load config file if it exists
configFile = require(path.join(__dirname, "config.json"));
} catch(e) { // ignore if the config file doesn't exist
if (e.code !== "MODULE_NOT_FOUND") {
throw e
}
}
callback(configFile); // argument will be false if file does not exist
}
function authenticateAdminUser(req) { return true; } // dummy logic
app.get("/admin/reload", function(req, res, next) {
if (!authenticateAdminUser(req)) {
let err = new Error("Forbidden");
err.StatusCode = 403;
return next(err);
}
loadConfigFile(function(file) {
if (file) {
// do something with the file here
}
res.send("Reloaded config file successfully.");
});
});
loadConfigFile(function(file) {
if (file) {
// do something with the file here
}
app.listen(PORT, function() {
console.log("Server started on port " + PORT + ".");
});
});
I am trying to get my MEAN application ready for production. The application was built on the Mean.js boilerplate. From my understanding, MEAN.js uses Forever.js to restart the application after an error (although documentation on preparing Mean.js for production is severely lacking); however, it appears the suggested way to handle the application crashing is using Node's Domains in conjunction with clusters. Here are a few references:
This is from Node's webpage on the deprecated uncaughtException Event:
Note that uncaughtException is a very crude mechanism for exception handling.
Don't use it, use domains instead.
Node.js domains : https://nodejs.org/api/domain.html
\http://shapeshed.com/uncaught-exceptions-in-node/
etc.
Although I have found many suggestions for using domains, I have yet to find one that tells me what needs to be done to incorporate domains in an application, especially one that has already been developed.
The Questions
What do I need to do to integrate node domains into a Mean.js application? From what I have gathered (from the Node.js domains webpage and here), you would go into server.js in the root of the Mean.js project and do something similar to this:
var cluster = require('cluster');
var PORT = +process.env.PORT || 1337;
if (cluster.isMaster) {
//Fork the master as many times as required.
cluster.fork();
cluster.fork();
cluster.on('disconnect', function(worker) {
console.error('disconnect!');
cluster.fork();
});
} else {
var domain = require('domain');
var d = domain.create();
d.on('error', function(er) {
console.error('error', er.stack);
try {
// make sure we close down within 30 seconds
var killtimer = setTimeout(function() {
process.exit(1);
}, 30000);
// But don't keep the process open just for that!
killtimer.unref();
// stop taking new requests.
server.close();
// Let the master know we're dead. This will trigger a
// 'disconnect' in the cluster master, and then it will fork
// a new worker.
cluster.worker.disconnect();
// try to send an error to the request that triggered the problem
res.statusCode = 500;
res.setHeader('content-type', 'text/plain');
res.end('Oops, there was a problem!\n');
} catch (er2) {
// oh well, not much we can do at this point.
console.error('Error sending 500!', er2.stack);
}
});
d.run(function() {
//Place the current contents of server.js here.
});
}
Do I need to wrap all of the backend controllers in domain.run()?
This answer was found by experimenting and a lot more digging. I had to edit both server.js and config/express.js to use domains. The domain is added part of the Express middleware for each request. Do not use the code in the question, it won't work as is.
First, the changes I made to server.js:
var init = require('./config/init')(),
config = require('./config/config'),
mongoose = require('mongoose'),
cluster = require('cluster');
var processes = 4; //Number of processes to run at the same time.
if(cluster.isMaster) {
for(var i = 0; i < processes; i++) {
cluster.fork();
}
cluster.on('disconnect', function(worker) {
console.error("Disconnect!");
cluster.fork();
});
} else {
/**
* Main application entry file.
* Please note that the order of loading is important.
*/
// Bootstrap db connection
var db = mongoose.connect(config.db, function(err) {
if (err) {
console.error('\x1b[31m', 'Could not connect to MongoDB!');
console.log(err);
}
});
// Init the express application
var expressConfig = require('./config/express');
var app = expressConfig.initialize(db);
app.use(function(err, req, res, next) {
console.error(err);
res.send(401).json({your_message_buddy: "Nice try, idiot."});
});
// Bootstrap passport config
require('./config/passport')();
// Start the app by listening on <port>
expressConfig.setServer(app.listen(config.port));
// Expose app
exports = module.exports = app;
// Logging initialization
console.log('MEAN.JS application started on port ' + config.port);
}
The necessary changes for config/express.js:
var domain = require('domain'),
cluster = require('cluster');
var appServer = null;
module.exports = {};
/**
* Since we begin listening for requests in server.js, we need a way to
* access the server returned from app.listen() if we want to close the
* server after an error. To accomplish this, I added this function to
* pass the server object after we begin listening.
*/
module.exports.setServer = function(server) {
appServer = server;
};
module.exports.initialize = function(db) {
//Initialize express app
var app = express();
//Globbing model files
config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) {
require(path.resolve(modelPath));
});
//Set up domain for request BEFORE setting up any other middleware.
app.use(function(req, res, next) {
//Create domain for this request
var reqdomain = domain.create();
reqdomain.on('error', function(err) {
console.error('Error: ', err.stack);
try {
//Shut down the process within 30 seconds to avoid errors.
var killtimer = setTimeout(function() {
console.error("Failsafe shutdown.");
process.exit(1);
}, 30000);
//No need to let the process live just for the timer.
killtimer.unref();
//No more requests should be allowed for this process.
appServer.close();
//Tell master we have died so he can get another worker started.
if(cluster.worker) {
cluster.worker.disconnect();
}
//Send an error to the request that caused this failure.
res.statusCode = 500;
res.setHeader('Content-Type', 'text/plain');
res.end('Oops, there was a problem. How embarrassing.');
} catch(err2) {
//Well, something is pretty screwed up. Not much we can do now.
console.error('Error sending 500!\nError2: ', err2.stack);
}
});
//Add request and response objects to domain.
reqdomain.add(req);
reqdomain.add(res);
//Execute the rest of the request chain in the domain.
reqdomain.run(next);
});
//The rest of this function, which used to be module.exports, is the same.
};
I have this basic express app:
var express = require('express');
var app = express();
var PORT = 3000;
var through = require('through');
function write(buf) {
console.log('writing...');
this.queue('okkkk');
}
function end() {
this.queue(null);
}
var str = through(write, end);
/* routes */
app.get('/', function(req, res){
res.send("Hello!");
})
app.post('/stream', function(req, res){
var s = req.pipe(str).pipe(res);
s.on('finish', function() {
console.log('all writes are now complete.'); // printed the first time
});
});
/* listen */
app.listen(PORT, function () {
console.log('listening on port ' + PORT + '...');
});
When I post some data to /stream endpoint for the first time after starting the server I get okkk as the response which is what I expect. However, after that, any requests to /stream endpoint just timeout and not return any response.
Why is it so? What's exactly happening here?
I had this same problem and looks like res was not being finished properly. So I added a callback to my stream and ended que res myself. That fixed my problem:
stream.on('end', () => res.end());
stream.pipe(res);
It worked when I replaced req.pipe(str).pipe(res) with req.pipe(through(write, end)).pipe(res) which essentially makes sure that a new instance of through stream is created for every request.