How do I get a node.js server to redirect users to a 404.html page when they enter an invalid url?
I did some searching, and it looks like most results are for Express, but I want to write my server in pure node.js.
The logic of determining a "wrong" url is specific to your application. It could be a simple file not found error or something else if you are doing a RESTful app. Once you've figured that out, sending a redirect is as simple as:
response.writeHead(302, {
'Location': 'your/404/path.html'
//add other headers here...
});
response.end();
If you are using ExpressJS, it's possible to use:
res.redirect('your/404/path.html');
To indicate a missing file/resource and serve a 404 page, you need not redirect. In the same request you must generate the response with the status code set to 404 and the content of your 404 HTML page as response body. Here is the sample code to demonstrate this in Node.js.
var http = require('http'),
fs = require('fs'),
util = require('util'),
url = require('url');
var server = http.createServer(function(req, res) {
if(url.parse(req.url).pathname == '/') {
res.writeHead(200, {'content-type': 'text/html'});
var rs = fs.createReadStream('index.html');
util.pump(rs, res);
} else {
res.writeHead(404, {'content-type': 'text/html'});
var rs = fs.createReadStream('404.html');
util.pump(rs, res);
}
});
server.listen(8080);
404 with Content/Body
res.writeHead(404, {'Content-Type': 'text/plain'}); // <- redirect
res.write("Looked everywhere, but couldn't find that page at all!\n"); // <- content!
res.end(); // that's all!
Redirect to Https
res.writeHead(302, {'Location': 'https://example.com' + req.url});
res.end();
Just consider where you use this (e.g. only for http request), so you don't get endless redirects ;-)
Try this:
this.statusCode = 302;
this.setHeader('Location', '/url/to/redirect');
this.end();
I used a switch statement, with the default as a 404:
var fs = require("fs");
var http = require("http");
function send404Response (response){
response.writeHead(404, {"Content-Type": "text/html"});
fs.createReadStream("./path/to/404.html").pipe(response);
}
function onRequest (request, response){
switch (request.url){
case "/page1":
//statements
break;
case "/page2":
//statements
break;
default:
//if no 'match' is found
send404Response(response);
break;
}
}
http.createServer(onRequest).listen(8080);
You have to use the following code:
response.writeHead(302 , {
'Location' : '/view/index.html' // This is your url which you want
});
response.end();
Use the following code this works fine in Native Nodejs
http.createServer(function (req, res) {
var q = url.parse(req.url, true);
if (q.pathname === '/') {
//Home page code
} else if (q.pathname === '/redirect-to-google') {
res.writeHead(301, { "Location": "http://google.com/" });
return res.end();
} else if (q.pathname === '/redirect-to-interal-page') {
res.writeHead(301, { "Location": "/path/within/site" });
return res.end();
} else {
//404 page code
}
res.end();
}).listen(8080);
use
res.redirect('/path/404.html');
or you can redirect to any defined URI as
res.redirect('/');
Related
I am not able to use more HTML files. I want to add 2-3 HTML links in my index.html, how can I do it? Below is my code where I used 2 html pagelinks, one css and one js.
This is test.js file:
var http = require('http'),
fs = require('fs');
http.createServer(function (req, res) {
if(req.url.indexOf('.html') != -1){ //req.url has the pathname, check if it conatins '.html'
fs.readFile(__dirname + '/public/index.html', function (err, data) {
if (err) console.log(err);
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
res.end();
});
}
if(req.url.indexOf('.js') != -1){ //req.url has the pathname, check if it conatins '.js'
fs.readFile(__dirname + '/public/js/bootstrap.min.js', function (err, data) {
if (err) console.log(err);
res.writeHead(200, {'Content-Type': 'text/javascript'});
res.write(data);
res.end();
});
}
if(req.url.indexOf('.css') != -1){ //req.url has the pathname, check if it conatins '.css'
fs.readFile(__dirname + '/public/css/bootstrap.min.css', function (err, data) {
if (err) console.log(err);
res.writeHead(200, {'Content-Type': 'text/css'});
res.write(data);
res.end();
});
}
if(req.url.indexOf('.html') != -1){ //req.url has the pathname, check if it conatins '.js'
fs.readFile(__dirname + '/public/hello.html', function (err, data) {
if (err) console.log(err);
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
res.end();
});
}
}).listen(3000, '127.0.0.1');
console.log('Server running at http://127.0.0.1:3000/');
I got this error in cmd while loading server:
D:\Nodejs>node test.js
Server running at http://127.0.0.1:3000/
events.js:160
throw er; // Unhandled 'error' event
^
Error: write after end
at ServerResponse.OutgoingMessage.write (_http_outgoing.js:441:15)
at D:\Nodejs\test.js:45:13
at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:446:3)
It seems that you are trying to write your own static file server in Node. There are easy solution for that, like the express.static middleware in the Express framework, where all you do is something like this:
Ready solution
var path = require('path');
var express = require('express');
var app = express();
var dir = path.join(__dirname, 'public');
app.use(express.static(dir));
app.listen(3000, () => console.log('Listening on http://localhost:3000/'));
(this is the entire program).
Reinventing the wheel
Now, if you insist of reimplementing all of that yourself then read on.
One of the bugs in your code is checking for the extensions when you in fact check for having strings like .html anywhere in the code. E.g. this:
if(req.url.indexOf('.html') != -1)
will match files like /my.html.styles/style.css which it shouldn't.
If you want to check the file extension then use:
path.extname(file);
See: https://nodejs.org/api/path.html
Also it doesn't terminate when it finds a match because you're using plain if and not else if blocks and you don't use early return in your code.
Another problem is that you have hardcoded all of the paths to files and with that code you will not be able to serve just any HTML or CSS code. Also your code will not serve images correctly, or multiple styles etc. It would actually be easier to rewrite from scratch that try to fix it.
Good examples
See this answer for examples of how to properly serve static images with Express andexpress.static, Express without express.static, connect, http module (like you do here) and net module with raw TCP connections:
How to serve an image using nodejs
Here is a full example of a file server using only http i.e. what you're trying to do here:
var path = require('path');
var http = require('http');
var fs = require('fs');
var dir = path.join(__dirname, 'public');
var mime = {
html: 'text/html',
txt: 'text/plain',
css: 'text/css',
gif: 'image/gif',
jpg: 'image/jpeg',
png: 'image/png',
svg: 'image/svg+xml',
js: 'application/javascript'
};
var server = http.createServer((req, res) => {
var reqpath = req.url.toString().split('?')[0];
if (req.method !== 'GET') {
res.statusCode = 501;
res.setHeader('Content-Type', 'text/plain');
return res.end('Method not implemented');
}
var file = path.join(dir, reqpath.replace(/\/$/, '/index.html'));
if (file.indexOf(dir + path.sep) !== 0) {
res.statusCode = 403;
res.setHeader('Content-Type', 'text/plain');
return res.end('Forbidden');
}
var type = mime[path.extname(file).slice(1)] || 'text/plain';
var s = fs.createReadStream(file);
s.on('open', () => {
res.setHeader('Content-Type', type);
s.pipe(res);
});
s.on('error', () => {
res.setHeader('Content-Type', 'text/plain');
res.statusCode = 404;
res.end('Not found');
});
});
server.listen(3000, () => console.log('Listening on http://localhost:3000/'));
This code is much more complicated and doesn't even serve properly files with other MIME types than the ones explicitly supported, which only demonstrates that it's much better to use module that does all of that properly.
So preferably use the right tool for the job, like express.static or any other well tested Node static file server and you will avoid a lot of the problems.
For more info see:
How to serve an image using nodejs
This is caused by your if statement. You should be using something like this:
if(req.url == '/index.html'){
// load index.html
}else if(req.url == '/hello.html'){
// load hello.html
}else if ... // the rest for css and js
Some explanations
When you check just the indexOf of the request url, you won't be able to differ index.html from hello.html (both have .html), so what you want to check is the file itself.
For that error, since any .html in the url matches both IFs, and you are not using if..else, it will enter both, calling end in the request in one IF, and trying to write after end in the other.
Side note
This should be used only for learning purposes. There is no need to use this method once you get a grasp on how it works and what's happening, because, when you do, just go with well-known tools that do the job in much simpler ways.
I am trying to create a module which can log some certain params for the request and print them to the page which can be checked online, the page will use the socket.io to load the latest logs.
And I want this module can worked as a plugin which means you just call this module, and initialize it, then an extra entry point /_logger will be added to you application, once you visit the page, the latest logs will be updated in real-time. So the module have to intercept the requests:
function setup(httpServer) {
//page
httpServer.on("request", function (request, response) {
var pathname = url.parse(request.url).pathname;
if (pathname === '/_logger') {
fs.readFile(__dirname + '/logger.html', (err, data) => {
response.writeHead(200, { 'Content-Type': 'text/html' });
response.write(data);
response.end();
});
}else{
// how to give up the control for this requset
}
});
var io = require('socket.io')(httpServer);
io.on('connection', function (socket) {
//TO BE DONE
socket.on('event', function (data) { });
socket.on('disconnect', function () { });
});
}
module.exports = {
setup: setup
}
Usage:
var logger= require("./logger/index");
var server = require('http').createServer();
logger.setup(server);
server.on("request", function(req,res){
//Normal logic for different application
});
server.listen(3333);
Now the problem is that once the requested url is not /_logger, I should release the control of this request.
if (pathname === '/_logger') {
//take control
}else{
// Nothing should be done here, it should go to the next request chain.
}
After read the documents, I can not find the right way to make it.
Any ideas?
Assuming that you want to use low-level NodeJS HTTP API. You can compose several handlers into one handler using function composition. Each handler should yield the execution to the next handler, if the req.url doesn't matches.
var http = require('http');
var handler1 = function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.write('/');
res.end();
}
var handler2 = function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.write('/Hello');
res.end();
}
var middleware = compose([wrapHandler('/', handler1),
wrapHandler('/hello', handler2)]);
http.createServer(middleware).listen(3000);
function wrapHandler(path, cb) {
return function (req, res, next) {
if (req.url === path) {
cb(req, res);
} else {
next();
}
};
}
function notFoundHandler(req, res) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.write('No Path found');
res.end();
};
// adapted from koa-compose
function compose(middleware) {
return function (req, res){
let next = function () {
notFoundHandler.call(this, req, res);
};
let i = middleware.length;
while (i--) {
let thisMiddleware = middleware[i];
let nextMiddleware = next;
next = function () {
thisMiddleware.call(this, req, res, nextMiddleware);
}
}
return next();
}
}
In your case, you can write.
var loggerHandler = wrapHandler('/_logger', logger.handler);
httpServer.on('request', compose(loggerHandler, handler2, handler3));
httpServer.on("request", ...) is just one request listener. It is under no obligation to process the request if it doesn't need to. Even if it does nothing, any other request listeners will still get notified of this request.
If there are other request listeners (which you are implying that there are), then you can just do nothing in the request listener you show and the other listeners will also get a shot at the particular request. This allows you to add your own request listener to a working http server and your listener only has to pay attention to the new route that it wants to support and can just ignore all the other routes and they will get handled by the other listeners that are already in place.
Now, there are frameworks built to make this both simpler and to give you more control. In general, these frameworks use one listener and they provide a means for you to handle the request OR explicitly tell the framework that you have not handled the request and would like other route handlers to have a shot at handling the request. This is a bit more flexible than just have multiple listeners, all of which will get notified of the same route.
For example, using the Express framework, you can do this:
var express = require('express');
var app = express();
// route handler for / request only when a user=xxx is in the query string
app.get('/', function(req, res, next) {
// if user was included in query parameter
if (req.query.user) {
// do something specific when ?user=xxxx is included in the URL
} else {
// pass handling to the next request handler in the chain
next();
}
});
// route handler for / request that wasn't already handled
app.get('/', function(req, res) {
// handle the / route here
});
app.listen(80);
I am using Meteor as the backend to my ionic+angular webApp. I'm deploying the app using meteor-up. I have put my entire app in the Meteor /public folder and it works find when I access it like this:
http://localhost:3000/index.html
How can I set/rewrite/redirect the Meteor default page so I can load the same page from:
http://localhost:3000/ or http://localhost:3000/myApp
without losing my Meteor server
Here is the complete solution:
fs = Npm.require('fs');
crypto = Npm.require('crypto');
WebApp.connectHandlers.use("/", function(req, res, next) {
var data, filepath;
if (req.method !== 'GET') {
return next();
}
filepath = process.env.PWD + '/public/index.html';
// serve default file, with eTag,
// i.e. http://localhost:3000/
fs.readFile(filepath, function(err, buf) {
var eTag;
eTag = crypto.createHash('md5').update(buf).digest('hex');
if (req.headers['if-none-match'] === eTag) {
res.writeHead(304, 'Not Modified');
return res.end();
}
res.writeHead(200, {
'ETag': eTag,
'Content-Type': 'text/html'
});
return res.end(buf);
});
return;
// serve default file, without eTag or caching headers
// i.e. http://localhost:3000/
data = fs.readFileSync(filepath);
res.writeHead(200, {
'Content-Type': 'text/html'
});
res.write(data);
return res.end();
// redirect to default file
// i.e. http://localhost:3000/index.html
res.writeHead(301, {
'Location': '/index.html'
});
return res.end();
});
I wrote a script in node.js (base on web beacon pixel), that need to set cookie and send a pixel back to the client.
My problem is that the cookie is not setting , if i remove from the code the part that send the GIF its working, but i cant find a way to make this 2 thing together set a cookie and send the GIF back.
http = require('http');
url = require('url');
http.createServer(function(req, res){
var requestURL = url.parse(req.url, true);
if (requestURL.pathname == '/log.gif') {
// Write a Cookie
res.writeHead(200, {
'Set-Cookie' : 'id='+requestURL.query.id+'; expires=' + new Date(new Date().getTime()+86409000).toUTCString()
});
var imgHex = '47494638396101000100800000dbdfef00000021f90401000000002c00000000010001000002024401003b';
var imgBinary = new Buffer(imgHex, 'hex');
res.writeHead(200, {'Content-Type': 'image/gif' });
res.end(imgBinary, 'binary');
} else {
res.writeHead(200, {'Content-Type': 'text/plain' });
res.end('');
}
}).listen(8080);
http://nodejs.org/api/http.html#http_response_writehead_statuscode_reasonphrase_headers:
“This method must only be called once on a message”
Just add the Set-Cookie header in your res.WriteHead call that sets the Content-type:
res.writeHead(200, {
'Content-Type': 'image/gif',
'Set-Cookie' : '…'
});
Im trying to learn node.js and have hit a bit of a roadblock.
My issue is that i couldn't seem to load an external css and js file into a html file.
GET http://localhost:8080/css/style.css 404 (Not Found)
GET http://localhost:8080/js/script.css 404 (Not Found)
(this was when all files were in the root of the app)
I was told to somewhat mimic the following app structure, add a route for the public dir to allow the webserver to serve the external files.
my app structure is like so
domain.com
app/
webserver.js
public/
chatclient.html
js/
script.js
css/
style.css
So my webserver.js script is in the root of app, and everything I want to access is in 'public'.
I also saw this example that uses path.extname() to get any files extentions located in a path. (see the last code block).
So I've tried to combine the new site structure and this path.extname() example, to have the webserver allow access to any file in my public dir, so I can render the html file, which references the external js and css files.
My webserver.js looks like this.
var http = require('http')
, url = require('url')
, fs = require('fs')
, path = require('path')
, server;
server = http.createServer(function(req,res){
var myPath = url.parse(req.url).pathname;
switch(myPath){
case '/public':
// get the extensions of the files inside this dir (.html, .js, .css)
var extname = mypath.extname(path);
switch (extname) {
// get the html
case '.html':
fs.readFile(__dirname + '/public/chatclient.html', function (err, data) {
if (err) return send404(res);
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data, 'utf8');
res.end();
});
break;
// get the script that /public/chatclient.html references
case '.js':
fs.readFile(__dirname + '/public/js/script.js', function (err, data) {
if (err) return send404(res);
res.writeHead(200, { 'Content-Type': 'text/javascript' });
res.end(content, 'utf-8');
res.end();
});
break;
// get the styles that /public/chatclient.html references
case '.css':
fs.readFile(__dirname + '/public/css/style.css', function (err, data) {
if (err) return send404(res);
res.writeHead(200, { 'Content-Type': 'text/javascript' });
res.end(content, 'utf-8');
res.end();
});
}
break;
default: send404(res);
}
});
Inside the case of public, I'm trying to get any of the folders/files inside of this dir via
var extname = mypath.extname(path);
Similar to the link I provided.
But at the moment 'extname' is empty when I console log it.
Can anyone advise what I might need to add or tweek here?
I'm aware this can be done easily in Express, but I would like to know how to achieve the same thing just relying on Node.
I's appreciate any help on this.
Thanks in advance.
There are several problems with your code.
Your server is not going to run as you have not specified a port to listen from.
As Eric pointed out your case condition will fail as 'public' does not appear in the url.
Your are referencing a non-existent variable 'content' in your js and css responses, should be 'data'.
You css content-type header should be text/css instead of text/javascript
Specifying 'utf8' in the body is unnecessary.
I have re-written your code.
Notice I do not use case/switch. I prefer much simpler if and else, you can put them back if that's your preference. The url and path modules are not necessary in my re-write, so I have removed them.
var http = require('http'),
fs = require('fs');
http.createServer(function (req, res) {
if(req.url.indexOf('.html') != -1){ //req.url has the pathname, check if it conatins '.html'
fs.readFile(__dirname + '/public/chatclient.html', function (err, data) {
if (err) console.log(err);
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
res.end();
});
}
if(req.url.indexOf('.js') != -1){ //req.url has the pathname, check if it conatins '.js'
fs.readFile(__dirname + '/public/js/script.js', function (err, data) {
if (err) console.log(err);
res.writeHead(200, {'Content-Type': 'text/javascript'});
res.write(data);
res.end();
});
}
if(req.url.indexOf('.css') != -1){ //req.url has the pathname, check if it conatins '.css'
fs.readFile(__dirname + '/public/css/style.css', function (err, data) {
if (err) console.log(err);
res.writeHead(200, {'Content-Type': 'text/css'});
res.write(data);
res.end();
});
}
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
You might want to look into using server frameworks like express which allow you to set a 'public' directory for automatically routing static files
var express = require('express'),app = express();
app.use(express.static(path.join(__dirname, 'public')));
The cheap overhead of such a framework would really be worth the effort of effectively 'reinventing the wheel'
public does not appear in the URL requested by the client, so the switch on myPath will always fall through.
You could consider looking at the Static middleware provided in Connect. Looking at Static's source code might give you some ideas on how to do this with node.js code (if you want learn how to do it without using an existing library).
// get the extensions of the files inside this dir (.html, .js, .css)
var extname = **mypath**.extname(path);
These are reversed. Should be:
var extension = path.extname(mypath);
I also do not use function names for variable names when I can avoid it.
Auto Update files on change, delay for update 1 sec.
Format : app.js | index.htm | style.css
// packages
const http = require('http');
const fs = require('fs');
// server properties
const hostname = '127.0.0.1';
const port = 3000;
const timer = 300;
//should trigger atualize function every timer parameter
let htmlfile = '';
let cssfile = '';
let jsfile = '';
uptodate();
// should read file from the disk for html
function uptodate()
{
console.log(1);
fs.readFile('./index.html', function (err, html) {
if (err) {
throw err;
}
htmlfile = html;
});
// should read css from the disk for css
fs.readFile('./style.css', function (err, html) {
if (err) {
throw err;
}
cssfile = html;
});
// should read js file from the disk
fs.readFile('./app.js', function (err, html) {
if (err) {
throw err;
}
jsfile = html;
});
setTimeout(function(){ uptodate(); }, 1000);
}
const server = http.createServer((req, res) => {
res.statusCode = 200;
// should send css and js
if(req.url.indexOf('.css') != -1){ //req.url has the pathname, check if it conatins '.js'
res.writeHead(200, {'Content-Type': 'text/css'});
res.write(cssfile);
res.end();
return;
}
if(req.url.indexOf('.js') != -1){ //req.url has the pathname, check if it conatins '.js'
res.writeHead(200, {'Content-Type': 'text/javascript'});
res.write(jsfile);
res.end();
return;
}
// should send html file via request
res.writeHeader(200, {"Content-Type": "text/html"});
res.write(htmlfile);
res.end();
});
// should send css and js
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});