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


85

我正在尝试在nodejs中创建静态文件服务器,而不是将其作为完美的服务器,这更多是为了理解节点。我非常了解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. 节点中的.pipe()基本上只是执行以下操作吗?

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

感谢大家!


2
我编写了一个模块,可让您做到这一点而又不影响灵活性。它还会自动缓存所有资源。检查出来:github.com/topcloud/cachemere
乔恩·

2
您选择(?)返回HTTP状态代码为“ 200 OK”的“ 404 Not Found”有点奇怪。如果在URL上找不到资源,则适当的代码应为404(并且您在文档正文中编写的内容通常是次要的)。否则,您将使许多用户代理(包括Web爬网程序和其他漫游器)感到困惑,从而给他们没有真实价值的文档(它们也可能会缓存)。
2013年

1
谢谢。多年后仍然可以正常工作。
statosdotcom

1
谢谢!此代码运行完美。但是现在使用fs.exists()代替path.exists()上面的代码。干杯! 是的!不要忘记return
Kaushal28年

注意1) fs.exists()弃用fs.access()对于上述用例,请使用,甚至更好fs.stat()2) url.parse弃用;请改用较新的new URL界面。
rags2riches

Answers:


44
  • 基本服务器看起来不错,除了:

    有一个return失踪的语句。

    res.write('404 Not Found\n');
    res.end();
    return; // <- Don't forget to return here !!
    

    和:

    res.writeHead(200, mimeType);

    应该:

    res.writeHead(200, {'Content-Type':mimeType});

  • 是的pipe(),基本上是这样做的,它还会暂停/恢复源流(以防接收器变慢)。这是该pipe()函数的源代码:https : //github.com/joyent/node/blob/master/lib/stream.js


2
如果文件名像blah.blah.css会怎样?
ShrekOverflow'2

2
在这种情况下,mimeType应该是xP
ShrekOverflow 2012年

5
难道不是吗?如果您自己编写,则要求这些类型的错误。良好的学习能力,但我正在学习欣赏“连接”而不是自己动手。该页面的问题是人们只是在寻找如何做一个简单的文件服务器,而堆栈溢出首先出现。这个答案是正确的,但人们并没有在寻找它,只是一个简单的答案。我必须自己找出较简单的一个,因此放在这里。
杰森·塞布林

1
+1表示不以库的形式粘贴指向解决方案的链接,而是实际编写问题的答案。
肖恩·惠纳

57

少即是多

只需在您的项目上先进入命令提示符并使用

$ npm install express

然后像这样编写您的app.js代码:

var express = require('express'),
app = express(),
port = process.env.PORT || 4000;

app.use(express.static(__dirname + '/public'));
app.listen(port);

然后,您将在其中放置文件的地方创建一个“公用”文件夹。我首先用较难的方式尝试过,但是您必须担心mime类型,而这只需要映射耗时的内容,然后再担心响应类型等,等等。等等....不,谢谢。


2
+1对于使用经过测试的代码而不是自己编写代码,要说很多话。
jcollum

1
我尝试查看文档,但似乎找不到太多,您能解释一下您的代码段在做什么吗?我尝试使用此特定变体,但我不知道可以用什么替换。
onaclov2000

3
如果要列出目录,只需在connect.static行之后添加.use(connect.directory('public')),并用路径替换public。抱歉,这次劫持,但我认为这为我清除了一切。
onaclov2000

1
您不妨“使用jQuery”!这不是OP的问题,而是解决甚至不存在的问题的解决方案。OP指出,该实验的重点是学习Node。
肖恩·惠纳

1
@JasonSebring为什么require('http')在第二行?
小鹏-ZenUML.com 2014年

19

我也喜欢了解幕后情况。

我注意到您的代码中可能需要清理的几件事:

  • 当filename指向目录时,它会崩溃,因为exist为true,并且它尝试读取文件流。我使用fs.lstatSync确定目录是否存在。

  • 它没有正确使用HTTP响应代码(200、404等)

  • 在确定MimeType时(通过文件扩展名),但未在res.writeHead中正确设置它(如stewe所指出的)

  • 要处理特殊字符,您可能需要对uri进行转义

  • 它盲目地遵循符号链接(可能是出于安全考虑)

鉴于此,一些Apache选项(FollowSymLinks,ShowIndexes等)开始变得更有意义。我已经更新了您的简单文件服务器的代码,如下所示:

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

4
我可以建议“ var mimeType = mimeTypes [path.extname(filename).split(“。”)。reverse()[0]];” 代替?一些文件名具有多个“。”。例如“ my.cool.video.mp4”或“ download.tar.gz”
不同步,

这是否会以某种方式阻止某人使用诸如文件夹/../../../home/user/jackpot.privatekey之类的URL?我看到用于确保路径位于下游的联接,但是我想知道使用../../../表示法的类型是否可以解决该问题。也许我会自己测试。
雷纳德2015年

这没用。我不确定为什么,但是很高兴知道。
雷纳德

很好,RegEx匹配也可以收集扩展名;var mimeType = mimeTypes[path.extname(filename).match(/\.([^\.]+)$/)[1]];
约翰·穆图玛

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

4
可能需要对此进行解释。
Nathan Tuggy 2015年

3

如何处理此模式,避免了单独检查文件是否存在

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

1
如果成功,您会忘记模仿。我使用的是这种设计,但不是立即对流进行管道传输,而是在文件流的'open'事件中对管道进行管道传输:mimetype的writeHead,然后进行管道传输。不需要结尾:read.pipe
GeH 2014年

根据@GeH的注释进行修改。
Brett Zamir

应该是fileStream.on('open', ...
Petah


0

ST模块,使容易提供静态文件。这是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)

0

@JasonSebring的答案向正确的方向指出了我,但是他的代码已经过时了。这是您使用最新connect版本的方法。

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

connect GitHub Repository中,您可以使用其他中间件。


我只是用express来代替一个简单的答案。最新的Express版本具有静态功能,但没有太多其他功能。谢谢!
杰森·塞布林2014年

查看connect文档,这只是wrapperfor的middleware。其他所有有趣的事情middleware都来自express存储库,因此从技术上讲,您可以使用来使用这些API express.use()
ffleandro 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.