nodejs学習の実現簡易ルート

18858 ワード

これまではデータ転送機能が実現されていましたが、ローカルサーバを構築するには簡単なルート機能が必要です.ローカルサーバで自分のテスト用に使うだけなので、あまり完璧なルート機能は必要ないです.だから、expressフレームを使わずに、自分で簡単なルートを実現して、自分の需要に応じてルート機能をカスタマイズすることができます.
ルート機能を作る前に、まずルート表を書きました.自分がたぶん実現したい四つのルートの転換効果を表しました.この四つの効果はまさに自分のプロジェクトに必要です.
{
    "/my/**/*":"func:testFun",

    "index":"url:index.html",

    "test?v=*":"url:my*.html",

    "/public/bi*/**/*":"url:public/**/*"
}
第一に、私の住所がmy/my/*/**のフォーマットであれば、myディレクトリの任意のディレクトリの任意のファイルがtestFunをトリガするという意味です.例えば/my/test/index.または/my/1/2/3/index.はtestFunをトリガします.この方法をトリガするので、ルートはページ出力しません.
第二種類:つまり、私が/indexを訪問する時、index.ページを出力します.
第三種類:もし私がtest?v=indexと入力したら、出力のページはmyindexとなります.両方の*は数値が同じです.
第四種類:静的資源の取得に用いられます.私はpublic/bi*/**を訪問すると、public下の任意のファイルを出力します.例えば、私の要求経路はpublic/biz 009/styleesheets/css/main.cssです.それでは、経路が変換されたファイルの経路はpublic/stylistheets/css/main.cssです.
 
この四つの効果を実現する目的を持って、自分の実現を始めました.
第一段コードは、mimesの内容が長いので、代わりに…を使います.
まず正則を書いて、正則は主に***と**を置き換えるために使われ、正則文字列に置き換えられます.
次にRouterの構造関数を実現し、入ってきたパラメータを簡易的に処理し、着信したパラメータは直接上のオブジェクトでもいいし、jsonファイルのパスでもいいです.構造関数はevalでオブジェクトに変換します.JSON.parseを使わないのは、jsonフォーマットに対する要求が厳しいので、書きにくいからです.
後にイベントクラスを引き継ぎ、外部からのイベントバインディングを起動しやすくします.
"use strict";
var fs = require("fs");
var url = require("url");
var events = require("events");
var util = require("util");
var path = require("path");

var mimes = '...'.split(",");
var ALL_FOLDER_REG = /\/\*\*\//g;
var ALL_FOLDER_REG_STR = '/([\\w._-]*\/)*';  //  XXX/XXX/XX/
var ALL_FILES_REG = /\*+/g;
var ALL_FILES_REG_STR = '[\\w._-]+';  //  XX
var noop = function () {};

var Router = function (arg) {
    this.methods = {};

    if ((typeof arg == "object") && !(arg instanceof Array)) {
        this.maps = arg;
    } else if (typeof arg == "string") {
        try {
            var json = fs.readFileSync(arg).toString();
            this.maps = eval('(' + json + ')');
        } catch (e) {
            console.log(e);
            this.maps = {};
        }
    } else {
        this.maps = {};
    }

    this.handleMaps();
};

//     
util.inherits(Router, events.EventEmitter);

var rp = Router.prototype;

rp.constructor = Router;
上のコードの再構成関数では、ルーティングテーブルのルーティングアドレスとターゲットアドレスを処理してから、配列に保存するためのhandleMaps方法も実行されています.A_代表的な**B_*を表します.この二つも上記の正則文字列に対応しています.ALL_FOLDER_REG_STR 和 ALL_FILES_REG_STR
rp.handleMaps = function () {
    this.filters = [];  //      
    this.address = [];  //      

    for (var k in this.maps) {
        var fil = trim(k);
        var ad = trim(this.maps[k]);

        fil = fil.charAt(0) == "/" ? fil : ("/" + fil);

        ad = ad.replace(ALL_FOLDER_REG, '__A__').replace(ALL_FILES_REG, '__B__');
        fil = fil.replace(/\?/g , "\\?").replace(ALL_FOLDER_REG, '__A__').replace(ALL_FILES_REG, '__B__');

        this.filters.push(fil);
        this.address.push(ad);
    }
};
そして、functionを保存する方法も実現されます.ルーティングテーブルによって方法を実行するため、set方法があります.
rp.set = function (name, func) {
    if (!name)return;

    this.methods[name] = (func instanceof Function) ? func : noop;
};
前のすべてが実現されたら、具体的なルート方法を実現します.このコードは比較的簡単です.要求が発生した時に、上記に保存されているルートアドレスを経由して、ルートアドレスの中の__u u u uA_同前B_対応する正規文字列に変換し、RegExpによって正規例を実施し、要求アドレスを一致させる.一致が成功した場合、現在のインデックスiは、ターゲットアドレスのインデックスとなります.
そして文字列を分割し、urlであれば対応するurl処理を行い、functionであれば保存方法を実行し、req,resに入る.
rp.route = function (req, res) {
    var urlobj = url.parse(req.url);
    var pathname = urlobj.pathname;

    var i = 0;
    var match = false;
    var fil;

    for (; i < this.filters.length; i++) {
        fil = this.filters[i];
        var reg = new RegExp("^" + fil.replace(/__A__/g, ALL_FOLDER_REG_STR).replace(/__B__/g, ALL_FILES_REG_STR) + "$");

        if (reg.test(fil.indexOf("?") >= 0 ? (pathname = urlobj.path) : pathname)) {
            match = true;
            break;
        }
    }

    if (match) {
        var ad = this.address[i];
        var array = ad.split(':' , 2);

        if(array[0] === "url"){
            //   url     url   
            var filepath = getpath(fil , array[1] , pathname);

            this.emit("match", filepath , pathname);

            this.routeTo(res , filepath);
        }else if(array[0] === "func" && (array[1] in this.methods)){
            //   func      methods    
            this.methods[array[1]].call(this , req , res , pathname);
        }else {
            throw new Error("route Error");
        }
    }else {
        this.emit("notmatch");

        this.error(res);
    }
};
上のコードの中にはgetpath方法があります.この方法は***と**を実際のアドレスにマッピングしても、/public/biz 009/styleesheets/css/main.cssをpublic/styless/mail.cssに変換するロジックです.
function getpath(fil , ad , pathname){
    var filepath = ad;
    if(/__(A|B)__/g.test(fil) && /__(A|B)__/g.test(ad)){
        var ay = fil.split("__");
        var dy = ad.split("__");

        var index = 0;
        for(var k=0;k<ay.length;k++){
            if(!ay[k]) continue;

            var reg;
            if (ay[k] === 'A' || ay[k] === 'B') {
                reg = new RegExp(ay[k] === 'A' ? ALL_FOLDER_REG_STR : ALL_FILES_REG_STR);

                //    ,   AB      ,       ,  dy   ,     ay   ,        
                while(index < dy.length){
                    if(dy[index] === 'A' || dy[index] === 'B'){
                        if(dy[index] === ay[k]){
                            dy[index] = pathname.match(reg)[0];
                            index++;
                        }
                        break;
                    }
                    index++;
                }
            } else {
                reg = new RegExp(ay[k]);
            }

            pathname = pathname.replace(reg, '');
        }

        filepath =  dy.join("");
    }

    filepath = path.normalize(filepath);
    filepath = filepath.charAt(0) == path.sep ? filepath.substring(1,filepath.length):filepath;

    return filepath;
}
実現原理を説明します.まずルートの住所と目標の住所を配列に変えます.
/public/bi*/**/* ==> ['/public/bi','B','','A','','B']
public/**/*      ==> ['public','A','','B']
私が要請した時には/public/biz 009/styleesheets/css/main.cssになります.
['/public/bi','B','','A','','B']  ==>  ['/public/bi','z009','','/stylesheets/css/','','main.css']
そして上の「'public'、'A'、'、'B'」に対応します.
['public','A','','B'] ==> ['public','/stylesheets/css/','','main.css']
実現ロジックは以下の通りです.
要求されたpathnameはやはり/public/biz 009/styleesheets/css/main.cssです.スキャン['/public/bi','B',''A',''B']
スキャンの最初のものが'/public/bi'である場合、public/biを正則に変換し、マッチングによって/public/biz 009/stylistheets/css/main.cssを:z 009/styless/css/mail.cssに変更します.
二つ目のBをスキャンします.Bのために上のALL_を使います.FILES_REG_STRは「\w._-」+をマッチングさせてB対応のZ 009を取得し、同時にパス/styless/css/main.cssに変更してスキャンします. 「'public'、'、'、''B'」は、AまたはBにスキャンしたとき、対応するBではなくAなので、スキャンインデックスを更新しないので、上はindex+++ではなく、直接breakで、次のステップを継続します.
3番目の''をスキャンしますので、スキャンを続けても大丈夫です.
スキャンの4番目はAで、同じです.styleesheets/css/を取得して、pathnameをmain.cssにします.この時にスキャンします.「'public'、'、''A'、'、'B'」はスキャンインデックスがAに留まっています.その結果、両方ともAです.
その後、スキャンを続けます.スキャンが終わるまでは、「public」、「A」、「B」を「public」、「styleesheets/css/'、''main.css'」に変えます.
最後にまたjoinが出てきた結果:public/styless heets/css/main.cssは変換された最終パス、つまりマッチングされたファイルパスです.
 
もう一つの方法はファイルの内容を出力することです.一つは404です.比較的簡単です.詳細には説明しません.
rp.routeTo = function(res , filepath){
    var that = this;
    fs.stat(filepath , function(err , stats){
        if(err || !stats.isFile()){
            that.emit("error" , err || (new Error("path is not file")));

            that.error(res);
            return;
        }

        var fileKind = filepath.substring((filepath.lastIndexOf(".")+1)||0 , filepath.length);
        var readstream = fs.createReadStream(filepath);

        var index = mimes.indexOf('.'+fileKind);
        var options = {
            'Cache-Control':'no-cache',
            'Content-Type': mimes[index+1]+';charset=utf-8',
            'Content-Length':stats.size
        };
        res.writeHead(200, options);
        readstream.pipe(res);
    })
}

rp.error = function(res){
    res.writeHead(404);
    res.end("404 not found");
}
ソースはgithubにあります.
https://github.com/whxaxes/easy-router