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

私の質問は2つあります

  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
Jon

2
HTTPステータスコード「200OK」で「404NotFound」を返すために選択(?)するのは少しおかしいです。URLにリソースが見つからない場合、適切なコードは404である必要があります(通常、ドキュメントの本文に書き込む内容は2番目に重要です)。そうしないと、多くのユーザーエージェント(Webクローラーやその他のボットを含む)が実際の価値のないドキュメント(キャッシュする可能性もあります)を提供することで混乱することになります。
amn 2016

1
ありがとう。何年も経った今でもうまく機能しています。
statosdotcom 2017

1
ありがとう!このコードは完全に機能しています。ただし、上記のコードのfs.exists()代わりに使用してくださいpath.exists()。乾杯!そしてええ!忘れないでくださいreturn
Kaushal28 2017年

1) fs.exists()非推奨ですfs.access()上記のユースケースを使用するか、さらに良い方法fs.stat()です。2) url.parse推奨されていませんnew URL代わりに、新しいインターフェイスを使用してください。
rags2riches

回答:


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 2012

2
その場合、mimeTypeは
何とか

5
それは摩擦ではありませんか?あなたがあなた自身を書くならば、あなたはこれらのタイプのバグを求めています。良い学習の練習ですが、私は自分自身を転がすのではなく、「つながる」ことを理解することを学んでいます。このページの問題は、人々が単純なファイルサーバーを作成する方法を探しているだけで、スタックオーバーフローが最初に発生することです。この答えは正しいですが、人々はそれを探していません。単純な答えです。自分でもっと簡単なものを見つけなければならなかったので、ここに置いてください。
Jason Sebring

1
+1は、ソリューションへのリンクをライブラリの形式で貼り付けるのではなく、実際に質問への回答を書き込むためのものです。
Shawn Whinnery 2014

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 2013年

1
ドキュメントを見てみましたが、あまり見つからないようです。スニペットが何をしているのか説明していただけますか?この特定のバリエーションを使用しようとしましたが、何を何に置き換えることができるかわかりません。
onaclov2000 2013年

3
ディレクトリリストが必要な場合は、connect.static行の直後に.use(connect.directory( 'public'))を追加し、publicをパスに置き換えます。ハイジャックして申し訳ありませんが、それで問題は解決したと思います。
onaclov2000 2013年

1
'jQueryを使用する'のもよいでしょう!これは、OPの質問に対する答えではなく、存在すらしない問題の解決策です。OPは、この実験のポイントはノードを学習することであると述べました。
Shawn Whinnery 2014

1
@JasonSebringなぜrequire('http')2行目ですか?
Xiao Peng-ZenUML.com 2014年

19

内部で何が起こっているのかを理解するのも好きです。

コード内で、おそらくクリーンアップしたいことがいくつかあることに気づきました。

  • 存在が真であり、ファイルストリームを読み取ろうとするため、ファイル名がディレクトリを指すとクラッシュします。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
「varmimeType = mimeTypes [path.extname(filename).split( "。")。reverse()[0]];」を提案できますか?代わりに?一部のファイル名には複数の「。」が含まれています 例:「my.cool.video.mp4」または「download.tar.gz」
非同期

これはどういうわけか誰かがfolder /../../../ home / user / jackpot.privatekeyのようなURLを使用するのを止めますか?パスがダウンストリームであることを確認するために結合が表示されますが、.. / .. / .. /タイプの表記を使用するとそれを回避できるかどうか疑問に思います。たぶん私はそれを自分でテストします。
レイナード2015

それは動作しません。理由はわかりませんが、それは嬉しいことです。
レイナード2015

いいですね、正規表現の一致でも拡張子を収集できます。var mimeType = mimeTypes[path.extname(filename).match(/\.([^\.]+)$/)[1]];
ジョンムトゥマ2018年

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
これについてもう少し説明したいと思うかもしれません。
ネイサンタギー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
成功した場合、mimetypeを忘れました。私はこのデザインを使用していますが、ストリームをすぐにパイプする代わりに、ファイルストリームの「open」イベントでパイプします:mimetypeのwriteHead、次にパイプします。終わりは必要ありません:readable.pipe
GeH 2014

@GeHのコメントに従って変更されました。
ブレットザミール2015

あるべきfileStream.on('open', ...
ペタ2015

2

@Jeff Wardの回答に基づいて、一般的な使用法のための追加機能を備えたhttpServer関数を作成しました

  1. custtomdir
  2. index.htmlは、req === dirの場合に戻ります

使用法:

httpServer(dir).listen(port);

https://github.com/kenokabe/ConciseStaticHttpServer

ありがとう。


0

番目のモジュールは、簡単な静的ファイルを提供します。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のリポジトリに使用できる他のミドルウェアがあります。


答えを簡単にするために、代わりにExpressを使用しました。最新のExpressバージョンには静的なものが組み込まれていますが、それ以外はあまりありません。ありがとう!
Jason Sebring 2014

connectドキュメントを見ると、それはのwrapperためだけですmiddleware。他のすべての興味深いmiddlewareものはexpressリポジトリからのものであるため、技術的には、を使用してこれらのAPIを使用できますexpress.use()
ffleandro 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.