从路径

时间:2018-04-13 17:43:00

标签: regex express

我正在尝试创建一个函数,给定一个路径,将返回用于该路径的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来翻译所有内容。我还需要翻译网站上的链接。但是,当我在法语页面上看到网址/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

我能想到的唯一解决方案就是生成所有网址到法国同行的映射,这有点粗略。如果可能的话,我真的宁愿使用一些有一流支持的东西。感谢您的意见。

2 个答案:

答案 0 :(得分:2)

如果我理解您的编辑,原始问题是当用户的浏览器是法语时,如何将链接/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`

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

注意事项:

  1. 此代码未经过测试,可能包含一些错误。我希望在不使用Express内部的情况下为您提供有关如何执行此操作的指导性建议。
  2. 这假设一个世界,你只是想找到匹配的第一条路线。
  3. 这假设您的应用中没有动态路由,您需要将其用于此工作(例如,无需对网址进行编程检查)。
  4. 不要通过包装器定义中间件。请定期使用app.use()

答案 1 :(得分:1)

我应该通过说快递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"