天天看點

試試用 Node.js 編寫個簡單的web架構

今天跟往常一樣逛技術社群,發現了一篇 《Express 簡單實作》 ( github

) ,寫的還是比較簡單易懂的。

剛好平時在測試比如區域網路共享檔案,臨時網頁共享或測試等等情況下可能會需要一個臨時的http服務。

當然一般情況下我都是使用Python指令

python -m http.server 8080

來做臨時服務,但是如果需要有動态請求時可能就不能用這種辦法了(當然啦你也可以使用Python的Bottle庫或Node.js的Express來編寫臨時服務) 。

剛好那就自己也嘗試着寫寫一個簡單的web架構呗。

1.一個很簡單的示例

simple_http.js

const http = require('http');
const url = require('url');
const fs = require('fs');

var routes = {//路由
    '/'(req, res) {
        res.end('關愛單身狗成長協會');
    },
    '/demo'(req, res) { //讀取html示例
        res.write(fs.readFileSync('./demo.html', 'utf-8'));
        res.end();
    },
    /** 自定義路由.... **/
    err(req, res, errCode) {//自定義異常
        res.writeHead(errCode, { 'Content-Type': 'text/html' });
        res.write(errCode.toString());
        res.end();
    }
};

http.createServer((req, res) => {//監聽
    let { pathname } = url.parse(req.url, true);//擷取通路路徑
    let route = routes[pathname];//比對路由
    try {
        return route ? route(req, res) : routes.err(req, res, 400);
    }
    catch (err) {
        console.error(err);
        routes.err(req, res, 500);
    }
}).listen(8080);
           

2.将其包裝成庫

simple_http.js

'use strict';
const http = require('http');
const url = require('url');
function createApplication() {
    let app = function (req, res) {//監聽
        let { pathname } = url.parse(req.url, true);//擷取通路路徑
        let route = app.routes[pathname];
        let r = { "req": req, "res": res, "path": pathname, "app": app, method: req.method.toLocaleUpperCase() };
        try {
            return route ? route.apply(r) : app.error404(r);
        }
        catch (err) {
            app.error(Object.assign(r, { err: err.toString(), errCode: 500 }));
        }
    };
    app.routes = {};//路由
    app.error = (r) => {//異常處理
        console.error(r);
        r.res.writeHead(r.errCode, { 'Content-Type': 'text/html' });
        r.res.write(r.errCode.toString());
        r.res.end();
    };
    app.error404 = (r) => app.error(Object.assign(r, { err: "path not find", errCode: 404 }));//404異常處理
    app.listen = function () {
        let server = http.createServer(app);
        server.listen(...arguments);
        return server;
    };
    return app;
}
module.exports = createApplication;
           

app.js

調用:

const app = require("./simple_http")();
const fs = require('fs');

app.routes["/"] = function () {
    this.res.end('關愛單身狗成長協會');
};
app.routes["/demo"] = function () { 
    this.res.write(fs.readFileSync('./demo.html', 'utf-8'));
    this.res.end();
};

let server = app.listen(8080, '0.0.0.0', function () {
    console.log(`listening at http://${server.address().address}:${server.address().port}`)
});
           

3.增加靜态檔案夾通路,控制台異常資訊輸出

simple_http.js

'use strict';
const http = require('http')
const url = require('url')
const utils = require("./utils");
function createApplication() {
    let app = function (req, res) {
        // 解析請求,包括檔案名
        let { pathname } = url.parse(req.url, true);
        let route = app.routes[pathname];
        let r = { "req": req, "res": res, "path": pathname, "app": app, method: req.method.toLocaleUpperCase() };
        try {
            return route ? route.apply(r) :app.serve(r, (e, res) => {if (e && e.status) app.error404(r);});
        }
        catch (err) {
            app.error(Object.assign(r, { err: err.toString(), errCode: 500 }));
        }
    };
    app.routes = {};
    app.error = (r) => r.res.end();
    app.error404 = (r) => app.error(Object.assign(r, { err: "path not find", errCode: 404 }));
    app.listen = function () {
        let server = http.createServer(app);
        server.listen(...arguments);
        return server;
    };
    return utils(app);
}
module.exports = createApplication;
           

utils.js

擴充:

const mime = require("./mime");
const fs = require("fs");
module.exports = function (app) {
    app.getClientIP = (req) => req.headers['x-real-ip'] || req.headers['x-forwarded-for']|| req.connection.remoteAddress;
    app.errorLog = (r) => console.error({ "path": r.path, "ip": app.getClientIP(r.req), err: r.err, errCode: r.errCode });
    app.error = (r) => {
        app.errorLog(r);
        r.res.writeHead(r.errCode, { 'Content-Type': 'text/html' });
        r.res.write(r.errCode.toString());
        r.res.end();
    };
    app.static = [];
    app.serve = (r) => {
        let path = r.path.replace(/^\/+/, "");
        if (app.static.length == 0) return app.error404(r);
        let s = app.static.find(_ => path.indexOf(_) == 0);
        if (s) {
            let t = mime.getType(path);
            return fs.readFile(path, function (err, fr) {
                if (err) {
                    app.error404(r);
                } else {
                    r.res.writeHead(200, { 'Content-Type': t });
                    r.res.write(fr);
                    r.res.end();
                }
            });
        }
    };
    app.moveFile = (op, np) => fs.renameSync(op, np);
    app.copyFile = (op, np) => fs.writeFileSync(np, fs.readFileSync(op));
    app.readJSON = (p) => JSON.parse(fs.readFileSync(p));
    app.writeJSON = (p, d) => fs.writeFileSync(p, JSON.stringify(d));
    app.readText = (p) => fs.readFileSync(p, 'utf-8');
    return app;
};
           

mime.js

檔案擴充名識别

const M = {
    getType(p) {
        let e = p.toLocaleLowerCase().split(".");
        return M[e[e.length - 1]] || M["*"];
    }, 
    "*": "application/octet-stream", 
    "tif": "image/tiff",
    //.........
    "css": "text/css",
    //.........
    "htm": "text/html",
    "html": "text/html",
    //.........
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg", 
    "js": "application/javascript",
    "json":"application/json",
    //.........
    };
module.exports = M;
           

app.js

const app = require("./simple_http")();
const fs = require('fs');

app.static.push("static");//加入靜态目錄

app.routes["/"] = function () {//加入路由
    this.res.end('關愛單身狗成長協會');
};

let server = app.listen(8080, '0.0.0.0', function () {
    console.log(`listening at http://${server.address().address}:${server.address().port}`)
});
           

好啦一個簡單的web架構,就寫的差不多了。大家可以參考着寫寫看,或比如加入:正則路由解析、模闆引擎、post請求處理、檔案上傳處理等等的功能。

文章源碼放在:

https://gitee.com/baojuhua/node_simple_http/tree/master