まず、github と、github 上でこのソース コードを提供した作者に感謝します。今日の静的ファイル サーバーは昨夜のものよりも少し複雑になっており、多くの新しいことを学ぶことができます。
今回のコードをよく見てみると、fs.stat関数とReadStreamオブジェクトのpipe関数があり、stat関数はファイル情報を取得するために使用されています。最初のパラメータは渡されるファイル パスで、2 番目のパラメータはコールバック関数です。コールバック関数の 2 番目のパラメータ stat の属性はファイルの基本情報です。パイプ関数は、この読み取り可能なストリームを宛先の書き込み可能なストリームに接続するために使用されます。このストリームに渡されたデータは、宛先のストリームに書き込まれます。ソースと宛先のストリームは、必要に応じてストリームを一時停止および再開することで同期を維持します。
この静的ファイル サーバーの改良点は、Last-Modified ヘッダーと If-Modified-Since ヘッダーの使用です。これにより、既に存在するファイルをブラウザーに返す必要がなくなりました。ちなみに、ブラウザから要求されたリソースの圧縮方式に応じて、gzip圧縮やdeflate圧縮のリソースに返すことができます。
var PORT = 8000; var http = require("http"); var url = require("url"); var fs = require("fs"); var path = require("path"); var mime = require("./mime").types; var config = require("./config"); var zlib = require("zlib"); var server = http.createServer(function(request, response) { response.setHeader("Server", "Node/V5"); var pathname = url.parse(request.url).pathname; console.log("url = " + pathname); if (pathname.slice(-1) === "/") { pathname = pathname + config.Welcome.file; } var realPath = __dirname + "/" + path.join("assets", path.normalize(pathname.replace(/\.\./g, ""))); console.log("realPath = " + realPath); var pathHandle = function (realPath) { fs.stat(realPath, function (err, stats) { if (err) { response.writeHead(404, "Not Found", {'Content-Type': 'text/plain'}); response.write("stats = " + stats); response.write("This request URL " + pathname + " was not found on this server."); response.end(); } else { if (stats.isDirectory()) { realPath = path.join(realPath, "/", config.Welcome.file); pathHandle(realPath); } else { var ext = path.extname(realPath); ext = ext ? ext.slice(1) : 'unknown'; var contentType = mime[ext] || "text/plain"; response.setHeader("Content-Type", contentType); //获得文件的修改时间 var lastModified = stats.mtime.toUTCString(); var ifModifiedSince = "If-Modified-Since".toLowerCase(); //设置Last-Modified //服务器给浏览器返回文件最后一次修改时间Last-Modified response.setHeader("Last-Modified", lastModified); if (ext.match(config.Expires.fileMatch)) { var expires = new Date(); expires.setTime(expires.getTime() + config.Expires.maxAge * 1000); response.setHeader("Expires", expires.toUTCString()); response.setHeader("Cache-Control", "max-age=" + config.Expires.maxAge); } //服务器接收浏览器发送过来的If-Modified-Since报文头 //日期相同表示该资源没有变化则返回304 //告诉浏览器该资源你已经有了,不需要再请求了 if (request.headers[ifModifiedSince] && lastModified == request.headers[ifModifiedSince]) { response.writeHead(304, "Not Modified"); response.end(); } else { var raw = fs.createReadStream(realPath); var acceptEncoding = request.headers['accept-encoding'] || ""; var matched = ext.match(config.Compress.match); if (matched && acceptEncoding.match(/\bgzip\b/)) { response.writeHead(200, "Ok", {'Content-Encoding': 'gzip'}); raw.pipe(zlib.createGzip()).pipe(response); } else if (matched && acceptEncoding.match(/\bdeflate\b/)) { response.writeHead(200, "Ok", {'Content-Encoding': 'deflate'}); raw.pipe(zlib.createDeflate()).pipe(response); } else { response.writeHead(200, "Ok"); raw.pipe(response); } } } } }); }; pathHandle(realPath); }); server.listen(PORT); console.log("Server runing at port: " + PORT + ".");
Expires フィールドは、Web ページまたは URL アドレスがブラウザーによってキャッシュされなくなる時間を宣言します。この時間を超えると、ブラウザーは元のサーバーに接続する必要があります。ここでは有効期限を 1 年に設定します。
exports.Expires = { fileMatch: /^(gif|png|jpg|js|css)$/ig, maxAge: 60*60*24*365 }; exports.Compress = { match: /css|js|html/ig }; exports.Welcome = { file: "index.html" };
さまざまなリソースのタイプを列挙し、拡張子に応じて Content-Type を設定します。
exports.types = { "css": "text/css", "gif": "image/gif", "html": "text/html", "ico": "image/x-icon", "jpeg": "image/jpeg", "jpg": "image/jpeg", "js": "text/javascript", "json": "application/json", "pdf": "application/pdf", "png": "image/png", "svg": "image/svg+xml", "swf": "application/x-shockwave-flash", "tiff": "image/tiff", "txt": "text/plain", "wav": "audio/x-wav", "wma": "audio/x-ms-wma", "wmv": "video/x-ms-wmv", "xml": "text/xml" };