2011-09-01 19 views
79

我试图在nodejs中创建一个静态文件服务器,作为理解节点的练习,而不是理想的服务器。我非常了解像Connect和node-static这样的项目,并且完全打算将这些库用于更多可用于生产的代码,但我也想了解我正在使用的基础知识。考虑到这一点,我编写了一个小server.js:NodeJS中的基本静态文件服务器

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

我写了一个模块,可以在不影响灵活性的情况下做到这一点。它也会自动缓存所有资源。检查出来:https://github.com/topcloud/cachemere – Jon

+1

有趣的是,你选择(?)返回'404未找到'与HTTP状态代码'200 OK'。如果在URL中没有找到资源,则适当的代码应该是404(并且您在文档正文中编写的内容通常是次要的)。否则你会混淆许多用户代理(包括网络爬虫和其他机器人),给他们的文件没有真正的价值(他们也可能缓存)。 – amn

+1

谢谢。许多年后仍然很好地工作。 – statosdotcom

回答

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

+0

真棒 - 感谢修正史蒂夫。 – slapthelownote

+2

如果文件名与blah.blah.css类似,会发生什么情况? – ShrekOverflow

+2

在这种情况下,mimeType应该是blah xP – ShrekOverflow

52

少即是多

只要进入命令提示符先对你的项目和使用

$ 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

+0

为了澄清,将'public'更改为您自己的文件夹所在的路径,然后查看文件检查http:// :3000/如果您没有输入文件名,您将得到一个Can not Get /(我认为它会列出目录,但事实并非如此 – onaclov2000

20

我喜欢理解发动机盖下的情况。

我注意到在你的代码的几件事情,你可能要清理:

  • 这名点的时候崩溃到一个目录下,因为存在是真实的,它试图读取文件流。我用fs.lstatSync来确定目录的存在。

  • 它不使用HTTP响应代码正确(200,404等)

  • 当正在确定Mime类型(从文件扩展名),它不被正确地设置在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); 
+3

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

+0

这是否以某种方式阻止某人使用类似文件夹/../../../ home/user/jackpot.privatekey的URL?我看到连接确保路径在下游,但是我想知道是否使用../../../表示法可以解决这个问题。也许我会自己测试一下。 – Reynard

+0

它不起作用。我不知道为什么,但很高兴知道。 – Reynard

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

如果成功,您忘记了mimetype。我正在使用这种设计,但不是立即管道流,而是在文件流的'open'事件中对它们进行管道:writeHead用于mimetype,然后是管道。最后不需要:[readable.pipe](http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options)。 – GeH

+0

根据@GeH的评论修改。 –

+0

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

0

st module使得提供静态文件轻松。这里是一个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); 

connectGitHub Repository有您可以使用其他中间件。

+0

我只是用express来代替一个简单的答案,最新的express版本有静态的,但没有其他的东西,谢谢! –

+0

查看'connect'文档,它只是'中间件'的包装器,其他所有有趣的'中间件'都来自'express'仓库,所以在技术上你可以用'express.use()'来使用这些API。 – ffleandro

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

可能想要解释这个多一点。 –