I have a standard node.js static file server that I want to use to serve normal html, js, css, and jpg files in the same directory (ie- a typical HTML5 single page app). I would expect that the node server can handle this properly. What I see is different.
The index.html file is served, but then subsequent requests are dropped (ie- they never make it to the server). In my chrome dev tools, I see things like this:
GET http://projectcoho.cloudfoundry.com/css/coho.css http://projectcoho.cloudfoundry.com/:7
GET http://projectcoho.cloudfoundry.com/sencha-touch/sencha-touch-debug.js http://projectcoho.cloudfoundry.com/:8
GET http://projectcoho.cloudfoundry.com/coho-debug.js http://projectcoho.cloudfoundry.com/:8
But, these resources exist on the server and you can reach them if you enter their URL directly. And for these requests, my callback in app.js is never invoked (I can tell this because console.log is never called for these files.
Here is the app.js file:
var path = ".";
var port = process.env.VCAP_APP_PORT || 3000;;
var file = new(static.Server) (path, {
cache: 600
});
mime.define({
'text/css': ['css'],
'text/javascript': ['js'],
'image/jpeg': ['jpg', 'jpeg']
});
http.createServer(function (request, response) {
var uri = url.parse(request.url).pathname;
var filename = libpath.join(path, uri);
console.log("URI: " + request.url + " , filename: " + filename);
libpath.exists(filename, function (exists) {
console.log("Serving " + filename);
if (!exists) {
console.log("Not found");
response.writeHead(404, {
"Content-Type": "text/plain"
});
response.write("404 Not Found\n");
response.end();
return;
}
if (fs.statSync(filename).isDirectory()) {
filename += '/index.html';
}
var type = mime.lookup(filename);
file.serveFile(filename, 200, {'content-type' : type}, request, response);
});
}).listen(port);
What am I missing here?
I am using node v0.6.15
In the end, the answer was that my cache.manifest file was incorrect. The client application was looking for resources in a cache, but the didn't exist. When I corrected the manifest, things started working.
Related
I'm a bit confused about where meteor saves static files (the ones in the /public folder);
I have a method for uploading files:
saveFile: function(blob, name, path, encoding) {
if (!Meteor.isServer) return;
var fs = Npm.require('fs'),
chroot = Meteor.chroot || 'public';
//path = cleanPath(path);
name = cleanName(name || 'file');
encoding = encoding || 'binary';
path = chroot + (path ? '/' + path + '/' : '/');
var basePath = process.env.PWD;
var fullPath = basePath + '/' + path + name;
var dirs = (path.split('/'));
var currentCreatedPath = basePath;
//create needed folders
dirs.forEach(function(dir, index) {
if (dir.length > 0) {
currentCreatedPath = currentCreatedPath + '/' + dir;
if (!fs.existsSync(currentCreatedPath)){
fs.mkdirSync(currentCreatedPath);
}
}
});
fs.writeFile(fullPath, blob, encoding, function(err) {
if (err) console.log(err); //throw (new Meteor.Error(500, 'Failed to save file.', err));
else console.log('The file ' + fullPath + 'has been saved');
});
function cleanPath(str) {
if (str) return str.replace(/\.\./g,'').replace(/\/+/g,'').replace(/^\/+/,'').replace(/\/+$/,'');
}
function cleanName(str) {
return str.replace(/\.\./g,'').replace(/\//g,'');
}
return true;
}
Then in the template I reach the file like this:
{{#each this.files}}
<li>
<a href="/{{path}}" target="_blank">
{{title}}
</a>
</li>
{{/each}}
where path is /public/folder/to/file/file.ext
This works great locally; but once deployed, it is not able to find uploaded files;
in which folder of the deployed project does meteor save the files?
Its not advised to use this practice. If you deploy your app and have more than one instance or 'drone' running each one will not be able to see each others files.
Its best to use something like s3 to store your files on a 3rd party server due to this.
Anyway If you do decide to store your files on the same machine you just need to use an absolute path outside your meteor project.
If you use the public folder Meteor will restart due to the file change. If you use the bundled asset static folder you will have a bit of trouble finding your files.
Something like this may help (saves on your desktop):
var path = Npm.require("path").resolve("~/Desktop/");
saveFile(blob, name, path, encoding)
To view the files you can have some kind of proxy (if you use iron router):
Server side code (may need some tweaking):
Router.route('/file/:filename', function() {
var self = this;
var res = this.response;
var filename = this.params.filename
var path = Npm.require("path");
var fs = Npm.require("fs");
fs.readFile(path.join(path.resolve("~/Desktop"), filename), function(err, fileContent) {
if(err) console.log(err);
self.response.statusCode = 200;
//self.response.setHeader("Content-Type", response.headers['content-type']);
self.response.setHeader("Content-disposition", filename);
self.response.end(fileContent);
});
}, { where: 'server' });
For simplicity, I've used your Desktop folder (OS X). I'd advise to use another more secure folder.
Then you can use the url http://localhost:3000/file/your_file_name to download it.
In my meteor app, the server try to download some file to store them on filesystem.
I use Meteor.http package to do that, but in fact, if file are downloaded, they seems to be corrupted.
var fileUrl = 'http://cdn.sstatic.net/stackoverflow/img/sprites.png?v=5'; //for example
Meteor.http.call("GET", fileUrl, function funcStoreFile(error, result) {
"use strict";
if (!error) {
var fstream = Npm.require('fs'),
filename = './.meteor/public/storage/' + collectionId;
fstream.writeFile(filename, result.content, function funcStoreFileWriteFS(err) {
if (!err) {
var Fiber = Npm.require('fibers');
Fiber(function funcStoreImageSaveDb() {
MyfileCollection.update({_id: collectionId}, {$set: {fileFsPath: filename}});
}).run();
} else {
console.log('error during writing file', err);
}
});
} else {
console.log('dl file FAIL');
}
});
I did a symlink from public/storage to ../.meteor/public/storage to enable direct download from url (http://localhost:3000/storage/myfileId)
When i compare the file downloaded with this system and the same file downloaded directly from a browser, they are different. What's wrong with my conception?
I had a similar problem and made a solution based on this discussion:
on https://github.com/meteor/meteor/issues/905
By using the request library, which meteor is using under the hood as well, one can avoid the problem with binary downloads. Besides I would recommend not saving small files to the filesystem but base64 encoded in mongodb directly. This is the easiest solution, if you plan to deploy to meteor.com or other cloud services.
An other glitch I found when saving files to the public dir in development is that meteor is reloading the files for every change in the public dir. this can lead to data corruption as chunks of the file are being downloaded. Here some code i am using based on the above discussion.
Future = Npm.require("fibers/future")
request = Npm.require 'request'
Meteor.methods
downloadImage: (url) ->
if url
fut = new Future()
options =
url: url
encoding: null
# Get raw image binaries
request.get options, (error, result, body) ->
if error then return console.error error
base64prefix = "data:" + result.headers["content-type"] + ";base64,"
image = base64prefix + body.toString("base64")
fut.ret image
# pause until binaries are fully loaded
return fut.wait()
else false
Meteor.call 'downloadImage', url, (err, res) ->
if res
Movies.update({_id: id}, {$set: {image: res}})
Hope this is helpful.
This is the code I am using is as follows down below:
I am using IE9 and am unable to see the request being sent in the Network tab. I do have Access-Control headers set in the JSP as:
<% response.setHeader("Access-Control-Allow-Origin", "*");%>
Code to get the AJAX HTML Content from the JSP:
if ($.browser.msie && window.XDomainRequest) {
var xdr = new window.XDomainRequest();
xdr.open("GET", "http://dev01.org:11110/crs/qw/qw.jsp?&_=" + Math.random());
xdr.contentType = "text/plain";
xdr.timeout = 5000;
xdr.onerror = function () {
console.log('we have an error!');
}
xdr.onprogress = function () {
console.log('this sucks!');
};
xdr.ontimeout = function () {
console.log('it timed out!');
};
xdr.onopen = function () {
console.log('we open the xdomainrequest');
};
xdr.onload = function() {
alert(xdr.responseText);
};
xdr.send(null);
} else { ...... }
I am getting a Access is Denied Error. Any help would be much appreciated!
Requests must be targeted to the same scheme as the hosting page
In your example you are doing request to:
http://dev01 ...
And you should do this from HTTP protocol.
For example:
If your site, where js script is located: http://dev.org
You can do this:
xhr = new XDomainRequest();
xhr.open("GET", "http://dev01.org?p=1");
but this throws "Access denied":
xhr = new XDomainRequest();
xhr.open("GET", "https://dev01.org?p=1");
My experience with XDomainRequest is that it doesn't respect Access-Control-Allow-Origin: *. Instead, you must specify the domain. This can be obtained from the HTTP_REFERER header if you need to dynamically generate it, or if you are only expecting requests from one domain you can set it manually. This article might help.
<% response.setHeader("Access-Control-Allow-Origin", "http://dev01.org");%>
I'm trying to write a simple http web server, that (among other features), can send the client a requested file.
Sending a regular text file/html file works as a charm. The problem is with sending image files.
Here is a part of my code (after parsing the MIME TYPE, and including fs node.js module):
if (MIMEtype == "image") {
console.log('IMAGE');
fs.readFile(path, "binary", function(err,data) {
console.log("Sending to user: ");
console.log('read the file!');
response.body = data;
response.end();
});
} else {
fs.readFile(path, "utf8", function(err,data) {
response.body = data ;
response.end() ;
});
}
Why all I'm getting is a blank page, upon opening http://localhost:<serverPort>/test.jpg?
Here's a complete example on how to send an image with Node.js in the simplest possible way (my example is a gif file, but it can be used with other file/images types):
var http = require('http'),
fs = require('fs'),
util = require('util'),
file_path = __dirname + '/web.gif';
// the file is in the same folder with our app
// create server on port 4000
http.createServer(function(request, response) {
fs.stat(file_path, function(error, stat) {
var rs;
// We specify the content-type and the content-length headers
// important!
response.writeHead(200, {
'Content-Type' : 'image/gif',
'Content-Length' : stat.size
});
rs = fs.createReadStream(file_path);
// pump the file to the response
util.pump(rs, response, function(err) {
if(err) {
throw err;
}
});
});
}).listen(4000);
console.log('Listening on port 4000.');
UPDATE:
util.pump has been deprecated for a while now and you can just use streams to acomplish this:
fs.createReadStream(filePath).pipe(req);
I'm trying to create a static file server in nodejs more as an exercise to understand node than as a perfect server. I'm well aware of projects like Connect and node-static and fully intend to use those libraries for more production-ready code, but I also like to understand the basics of what I'm working with. With that in mind, I've coded up a small server.js:
var http = require('http'),
url = require('url'),
path = require('path'),
fs = require('fs');
var mimeTypes = {
"html": "text/html",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"png": "image/png",
"js": "text/javascript",
"css": "text/css"};
http.createServer(function(req, res) {
var uri = url.parse(req.url).pathname;
var filename = path.join(process.cwd(), uri);
path.exists(filename, function(exists) {
if(!exists) {
console.log("not exists: " + filename);
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('404 Not Found\n');
res.end();
}
var mimeType = mimeTypes[path.extname(filename).split(".")[1]];
res.writeHead(200, mimeType);
var fileStream = fs.createReadStream(filename);
fileStream.pipe(res);
}); //end path.exists
}).listen(1337);
My question is twofold
Is this the "right" way to go about creating and streaming basic html etc in node or is there a better/more elegant/more robust method ?
Is the .pipe() in node basically just doing the following?
.
var fileStream = fs.createReadStream(filename);
fileStream.on('data', function (data) {
res.write(data);
});
fileStream.on('end', function() {
res.end();
});
Thanks everyone!
Less is more
Just go command prompt first on your project and use
$ npm install express
Then write your app.js code like so:
var express = require('express'),
app = express(),
port = process.env.PORT || 4000;
app.use(express.static(__dirname + '/public'));
app.listen(port);
You would then create a "public" folder where you place your files. I tried it the harder way first but you have to worry about mime types which is just having to map stuff which is time consuming and then worry about response types, etc. etc. etc.... no thank you.
Your basic server looks good, except:
There is a return statement missing.
res.write('404 Not Found\n');
res.end();
return; // <- Don't forget to return here !!
And:
res.writeHead(200, mimeType);
should be:
res.writeHead(200, {'Content-Type':mimeType});
Yes pipe() does basically that, it also pauses/resumes the source stream (in case the receiver is slower).
Here is the source code of the pipe() function: https://github.com/joyent/node/blob/master/lib/stream.js
I like understanding what's going on under the hood as well.
I noticed a few things in your code that you probably want to clean up:
It crashes when filename points to a directory, because exists is true and it tries to read a file stream. I used fs.lstatSync to determine directory existence.
It isn't using the HTTP response codes correctly (200, 404, etc)
While MimeType is being determined (from the file extension), it isn't being set correctly in res.writeHead (as stewe pointed out)
To handle special characters, you probably want to unescape the uri
It blindly follows symlinks (could be a security concern)
Given this, some of the apache options (FollowSymLinks, ShowIndexes, etc) start to make more sense. I've update the code for your simple file server as follows:
var http = require('http'),
url = require('url'),
path = require('path'),
fs = require('fs');
var mimeTypes = {
"html": "text/html",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"png": "image/png",
"js": "text/javascript",
"css": "text/css"};
http.createServer(function(req, res) {
var uri = url.parse(req.url).pathname;
var filename = path.join(process.cwd(), unescape(uri));
var stats;
try {
stats = fs.lstatSync(filename); // throws if path doesn't exist
} catch (e) {
res.writeHead(404, {'Content-Type': 'text/plain'});
res.write('404 Not Found\n');
res.end();
return;
}
if (stats.isFile()) {
// path exists, is a file
var mimeType = mimeTypes[path.extname(filename).split(".").reverse()[0]];
res.writeHead(200, {'Content-Type': mimeType} );
var fileStream = fs.createReadStream(filename);
fileStream.pipe(res);
} else if (stats.isDirectory()) {
// path exists, is a directory
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('Index of '+uri+'\n');
res.write('TODO, show index?\n');
res.end();
} else {
// Symbolic link, other?
// TODO: follow symlinks? security?
res.writeHead(500, {'Content-Type': 'text/plain'});
res.write('500 Internal server error\n');
res.end();
}
}).listen(1337);
var http = require('http')
var fs = require('fs')
var server = http.createServer(function (req, res) {
res.writeHead(200, { 'content-type': 'text/plain' })
fs.createReadStream(process.argv[3]).pipe(res)
})
server.listen(Number(process.argv[2]))
How about this pattern, which avoids checking separately that the file exists
var fileStream = fs.createReadStream(filename);
fileStream.on('error', function (error) {
response.writeHead(404, { "Content-Type": "text/plain"});
response.end("file not found");
});
fileStream.on('open', function() {
var mimeType = mimeTypes[path.extname(filename).split(".")[1]];
response.writeHead(200, {'Content-Type': mimeType});
});
fileStream.on('end', function() {
console.log('sent file ' + filename);
});
fileStream.pipe(response);
I made a httpServer function with extra features for general usage based on #Jeff Ward answer
custtom dir
index.html returns if req === dir
Usage:
httpServer(dir).listen(port);
https://github.com/kenokabe/ConciseStaticHttpServer
Thanks.
the st module makes serving static files easy. Here is an extract of README.md:
var mount = st({ path: __dirname + '/static', url: '/static' })
http.createServer(function(req, res) {
var stHandled = mount(req, res);
if (stHandled)
return
else
res.end('this is not a static file')
}).listen(1338)
#JasonSebring answer pointed me in the right direction, however his code is outdated. Here is how you do it with the newest connect version.
var connect = require('connect'),
serveStatic = require('serve-static'),
serveIndex = require('serve-index');
var app = connect()
.use(serveStatic('public'))
.use(serveIndex('public', {'icons': true, 'view': 'details'}))
.listen(3000);
In connect GitHub Repository there are other middlewares you can use.