基于路由动态加载 Node.js 模块

Posted

技术标签:

【中文标题】基于路由动态加载 Node.js 模块【英文标题】:Loading Node.js modules dynamically based on route 【发布时间】:2012-06-10 12:00:38 【问题描述】:

我正在使用 express 在 Node.js 中做一个项目。这是我的目录结构:

root
|-start.js
|-server.js
|-lib/
|    api/
|        user_getDetails.js
|        user_register.js

lib/api/ 目录有许多与 API 相关的 JS 文件。我需要做的是制作一种挂钩系统,每当从 express HTTP 服务器请求一个 API 函数时,它都会执行相应 API 处理程序中指定的任何操作。这可能令人困惑,但希望您能理解。

    Larry 通过 POST 发送请求以获取用户详细信息。 服务器在lib/api 中查找与该请求关联的函数。 服务器执行操作并将数据发回给 Larry。

希望你能帮助我。我在想它可以使用原型来完成,但不确定。

谢谢!

【问题讨论】:

我不确定我是否理解。您只需执行var m = require('./lib/api/user_getDetails.js') 并在您的回复中使用该模块。我错过了什么吗? 我希望它动态加载。也就是说,如果我添加一个新的 API 函数,我不必手动要求它。但我不确定如何做到这一点。 @Fike 动态服务器端脚本?这听起来不太好。 :/ 你知道,经典解决方案被称为 classical 是有原因的。 :) 但是如果您知道自己在做什么,那么加载文件只是使用require('fs') 的问题。请参阅文档:nodejs.org/api/fs.html @FlorianMargaine 是的,但我不确定如何注入代码以便服务器路由请求。 @freakish 我会看看 :) 【参考方案1】:

如果您知道您的脚本在哪里,即您有一个初始目录,例如DIR,那么您可以使用fs,例如:

server.js

var fs = require('fs');
var path_module = require('path');
var module_holder = ;

function LoadModules(path) 
    fs.lstat(path, function(err, stat) 
        if (stat.isDirectory()) 
            // we have a directory: do a tree walk
            fs.readdir(path, function(err, files) 
                var f, l = files.length;
                for (var i = 0; i < l; i++) 
                    f = path_module.join(path, files[i]);
                    LoadModules(f);
                
            );
         else 
            // we have a file: load it
            require(path)(module_holder);
        
    );

var DIR = path_module.join(__dirname, 'lib', 'api');
LoadModules(DIR);

exports.module_holder = module_holder;
// the usual server stuff goes here

现在您的脚本需要遵循以下结构(因为require(path)(module_holder) 行),例如:

user_getDetails.js

function handler(req, res) 
    console.log('Entered my cool script!');


module.exports = function(module_holder) 
    // the key in this dictionary can be whatever you want
    // just make sure it won't override other modules
    module_holder['user_getDetails'] = handler;
;

现在,在处理请求时,您可以:

// request is supposed to fire user_getDetails script
module_holder['user_getDetails'](req, res);

这应该将所有模块加载到module_holder 变量。我没有测试它,但它应该可以工作(除了错误处理!!!)。您可能想要更改此功能(例如,将 module_holder 设为树,而不是单层字典),但我认为您会掌握这个想法。

这个函数应该在每次服务器启动时加载一次(如果您需要更频繁地触发它,那么您可能正在处理动态服务器端脚本,这是一个 baaaaaad 的想法,恕我直言)。您现在唯一需要做的就是导出 module_holder 对象,以便每个视图处理程序都可以使用它。

【讨论】:

如果您在启动时调用该函数一次,则没有理由使用异步版本;只需使用fs.lstatSyncfs.readdirSync。这也阻止了你吞下错误,因为异常会被抛出,而不是错误传递给回调然后被忽略。 @Domenic True。不知怎的,我习惯了异步编程,不再同步思考,呵呵。 :) 顺便说一句,你删除了我的 try catch 块。实际上,这里不需要它,因为即使模块抛出异常,脚本也会继续工作。但这对于同步版本不再适用! 很好的答案,但我仍然对如何让服务器使用模块感到困惑......一个理想的解决方案是以某种方式使用我可以扩展的基于原型的系统(如果这有意义的话)。无论加载什么脚本都应该有类似Hook.add("user_getDetails"); Hook.user_getDetails.action = function() console.log("user_getDetails method invoked!") ; 这样的东西,虽然我不知道它是如何工作的:P @Fike 没问题。在您的脚本中定义函数,例如module.exports = function(modules) modules["user_getDetails"] = 'test'; ;,并在我的加载程序中而不是modules[path]=m; 中执行require(path)(modules);。现在modules 对象恰好包含您想要的内容。缺点是您需要所有脚本都遵循相同的格式。 那服务器呢?这让我很困惑:/【参考方案2】:

app.js

var c_file = 'html.js';

var controller = require(c_file);
var method = 'index';

if(typeof controller[method] === 'function')
    controller[method]();

html.js

module.exports =

    index: function()
    
        console.log('index method');
    ,
    close: function()
    
        console.log('close method');    
    
;

稍微动态化这段代码,你可以做一些神奇的事情:D

【讨论】:

我觉得应该是 if(typeof(controller[method])=='function')【参考方案3】:

这是一个 REST API Web 服务的示例,它根据发送到服务器的 url 动态加载处理程序 js 文件:

server.js

var http = require("http");
var url = require("url");

function start(port, route) 
   function onRequest(request, response) 
       var pathname = url.parse(request.url).pathname;
       console.log("Server:OnRequest() Request for " + pathname + " received.");
       route(pathname, request, response);
   

   http.createServer(onRequest).listen(port);
   console.log("Server:Start() Server has started.");


exports.start = start;

路由器.js

function route(pathname, req, res) 
    console.log("router:route() About to route a request for " + pathname);

    try 
        //dynamically load the js file base on the url path
        var handler = require("." + pathname);

        console.log("router:route() selected handler: " + handler);

        //make sure we got a correct instantiation of the module
        if (typeof handler["post"] === 'function') 
            //route to the right method in the module based on the HTTP action
            if(req.method.toLowerCase() == 'get') 
                handler["get"](req, res);
             else if (req.method.toLowerCase() == 'post') 
                handler["post"](req, res);
             else if (req.method.toLowerCase() == 'put') 
                handler["put"](req, res);
             else if (req.method.toLowerCase() == 'delete') 
                handler["delete"](req, res);
            

            console.log("router:route() routed successfully");
            return;
         
     catch(err) 
        console.log("router:route() exception instantiating handler: " + err);
    

    console.log("router:route() No request handler found for " + pathname);
    res.writeHead(404, "Content-Type": "text/plain");
    res.write("404 Not found");
    res.end();



exports.route = route;

index.js

var server = require("./server");
var router = require("./router");

server.start(8080, router.route);

在我的例子中,处理程序位于 /TrainerCentral 子文件夹中,因此映射的工作方式如下:

localhost:8080/TrainerCentral/Recipe 将映射到 js 文件 /TrainerCentral/Recipe.js localhost:8080/TrainerCentral/Workout 会映射到js文件/TrainerCentral/Workout.js

这是一个示例处理程序,它可以处理 4 个主要 HTTP 操作中的每一个,用于检索、插入、更新和删除数据。

/TrainerCentral/Workout.js

function respond(res, code, text) 
    res.writeHead(code,  "Content-Type": "text/plain" );
    res.write(text);
    res.end();


module.exports = 
   get: function(req, res) 
       console.log("Workout:get() starting");

       respond(res, 200, " 'id': '123945', 'name': 'Upright Rows', 'weight':'125lbs' ");
   ,
   post: function(request, res) 
       console.log("Workout:post() starting");

       respond(res, 200, "inserted ok");
   ,
   put: function(request, res) 
       console.log("Workout:put() starting");

       respond(res, 200, "updated ok");
   ,
   delete: function(request, res) 
       console.log("Workout:delete() starting");

       respond(res, 200, "deleted ok");
   
;

使用“node index.js”从命令行启动服务器

玩得开心!

【讨论】:

我不是 100% 确定,因为我只是看了一眼代码,但这段代码没有一些目录遍历漏洞吗? ../../../ 只是为希望了解如何操作的人提供的示例。当然需要加强。 要小心,有些人会从这个站点复制和粘贴代码 xD 希望他们会阅读这些 cmets 并查看目录遍历漏洞。 -咳嗽-和你说话,读者。是的,就是你。 o_o

以上是关于基于路由动态加载 Node.js 模块的主要内容,如果未能解决你的问题,请参考以下文章

es6和node.js模块的区别

Nde模块篇

AngularJS - 基于路由动态加载外部 JS

Routerlinkactive在Angular 8中的页面加载时不适用于动态生成的路由

从 node.js 和 Typescript 中的动态文件名加载 JSON 的最佳方法是啥?

深入浅出Node.js 模块机制