Node.js + Serve Static Files with RESTIFY - node.js

I have a multi-level collection of .html, .js, .png, .css, etc files in a site. A peek at my site hiearchy looks like the following:
index.html
child1
index.html
page1.html
page2.html
...
child2
grandchild1
index.html
grandchild2
index.html
index.html
page1.html
page2.html
resources
css
myTheme.css
img
logo.png
profile.png
js
jquery.js
...
...
I am migrating this to run under Node.js. I have been told I MUST use RESTIFY. Currently, I've written the following for my server:
var restify = require('restify');
var fs = require('fs');
var mime = require('mime');
var server = restify.createServer({
name: 'Demo',
version: '1.0.0'
});
server.use(restify.acceptParser(server.acceptable));
server.use(restify.queryParser());
server.use(restify.bodyParser());
server.get('/', loadStaticFile);
server.get('/echo/:name', function (req, res, next) {
res.send(req.params);
return next();
});
server.listen(2000, function () {
console.log('Server Started');
});
function loadStaticFile(req, res, next) {
var filePath = __dirname + getFileName(req);
console.log("Returning " + filePath);
fs.readFile(filePath, function(err, data) {
if (err) {
res.writeHead(500);
res.end("");
next(err);
return;
}
res.contentType = mime.lookup(filename);
res.writeHead(200);
res.end(data);
return next();
});
}
function getFileName(req) {
var filename = "";
if (req.url.indexOf("/") == (req.url.length-1)) {
filename = req.url + "index.html";
} else {
console.log("What Now?");
}
return filename;
}
With this code, I can successfully load index.html. However, my index.html file references some JavaScript, image files, and style sheets. I can see via Fiddler that that these files are being requested. However, in my node.js console window, I never see "Returing [js|css|png filename]". Its like my node.js web server returns index.html and that's it.
What am I doing wrong?

The latest versions of restify has builtin middleware serveStatic() middleware that will do this for you.
from a http://mcavage.me/node-restify/#Server-API
server.get(/\/docs\/public\/?.*/, restify.serveStatic({
directory: './public'
}));
for more detailed example:
http://mushfiq.me/2013/11/02/serving-static-files-using-restify/

Do any of your served files contain relative paths (say ../abc.js)?
You have to use path.resolve() to get the real path for fs.readFile().
Anyway there are a lot of pitfalls in serving files:
invalid url (400)
file not found (404)
escape sequence (url encoding)
fs.read() read files into memory (by #robertklep)
etc
You can use existing static file serving middleware.
I've been using Ecstatic, AFAIK it handles those issues properly.
Try
server.use(ecstatic({ root: __dirname + '/' }));
If that fails you can refer to this to stack Restify on top of Connect/Express.

Related

Not able to use multiple html files in node js

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.

Node.js Serving HTML pages and Static content

var express = require("express");
var app = express();
var path = require("path");
app.use(express.static(__dirname + '/view'));
app.get('/dashboard',function(req,res){
res.sendFile((path.join(__dirname + '/dashboard.html'));
});
app.get('/index1',function(req,res){
res.sendFile((path.join(__dirname+'/index.html'));
});
app.get('/',function(req,res){
res.redirect('/login');
});
app.get('/login',function(req,res){
res.redirect((path.join(__dirname + '/login'));
});
app.listen(3000);
console.log("Running at Port 3000");
My Problem here is why do I need to check each time what user is requesting for?
Also, what if I have 100 html files in my directory do I need to check the each file through get method and then return the page through res.sendFile?
What #OrangeDog and #Clemens Himmer said are both true. However, the simplest way to serve all the files in a directory is already in your script:
app.use(express.static(__dirname + '/view'));
That will serve everything in your view directory if the name matches the URL, e.g. http://yoursite.com/index.html will resolve to the file at __dirname + '/view/index.html'.
In your example routes, however, you seem to be changing the URL path to no longer match the file location (for example, /login you want to resolve to '/login.html'). You could definitely write middleware to munge that and resolve the file based on a pattern, but at that point it's far simpler to use a purpose built web server (like nginx as previously suggested) which has URL rewrite features already baked in.
No, you don't have to list all your static files programatically. You can serve them dynamically by binding middleware to the server's root.
This is a small express.js script i've done, it's basically a really simple web server that serves anything and pretty HTML.
// This servers a file..
var serveFile = function(filePath, res){
var options = {
dotfiles: 'deny',
headers: {
'x-timestamp': Date.now(),
'x-sent': true
}
};
res.sendFile(filePath, options, function (err) {
if (err) {
console.log(err);
res.status(err.status).end();
}
});
};
// Serve web files
app.use("/", function (req, res, next) {
var filePath = (absoluteServePath + req.originalUrl).replace(/\//g,"\\");
var checkFilePath = function(filePath){
return new Promise(function(resolve, reject) {
fs.access(filePath, fs.F_OK, function(err) {
if(!err){
// If FILE / DIR exists check if file or DIR
if(fs.lstatSync(filePath).isDirectory() == true){
reject();
}
else{
resolve();
}
}else{
reject(err);
}
});
});
};
checkFilePath(filePath).then(function(){
serveFile(filePath,res);
},function(){
// Check if path ends with a slash
var endsWithSlash = filePath.substr(filePath.length - 1) == "\\";
// Check if a index.html exists in the path
var indexHTMLPath = filePath + ((endsWithSlash == true) ? "" : "\\") + "index.html";
checkFilePath(indexHTMLPath).then(function(){
serveFile(indexHTMLPath,res);
},function(){
// Check if .html for the path exists
var plusHTMLPath = filePath +".html";
checkFilePath(plusHTMLPath).then(function(){
serveFile(plusHTMLPath,res);
},function(){
// Nope, does not exist at all
next();
});
});
});
});
My Problem here is why do I need to check each time what user is requesting for?
How are you supposed to give the user what they requested if you don't check what that is?
What if I have 100 html files in my directory do I need to check the each file through get method and then return the page through res.sendFile?
If you mean do you need to declare a separate route for every file, then the answer is no. Express routes can be pattern-based, so you can define a route for e.g. a whole directory, then return the specific file that was requested: Simple static HTML server in Node.
This all leads on however to Node.JS not being a great choice for serving lots of static content. There are many security and other concerns you need to take care of, and it's just not as performant as it could be. You are likely to have a better time if you use an nginx or apache2 server to serve these files, forwarding dynamic requests to your Node server.
Node.js + Nginx - What now?

Serving static files with restify

I am learning to use Node.js. Currently, I have a folder structure that looks like the following:
index.html
server.js
client
index.html
subs
index.html
page.html
res
css
style.css
img
profile.png
js
page.js
jquery.min.js
server.js is my webserver code. I run this from a command-line using node server.js. The contents of that file are:
var restify = require('restify');
var server = restify.createServer({
name: 'Test App',
version: '1.0.0'
});
server.use(restify.acceptParser(server.acceptable));
server.use(restify.queryParser());
server.use(restify.bodyParser());
server.get('/echo/:name', function (req, res, next) {
res.send(req.params);
return next();
});
server.listen(2000, function () {
console.log('%s running on %s', server.name, server.url);
});
As you can see, this server relies on RESTIFY. I've been told I must use RESTIFY. However, I can't figure out how to serve static files. For instance, how do I server the *.html, *.css, *.png, and *.js files in my app?
Thank you!
From the documentation:
server.get(/\/docs\/public\/?.*/, restify.plugins.serveStatic({
directory: './public'
}));
But this will search files in the ./public/docs/public/ directory.
If you want to not append request path to it, use appendRequestPath: false option.
I prefer to use __dirname key here:
server.get(/\/public\/?.*/, restify.plugins.serveStatic({
directory: __dirname
}));
The value of __dirname is equal to script file directory path, which assumed to be also a folder, where is public directory.
And now we map all /public/.* urls to ./public/ directory.
Now also exists serveStaticFiles plugin:
server.get('/public/*', // don't forget the `/*`
restify.plugins.serveStaticFiles('./doc/v1')
); // GET /public/index.html -> ./doc/v1/index.html file
According to my current restify version (v5.2.0)
the serveStatic has been moved into plugins, so the code would be like this
server.get(
/\/(.*)?.*/,
restify.plugins.serveStatic({
directory: './static',
})
)
Syntax above will serve your static files on folder static. So you can get the static file like http://yoursite.com/awesome-photo.jpg
For some reason if you want to serve the static files under specific path like this http://yoursite.com/assets/awesome-photo.jpg for example.
The code should be refactored into this
server.get(
/\/assets\/(.*)?.*/,
restify.plugins.serveStatic({
directory: `${app_root}/static`,
appendRequestPath: false
})
)
The option appendRequestPath: false above means we dont include assets path into the file name
From Restify 7 the routes no longer take full regexes, so if you want /public/stylesheet.css to serve the file ./public/stylesheet.css, your code would now look like this:
server.get('/public/*', restify.plugins.serveStatic({
directory: __dirname,
}))
This is because Restify 7 has a new (potentially faster) routing back-end: find-my-way
this is how i'm serving static files in restify
server.get(/\/public\/docs\/?.*/, restify.plugins.serveStatic({
directory: __dirname,
default: 'index.html'
}));
public access path will be : example.com/public/docs/index.html
I came across this issue just recently, so while this may not help you it could help others who are having trouble with it.
When you declare Restify as const restify = require('restify');, the serveStatic method will be in the plugins object so using restify.serveStatic will quietly fail. The correct way to access the method is restify.plugins.serveStatic.
You can find the update docs here: http://restify.com/docs/plugins-api/#serve-static
server.get('/', function(req, res, next) {
fs.readFile(__dirname + '/index.html', function (err, data) {
if (err) {
next(err);
return;
}
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
res.end(data);
next();
});
});
Try this : Here view is a static resource directory name
server.get('/\/.*/', restify.plugins.serveStatic({
directory: __dirname + "/view/",
default: './home.html'
})
);

Any way to serve static html files from express without the extension?

I would like to serve an html file without specifying it's extension. Is there any way I can do this without defining a route? For instance instead of
/helloworld.html
I would like to do just
/helloworld
you can just use extension option in express.static method .
app.use(express.static(path.join(__dirname, 'public'),{index:false,extensions:['html']}));
A quick'n'dirty solution is to attach .html to requests that don't have a period in them and for which an HTML-file exists in the public directory:
var fs = require('fs');
var publicdir = __dirname + '/public';
app.use(function(req, res, next) {
if (req.path.indexOf('.') === -1) {
var file = publicdir + req.path + '.html';
fs.exists(file, function(exists) {
if (exists)
req.url += '.html';
next();
});
}
else
next();
});
app.use(express.static(publicdir));
While Robert's answer is more elegant there is another way to do this. I am adding this answer just for the sake of completeness. To serve static files without extension you can create a folder with the name of the route you want to serve against and then create an index.html file in it.
Taking my own example if I wanted to serve hello.html at /hello. I would create a directory called hello and put an index.html file in it. Now when '/hello' is called express will automatically serve this file without the extension.
Kind of obvious as this is supported by all web frameworks but I missed it back then.
This single line can route all the html file extension in the public folder.
app.use(express.static('public',{extensions:['html']}));
If you want to go the reverse way like I did(serving an html file called "helloworld" as html) this is the middleware I used.
var express = require('express');
var app = express();
app.use(function(req, res, next) {
if (req.path.indexOf('.') === -1) {
res.setHeader('Content-Type', 'text/html');
}
next();
});
app.use('/', express.static(__dirname + '/public'));
app.listen(8080, function () {
console.log('App listening on port 8080!');
})

Node.js - external JS and CSS files (just using node.js not express)

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}/`);
});

Resources