1. 程式人生 > >Nodejs實現靜態伺服器

Nodejs實現靜態伺服器

檔案結構:

assets資料夾存放請求的檔案

app.js檔案實現伺服器,執行時執行node app

瀏覽器在3000埠請求url為http://localhost:3000/app.html

//nodejs實現一個靜態檔案系統
var url = require('url');
var http = require('http');
var fs = require('fs');  //涉及檔案讀取
var path = require('path');  //涉及路徑處理
var mime = require('./mime').types; //引入mime型別
var config = require('./config');

var server = http.createServer(function(request, response) {
   //獲得請求url並得到最終的路徑
    var pathname = url.parse(request.url).pathname;
   //檔案路徑字首為assets的具體路徑
    var realPath = "/Users/cmy/Desktop/nodeserver/assets" + pathname;
    
    //通過fs模組的fs.access方法來判斷靜態檔案是否存在
    fs.access(realPath, function (error) {
        if (!error) {
            //獲得副檔名,通過slice方法來剔除掉”.”,沒有後綴名的檔案認為是unknown
            var ext = path.extname(realPath);
            ext = ext ? ext.slice(1) : 'unknown';

            //設定contentType型別,支援多種mime型別
            var contentType = mime[ext] || 'text/plain';
            response.setHeader('Content-Type',contentType);

            //設定快取支援和控制,提高效能,減少IO操作
            fs.stat(realPath,function(err,stats){
            	//stats.mtime表示檔案最後一次被修改的時間。
                var lastModified = stats.mtime.toUTCString();
                var ifModifiedSince = 'If-Modified-Since'.toLowerCase();
                response.setHeader('Last-Modified',lastModified);

                //為指定的某幾種字尾的檔案設定有效時間為1小時,在config中配置
                if(ext.match(config.Expires.fileMatch)){
                    var expires = new Date();
                    //設定過期時間為當前時間加上maxage時間
                    //toUTCString() 方法可根據世界時 (UTC) 把 Date 物件轉換為字串,並返回結果。
                    //setTime和getTime都是以毫秒數設定或獲取時間
                    //max-age的單位是秒
                    expires.setTime(expires.getTime() + config.Expires.maxAge * 1000);
                    response.setHeader("Expires", expires.toUTCString());
                    response.setHeader("Cache-Control", "max-age=" + config.Expires.maxAge);
                }

                //判斷請求頭部的是否存在ifModifiedSince,並判斷lastModified是否等於ifModifiedSince
                //如果相等則說明檔案沒有修改,返回304
                if(request.headers[ifModifiedSince] && lastModified == request.headers[ifModifiedSince]){
                    response.writeHead(304,'Not Modified');
                    response.end();
                }else{
                //如果修改過則讀取檔案
                    fs.readFile(realPath, "binary", function(err, file) {
                        if (err) {
                            response.writeHead(500, {'Content-Type': 'text/plain'});
                            response.end(err);
                        } else {
                            response.writeHead(200, {'Content-Type': 'text/html'});
                            //response.write首次被呼叫時,會發送緩衝的響應頭資訊和響應主體的第一塊資料到客戶端
                            response.write(file, "binary");
                            response.end();
                        }
                     });
                }
            });
            
        }else{
            //如果檔案不存在返回404
            response.writeHead(404, {'Content-Type': 'text/plain'});
            response.write("This request URL " + pathname + " was not found on this server.");
            response.end();       	
        }
    });
});

server.listen(3000);
console.log('listenning at 3000');

新增gzip壓縮功能和路徑判斷補充功能的app.js

var url = require('url');
var http = require('http');
var fs = require('fs');  //涉及檔案讀取
var path = require('path');  //涉及路徑處理
var mime = require('./mime').types; //引入mime型別
var config = require('./config');
var zlib = require("zlib");  //使用gzip壓縮,引入原生模組zlib

var server = http.createServer(function(request, response) {
	//獲得請求url並得到最終的路徑
    var pathname = url.parse(request.url).pathname;
    
    ///結尾的請求,自動新增上”index.html
    if (pathname.slice(-1) === "/") {
        pathname = pathname + config.Welcome.file;
    }

    //使用normalize方法來處理掉不正常的/
    var realPath = path.join("/Users/cmy/Desktop/nodeserver/assets", path.normalize(pathname.replace(/\.\./g, "")));
    
    var pathHandle = function (realPath) {
    	//使用fs.stat處理路徑
        fs.stat(realPath, function (err, stats) {
            if (err) {
                response.writeHead(404, "Not Found", {'Content-Type': 'text/plain'});
                response.write("This request URL " + pathname + " was not found on this server.");
                response.end();
            } else { //如果請求的路徑沒有以/結尾,需要做判斷,看路徑是目錄還是檔案
                if (stats.isDirectory()) {
                	//如果是目錄則新增上/和index.html
                    realPath = path.join(realPath, "/", config.Welcome.file);
                    pathHandle(realPath);
                } else {
                	//獲得副檔名,通過slice方法來剔除掉”.”,沒有後綴名的檔案認為是unknown
                    var ext = path.extname(realPath);
                    ext = ext ? ext.slice(1) : 'unknown';
                    var contentType = mime[ext] || "text/plain";
                    response.setHeader("Content-Type", contentType);

                    //設定快取支援和控制,提高效能,減少IO操作
                    var lastModified = stats.mtime.toUTCString();
                    var ifModifiedSince = "If-Modified-Since".toLowerCase();
                    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);
                    }

                    //判斷請求頭部的是否存在ifModifiedSince,並判斷lastModified是否等於ifModifiedSince
                    //如果相等則說明檔案沒有修改,返回304
                    if (request.headers[ifModifiedSince] && lastModified == request.headers[ifModifiedSince]) {
                        response.writeHead(304, "Not Modified");
                        response.end();
                    } else {
                        //如果修改過則讀取檔案
                        //為了防止大檔案,也為了滿足zlib模組的呼叫模式,將讀取檔案改為流的形式進行讀取。
                        //對於支援壓縮的檔案格式以及瀏覽器端接受gzip或deflate壓縮,我們呼叫壓縮。
                        //若不,則管道方式轉發給response。
                        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(3000);
console.log('listenning at 3000');

 

config.js 和 mime.js


//指定字尾檔案和過期日期,config.js
exports.Expires = {
	fileMatch: /^(gif|png|jpg|js|css)$/ig,
	maxAge: 60*60
}

//對於圖片一類的檔案,不需要進行gzip壓縮,只壓縮三種檔案
exports.Compress = {
    match: /css|js|html/ig
};


//使用者請求了一個目錄路徑,而且沒有帶上/。那麼我們為其新增上/index.html,再重新做解析
exports.Welcome = {
    file: "index.html"
};




//content-type對應型別,mime.js
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"
};

轉載自:https://www.toolmao.com/nodejs-static-server