NodeJS 中的基本静态文件服务器

我试图在 nodejs 中创建一个静态文件服务器,更多的是作为理解 node 的练习,而不是作为一个完美的服务器。我非常了解 Connect 和 node-static 这样的项目,并且完全打算将这些库用于更多的生产就绪代码,但是我也想了解我正在处理的内容的基本知识。考虑到这一点,我编写了一个小的 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);

我的问题是双重的

  1. 这是在节点中创建和流化基本 html 等的“正确”方法吗? 还是有更好的/更优雅的/更健壮的方法?

  2. 节点中的. tube ()基本上是在执行以下操作吗?

.

var fileStream = fs.createReadStream(filename);
fileStream.on('data', function (data) {
res.write(data);
});
fileStream.on('end', function() {
res.end();
});

谢谢大家!

86154 次浏览
  • 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

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.

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);

I made a httpServer function with extra features for general usage based on @Jeff Ward answer

  1. custtom dir
  2. index.html returns if req === dir

Usage:

httpServer(dir).listen(port);

https://github.com/kenokabe/ConciseStaticHttpServer

Thanks.

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);

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.

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]))