NodeJS'de temel statik dosya sunucusu


85

Mükemmel bir sunucudan çok düğümü anlamak için bir egzersiz olarak nodejs'de statik bir dosya sunucusu oluşturmaya çalışıyorum. Connect ve node-static gibi projelerin farkındayım ve bu kitaplıkları daha üretime hazır kod için kullanmayı tamamen planlıyorum, ancak aynı zamanda üzerinde çalıştığım şeyin temellerini de anlamaktan hoşlanıyorum. Bunu göz önünde bulundurarak, küçük bir server.js kodladım:

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

Benim sorum iki yönlü

  1. Bu, düğümde temel html vb. Oluşturmanın ve yayınlamanın "doğru" yolu mu yoksa daha iyi / daha zarif / daha sağlam bir yöntem var mı?

  2. Düğümdeki .pipe () temelde aşağıdakileri mi yapıyor?

.

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

Herkese teşekkürler!


2
Bunu esneklikten ödün vermeden yapmanıza izin veren bir modül yazdım. Ayrıca tüm kaynaklarınızı otomatik olarak önbelleğe alır. Şuna bir
Jon

2
'200 Tamam' HTTP durum koduyla '404 Bulunamadı' döndürmek için (?) Öğesini seçmeniz biraz komik. URL'de hiçbir kaynak bulunamıyorsa, uygun kod 404 olmalıdır (ve belge gövdesine yazdıklarınız genellikle ikincil öneme sahiptir). Aksi takdirde, onlara gerçek değeri olmayan (önbelleğe alabilecekleri) belgeler veren birçok kullanıcı aracısının (web tarayıcıları ve diğer botlar dahil) kafasını karıştırırsınız.
amn

1
Teşekkürler. Yıllar sonra hala güzel çalışıyor.
statosdotcom

1
Teşekkürler! bu kod mükemmel çalışıyor. Ama şimdi yukarıdaki kod fs.exists()yerine kullanın path.exists(). Şerefe! ve evet! unutma return:
Kaushal28

NOT : 1) fs.exists() kullanımdan kaldırılmıştır . Kullanım fs.access()veya daha iyi yukarıdaki kullanım durumunda olduğu gibi, fs.stat(). 2) url.parse kullanımdan kaldırıldı ; new URLbunun yerine daha yeni Arayüzü kullanın.
rags2riches

Yanıtlar:


44
  • Aşağıdakiler dışında temel sunucunuz iyi görünüyor:

    returnEksik bir ifade var.

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

    Ve:

    res.writeHead(200, mimeType);

    olmalı:

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

  • Evet pipe(), temelde bunu yapar, aynı zamanda kaynak akışını duraklatır / devam ettirir (alıcının daha yavaş olması durumunda). pipe()Fonksiyonun kaynak kodu : https://github.com/joyent/node/blob/master/lib/stream.js


2
dosya adı blah.blah.css gibi olursa ne olur?
ShrekOverflow

2
mimeType bu durumda blah olacaktır xP
ShrekOverflow

5
Öyle de değil mi? kendiniz yazarsanız, bu tür böcekleri sorarsınız. İyi bir öğrenme alıntıdır, ancak kendimi devirmek yerine "bağlanmayı" takdir etmeyi öğreniyorum. Bu sayfadaki sorun, insanların sadece basit bir dosya sunucusunun nasıl yapılacağını bulmaya çalışması ve yığın taşması ilk önce ortaya çıkıyor. Bu cevap doğru ama insanlar onu aramıyor, sadece basit bir cevap. Daha basit olanı kendim bulmalıydım, bu yüzden buraya koyun.
Jason Sebring

1
Kütüphane biçimindeki bir çözüme bağlantı yapıştırmak değil, aslında soruya bir cevap yazmak için +1.
Shawn Whinnery

56

Az ama öz

Projenizde önce komut istemine gidin ve

$ npm install express

Ardından app.js kodunuzu şu şekilde yazın:

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

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

Daha sonra dosyalarınızı yerleştirdiğiniz "genel" bir klasör oluşturursunuz. Önce daha zor yolu denedim ama sadece zaman alıcı olan şeyleri haritalamak zorunda kalan mime türleri hakkında endişelenmeli ve sonra yanıt türleri vb. Hakkında endişelenmelisin. Hayır, teşekkür ederim.


2
+1 Kendi kodunuzu yazmak yerine test edilmiş kodu kullanmak için söylenecek çok şey var.
jcollum

1
Belgelere bakmayı denedim ama pek bir şey bulamıyorum, pasajınızın ne yaptığını açıklayabilir misiniz? Bu belirli varyasyonu kullanmaya çalıştım ve neyin neyle değiştirilebileceğini bilmiyorum.
onaclov2000

3
Dizin listesini istiyorsanız, connect.static satırının hemen arkasına .use (connect.directory ('public')) ekleyin ve public yerine kendi yolunuzu yazın. Kaçırıldığım için özür dilerim ama sanırım bu benim için işleri çözüyor.
onaclov2000

1
Siz de 'jQuery kullanın'! Bu, OP'nin sorusuna bir cevap değil, var olmayan bir soruna bir çözümdür. OP, bu deneyin amacının Node'u öğrenmek olduğunu belirtti.
Shawn Whinnery

1
@JasonSebring Neden require('http')ikinci satırda?
Peng, ZenUML.com'da

20

Ben de kaputun altında neler olduğunu anlamayı seviyorum.

Kodunuzda muhtemelen temizlemek isteyeceğiniz birkaç şey fark ettim:

  • Dosya adı bir dizini gösterdiğinde çöker, çünkü var olan doğrudur ve bir dosya akışını okumaya çalışır. Dizinin varlığını belirlemek için fs.lstatSync kullandım.

  • HTTP yanıt kodlarını doğru kullanmıyor (200, 404 vb.)

  • MimeType belirlenirken (dosya uzantısından), res.writeHead'de doğru ayarlanmıyor (stewe'nin işaret ettiği gibi)

  • Özel karakterleri işlemek için, muhtemelen uri'nin çıkışını kaldırmak istersiniz.

  • Sembolik bağları körü körüne takip eder (bir güvenlik sorunu olabilir)

Bu göz önüne alındığında, bazı apache seçenekleri (FollowSymLinks, ShowIndexes, vb.) Daha anlamlı olmaya başlar. Basit dosya sunucunuzun kodunu aşağıdaki gibi güncelledim:

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 [yol.extname (dosya adı) .split (". "). reverse () [0]];" yerine? bazı dosya adlarının birden fazla "." ör. "my.cool.video.mp4" veya "download.tar.gz"
senkronize edilmemiş

Bu bir şekilde bir kullanıcının /../../../ home / user / jackpot.privatekey gibi bir url kullanmasını engelliyor mu? Yolun aşağı akışta olduğundan emin olmak için birleşimi görüyorum, ancak ../../../ tür gösterimi kullanmanın bunu aşıp aşmayacağını merak ediyorum. Belki kendim test ederim.
Reynard

İşe yaramıyor. Neden olduğundan emin değilim ama bunu bilmek güzel.
Reynard

güzel, bir RegEx eşleşmesi de uzantıyı toplayabilir; var mimeType = mimeTypes[path.extname(filename).match(/\.([^\.]+)$/)[1]];
John Mutuma

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
Bunu biraz daha açıklamak isteyebilirsin.
Nathan Tuggy

3

Dosyanın var olup olmadığını ayrı ayrı kontrol etmeyi önleyen bu modele ne dersiniz?

        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
Başarı durumunda mime tipini unuttunuz. Bu tasarımı kullanıyorum, ancak akışları anında borulamak yerine, onları filestream'in 'açık' olayında boruluyorum: mime türü için writeHead, sonra boru. Sona gerek yok: readable.pipe .
geh

@GeH'nin yorumuna göre değiştirildi.
Brett Zamir

OlmalıfileStream.on('open', ...
Petah


0

st modülü kolay statik sunum dosyalarını yapar. İşte README.md'nin bir özeti:

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 cevabı beni doğru yönü gösterdi, ancak kodu güncel değil. İşte en yeni connectsürümle bunu nasıl yapacağınız .

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 kullanabileceğiniz diğer ara katman vardır.


Bunun yerine daha basit bir cevap için ifade kullandım. En yeni ekspres sürümde statik fırınlanmış ancak başka bir şey yok. Teşekkürler!
Jason Sebring

Baktığımızda connectbelgeler, sadece bir olduğunu wrapperiçin middleware. Diğer tüm ilginç middlewareşeyler expressdepodan, bu nedenle teknik olarak bu API'leri express.use().
ffleandro
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.