【问题标题】:Get Express Route Regex From Path从路径获取快速路由正则表达式
【发布时间】:2018-04-13 17:42:59
【问题描述】:

我正在尝试创建一个函数,在给定路径的情况下,该函数将返回用于该路径的 Express 路由正则表达式。如果我有:

app.get('/a/b/c/:slug(a-z)', (req, res) => {
  res.send('ok')
});

我希望能够有一个功能:

function getRoute(path) {
   // ...
}

getRoute('/a/b/c/whatever') // => '/a/b/c/:slug(a-z0-9*)'

在一个请求中,我可以在req.route 访问一个快速路由对象。它看起来像这样:

{  
   "path":"/a/b/c/:slug(a-z0-9*)",
   "stack":[ ... ],
   "methods":{  
      "get":true
   }
}

但我正在寻找一种在没有请求上下文的情况下执行此操作的方法,以便可以在任何地方调用它。我有app 上下文可用。 Express 应用程序中是否有某种路由工具可供我使用?

@jfriend00,这就是我想要做的。

我有一条路线,比如说:

app.get('/my-blog/:slug', (req, res) => {
  ...
});

并且该网站支持多种语言。假设我们有一篇名为“Hello World”的文章。在这种情况下,slug 是hello-world,所以路由是:

/my-blog/hello-world

不过,我也需要法语版本。所以现在的路线是:

app.get('/my-blog/:slug|/mon-blog/:slug', (req, res) => {
  ...
});

很酷,所以没问题。现在,该站点是服务器生成的,我正在使用i18n2 来翻译所有内容。我还需要翻译网站上的链接。但是,当我在法语页面上遇到 URL:/my-blog/hello-world 时,我需要将其翻译为 /mon-blog/bonjour-le-monde。假设一篇博客文章有一个法语和英语的 slug,并且可以被其中任何一个检索,那么所有这些 URL 都有效

  • /mon-blog/bonjour-le-monde
  • /my-blog/bonjour-le-monde(永远不会使用)
  • /mon-blog/hello-world(永远不会使用)
  • /my-blog/hello-world

如果用户的浏览器语言设置为法语,我还需要将到达/my-blog/hello-world 的用户重定向到/mon-blog/bonjour-le-monde

我能想到的唯一解决方案是生成所有 URL 到其法语对应项的映射,这有点恶心。如果可能的话,我真的宁愿使用具有一流支持的东西。感谢您的意见。

【问题讨论】:

  • 我不知道。 Express 中的匹配路由发生在传入请求对象的上下文中。您真正想在这里解决什么问题导致您提出这个要求?
  • Express 这样做是为了获得路由的请求路径,所以它必须在某个地方完成,不是吗?它是本地化 URL 解决方案的一部分,即使这些 URL 具有也能够本地化的参数。
  • 是的,但是当 Express 执行此操作时,它是在您说您不想拥有的传入请求对象的上下文中完成的。您必须使用 Express 数据结构的内部来编写自己的代码才能找到它。你为什么要这样做?我不禁认为可能有一种更受支持的方式来攻击你正在尝试做的任何事情。
  • @jfriend00 我已经更新了我的问题,提供了有关该问题的更多详细信息。
  • 那么,原来的问题是当用户的浏览器是法语时如何将链接/my-blog/hello-world翻译成/mon-blog/bonjour-le-monde(并且对页面中的所有链接都这样做)?

标签: regex express


【解决方案1】:

如果我正确理解您的编辑,最初的问题是当用户的浏览器是法语时如何将链接/my-blog/hello-world 翻译成/mon-blog/bonjour-le-monde(并对页面中的所有其他链接执行此操作)?

这是一个想法:

而不是使用app.get(),而是使用一些包装函数,该函数既可以向 Express 注册所需的路由,又可以创建查找路由以进行匹配的功能。然后,您无需使用 Express 中未记录的内部结构,只需将数据捕获到您自己的数据结构中,您可以随意使用它。这是一个可用于此目的的模块:

// this is the library that Express uses for converting express 
//   route definitions to regular expressions
const pathToRegexp = require('path-to-regexp');

// this is an array of arrays or route regular expressions
// the top level array will be in route definition order
// the sub arrays contain an object for each language and must be in a consistent language order
//   with English first and then other languages to follow in a consistent order
// For example the sub-array could be routes for English, French, German, Italian in that order
// The object for each language has properties:
//    route - original express route string
//    keys - keys returned by pathToRegexp
//    re - regular expression for this route
//    verb - http verb "get", "post", etc... for this route
const allRoutes = [];

// create one of these for each router you are defining matchable routes on
// Then, instead of app.get(...) to define your routes, do it like this:
// const appW = new RouterWrapper(app);
// appW.get(['/my-blog/hello-world', '/mon-blog/bonjour-le-monde'], (req, res) => { ... });
// This wrapper object will both register the route in Express and build a lookup mechanism for mapping routes
class RouterWrapper() {
    constructor(router) {
        this.router = router;
    }
    // common function used by all the verbs
    _register(routes, verb, ...fn) {
        // register route with express
        // join all of them together in a regex
        let joinedRoute = routes.join("|");
        this.router[verb](joinedRoute, ...fn);

        // save this set of routes in our master list
        allRoutes.push(routes.map(route => {
            let obj = {keys: [], route, verb};
            obj.re = pathToRegexp(route, obj.keys);
            return obj;
        }));
    }

    // pass in an English Route
    // returns first route that matches
    static getRouteData(englishRoute, languageIndex, verb = "get") {
        for (let data of allRoutes) {
            // english route is always in position 0 in the array
            if (data[0].verb === verb && data[0].re.test(englishRoute)) {
                return data[languageIndex];
            }
        }
        // not found
        return null;        
    }
}

// add actual verb methods
["get", "post", "put"].forEach(verb => {
    // all verb methods call common function
    RouterWrapper.protototype[verb] = function(path, ...fn) {
        return this._register(path, verb, ...fn;)
    };
});

module.exports = RouterWrapper;

这个想法是你会像这样使用它:

// usage
let RouterWrapper = require('router-wrapper');

let appWrapper = new RouterWrapper(app);

// you define these indexes based on how you order your URLs
const englishIndex = 0;
const frenchIndex = 1;

// define routes
appWrapper.get(['/my-blog/:slug','/mon-blog/:slug'], (req, res) => {
    ...
});

然后,查找特定的英文路径:

let routeData = RouterWrapper.getRouteData('/my-blog//hello-world', frenchIndex);
console.log(routeData.route);    // '/mon-blog/:slug`

根据您的 cmets,翻译 slug 将留给您,因为该信息不在路线定义中。


注意事项:

  1. 此代码未经测试,可能包含一些错误。我希望能给你一个方向性的想法,告诉你如何在不使用 Express 内部的情况下做到这一点。
  2. 这假设您只是试图找到匹配的第一条路线。
  3. 这假设您的应用中不存在您需要它来处理的动态路由(例如,没有对 URL 的编程检查)。
  4. 不要通过包装器定义中间件。对那些使用常规的app.use()

【讨论】:

  • 通过对getRouteData 函数进行一些更改,我能够使本地化双向工作。也就是说,我可以为它提供一个法语路径,并要求提供英文,它会返回英文 URL。我所做的只是遍历该路由的正则表达式并检查每一个,然后如果其中任何一个匹配,则从该路由返回请求的语言。非常感谢!
  • @JamieCounsell - 当您决定是否重定向并且不知道起始语言时,我可以看到它是如何工作的。对于页面中的翻译,我认为您只想查看英语路线(出于性能原因),特别是如果您有很多语言。无论如何,很高兴你有一个可以为你工作的想法。
【解决方案2】:

我应该先说 express 4 hides the router

此解决方案依赖于 express 的私有内部结构。谨慎行事。

快速路由器内部维护着一个数组。在处理请求时,它会一层一层地查看这些层并测试它们是否与请求路径匹配。

您可以使用类似于以下的方法来模拟 express 的内部行为。

    function getRoutePath(path) {
            var stack = app._router.stack;
            for (var i = 0; i < stack.length; i++) {
                    if (stack[i].route && stack[i].match(path)) {
                            return stack[i].route.path;
                    }
            }
    }

这需要一个路径并返回匹配的第一个路由的模式。

    app.get('/foo/:bar', (req, res) => {
            res.send('ok')
    });

    getRoutePath('/foo/gotcha'); // -> "/foo/:bar"

【讨论】:

  • 您将如何处理定义为应用程序一部分的多个路由器?也匹配路由的中间件?决定实际路由的程序化决策? app.post()app.get() 等不同 http 动词的处理程序,它们匹配相同的路由,但可能有不同的正则表达式?
  • 这就是使用私有内部来重新实现内部逻辑的麻烦所在。 ?
  • 糟糕,我花了太长时间来编辑。我想说:这些都是好点。这是使用私有内部来重新实现内部逻辑的麻烦。这是一个不完美的解决方案,但在简单的应用程序中使用可能就足够了。
  • 是的,我只是想让 OP 了解这些限制。
猜你喜欢
  • 1970-01-01
  • 2016-01-15
  • 2018-08-29
  • 1970-01-01
  • 2013-10-22
  • 2019-02-24
  • 1970-01-01
  • 2021-09-03
  • 1970-01-01
相关资源
最近更新 更多