2011-09-01 25 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

可能想要解釋這個多一點。 –