【问题标题】:How to handle 404's with Regex-based routing?如何使用基于正则表达式的路由处理 404?
【发布时间】:2011-03-15 23:32:32
【问题描述】:

请考虑以下非常基本的“控制器”(为简单起见,本例中的函数):

function Index() {
    var_dump(__FUNCTION__); // show the "Index" page
}

function Send($n) {
    var_dump(__FUNCTION__, func_get_args()); // placeholder controller
}

function Receive($n) {
    var_dump(__FUNCTION__, func_get_args()); // placeholder controller
}

function Not_Found() {
    var_dump(__FUNCTION__); // show a "404 - Not Found" page
}

以及以下基于正则表达式的Route() 函数

function Route($route, $function = null)
{
    $result = rtrim(preg_replace('~/+~', '/', substr($_SERVER['PHP_SELF'], strlen($_SERVER['SCRIPT_NAME']))), '/');

    if (preg_match('~' . rtrim(str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $route), '/') . '$~i', $result, $matches) > 0)
    {
        exit(call_user_func_array($function, array_slice($matches, 1)));
    }

    return false;
}

现在我想将以下 URL(尾斜杠被忽略)映射到相应的“控制器”:

/index.php -> Index()
/index.php/send/:NUM -> Send()
/index.php/receive/:NUM -> Receive()
/index.php/NON_EXISTENT -> Not_Found()

这是事情开始变得棘手的部分,我有两个问题我无法解决......我想我不是第一个遇到这个问题的人,所以那里的人应该有解决方案。


捕获 404(已解决!)

我找不到一种方法来区分对根 (index.php) 的请求和不应该存在的请求 (index.php/notHere)。我最终为 URL 提供默认的 index.php 路由,否则应该提供 404 - Not Found 错误页面。 我该如何解决?

编辑 - 我脑海中闪过的解决方案:

Route('/send/(:num)', 'Send');
Route('/receive/(:num)', 'Receive');
Route('/:any', 'Not_Found'); // use :any here, see the problem bellow
Route('/', 'Index');

路线的顺序

如果我按“逻辑”顺序设置路线,如下所示:

Route('/', 'Index');
Route('/send/(:num)', 'Send');
Route('/receive/(:num)', 'Receive');
Route(':any', 'Not_Found');

所有 URL 请求都被 Index() 控制器捕获,因为空的正则表达式(记住:尾部斜杠被忽略)匹配所有内容。但是,如果我以“hacky”顺序定义路由,如下所示:

Route('/send/(:num)', 'Send');
Route('/receive/(:num)', 'Receive');
Route('/:any', 'Not_Found');
Route('/', 'Index');

一切似乎都可以正常工作。 有解决这个问题的优雅方法吗?

路由可能并不总是被硬编码(从数据库或其他东西中提取),我需要确保它不会因为定义的顺序而忽略任何路由。 感谢您的帮助!

【问题讨论】:

    标签: php regex routing controller routes


    【解决方案1】:

    好的,我知道给猫剥皮的方法不止一种,但你到底为什么要这样做呢?似乎是一些可以用 mod_rewrite 轻松处理的 RoR 方法

    话虽如此,我重写了您的 Route 函数并且能够实现您的目标。请记住,当您删除所有 / 时,我添加了另一个条件来直接捕获索引,这就是为什么当您希望它匹配 404 时它匹配索引。我还合并了 4 个 Route() 调用以使用一个 foreach()。

    function Route()
    {
            $result = rtrim(preg_replace('~/+~', '/', substr($_SERVER['PHP_SELF'], strlen($_SERVER['SCRIPT_NAME']))), '/');
            $matches = array();
    
            $routes = array(
                    'Send'      => '/send/(:num)',
                    'Receive'   => '/receive/(:num)',
                    'Index'     => '/',
                    'Not_Found' => null
            );
    
            foreach ($routes as $function => $route)
            {
                    if (($route == '/' && $result == '')
                            || (preg_match('~' . rtrim(str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $route)) . '$~i', $result, $matches) > 0))
                    {
                            exit(call_user_func_array($function, array_slice($matches, 1)));
                    }
            }
    
            return false;
    }
    
    Route();
    

    干杯!

    【讨论】:

    • 就像我在帖子中所说的,这只是一个示例,在现实世界中还有更多的路由,而且几乎所有路由都使用类控制器,mod_rewrite 做不到。我感谢您的努力,但我已经用另一种方法解决了丢失的 404 错误(请参阅我的 OP)-但无论如何感谢!您是否碰巧有定义订单路线的解决方案?
    • 你是如何设计你的 gravatar 的?
    • 面对你的漫画:faceyourmanga.com我非常喜欢它,我把它融入了我的网站设计中:)
    • 谢谢,我现在用它作为我的头像。这里仍然有一些人有一个类似的,但看起来像像素的头像。我想知道他们是怎么做到的。
    • 正确!把我链接到你在说什么,我有兴趣看到它...是时候更新我的头像了:)
    【解决方案2】:

    这是 MVC webapps 的一个常见问题,通常在成为问题之前就解决了。

    最简单、最通用的方法是使用异常。如果您没有给定参数的内容,则抛出 PageNotFound 异常。在您的应用程序的顶层,捕获所有异常,如下面的简化示例:

    index.php:

    try {
        $controller->method($arg);
    } catch (PageNotFound $e) {
        show404Page($e->getMessage());
    } catch (Exception $e) {
        logFatalError($e->getMessage());
        show500Page();
    }
    

    controller.php:

    function method($arg) {
        $obj = findByID($arg);
        if (false === $obj) {
             throw new PageNotFound($arg);
        } else {
             ...
        }
    }
    

    排序问题可以通过对正则表达式进行排序来解决,这样最具体的正则表达式首先匹配,最不具体的正则表达式最后匹配。为此,请计算正则表达式中的路径分隔符(即斜杠),不包括开头的路径分隔符。你会得到这个:

     Regex           Separators
     --------------------------
     /send/(:num)    1
     /send/8/(:num)  2
     /               0
    

    按降序排列,然后处理。流程顺序为:

    1. /send/8/(:num)
    2. /send/(:num)
    3. /

    【讨论】:

    • 这在非正则表达式环境中很棒,但我说的是正则表达式路由。无论如何,404问题解决了,你能提出一些解决订购问题的方法吗?
    • 我的解决方案与正则表达式无关。这是应用程序逻辑;它不关心您是否使用正则表达式来处理请求。这个想法是在最顶层的应用程序脚本中捕获异常,并对它们做出反应。您的订购问题只是一个问题,因为您没有跟踪规则范围。 /send/8/foo 比 /send/(:num) 更具体,而 /send 比 /send 更具体。
    • @jmz:我已经成功实现了你之前提到的逻辑,这不是我的问题。 =) 我还找到了使用:any 正则表达式模式处理404 错误的解决方案,正如您在我编辑的问题中看到的那样。关于路线的特殊性和顺序,您有什么巧妙的解决方案吗?
    • @Alix:在我之前的评论中,我建议您分析每个正则表达式的范围,例如通过计算它有多少个斜线,而不是计算正则表达式末尾的斜线。按此范围排序正则表达式,按此顺序降序和处理。第一个匹配的正则表达式将完成这项工作。
    • 好的,我阅读了您的帖子并检查了 phunction 框架。因此,您对Route() 的设计选择使得在框架中对路由进行排序是不可能的。你真正需要的是文件——从空缺职位来看——你已经知道了。说到文档,phunction 框架将受益于 cmets。它会更容易阅读、使用和理解。
    【解决方案3】:

    首先确定如下:

    foo.com/index.php/more/info/to/follow 
    

    完全有效,按照标准应该加载 index.php 并将 $_SERVER[PATH_INFO] 设置为 /more/info/to/follow。这是CGI/1.1 standard。如果您希望服务器不执行 PATH_INFO 扩展,请在您的服务器设置中将其关闭。在 apache 下使用:

    AcceptPathInfo Off
    

    如果你在 Apache2 下将它设置为 Off ... 它会发出 404。

    我不确定 IIS 标志是什么,但我想你可以找到它。

    【讨论】:

    • 您至少阅读了整个问题吗?我不明白这有什么资格回答我的任何问题......哦,顺便说一句,你不能总是依赖PATH_INFO,请参阅stackoverflow.com/questions/1884041/…
    猜你喜欢
    • 1970-01-01
    • 2019-04-29
    • 2015-01-06
    • 2020-10-10
    • 2019-01-01
    • 2014-04-14
    • 2012-09-23
    • 1970-01-01
    相关资源
    最近更新 更多