Laravel 重定向路由 bug

如果要定义重定向到另一个 URI 的路由,可以使用 Route::redirect 方法。这个方法可以快速地实 现重定向,而不再需要去定义完整的路由或者控制器。

Route::redirect('/here', '/there', 301);

当我们在路由定义文件中定义重定向路由

Route::redirect('/here', '/there', 301);

当我们在浏览器访问 mydomain/here, 浏览器会跳转到 mydomain/there
接着修改上面定义文件

Route::redirect('/here', '/other', 301);

再次在浏览器访问 mydomain/here, 浏览器依然会跳转到 mydomain/there,而不是 mydomain/other

下面分析一下原因
我们看看这个 redirect 函数,在 Illuminate\Routing\Router 中:

    public function redirect($uri, $destination, $status = 301)
    {
        return $this->any($uri, '\Illuminate\Routing\RedirectController')
                ->defaults('destination', $destination)
                ->defaults('status', $status);
    }

redirect 函数定义了一个目标指向 RedirectController 的路由,同时把重定向地址和状态码设置为路由的默认参数。
下面看看 RedirectController 这个类:

 class RedirectController extends Controller
{
    /**
     * Invoke the controller method.
     *
     * @param  array  $args
     * @return \Illuminate\Http\RedirectResponse
     */
    public function __invoke(...$args)
    {
        list($destination, $status) = array_slice($args, -2);

        return new RedirectResponse($destination, $status);
    }
}

这是一个典型的单一操作的控制器,当我们访问想要重定向的地址时,返回的就是 Illuminate\Http\RedirectResponse ,看看它的构造函数,在 Symfony\Component\HttpFoundation\RedirectResponse 中:

    public function __construct(?string $url, int $status = 302, array $headers = array())
    {
        parent::__construct('', $status, $headers);

        $this->setTargetUrl($url);

        if (!$this->isRedirect()) {
            throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
        }

        if (301 == $status && !array_key_exists('cache-control', $headers)) {
            $this->headers->remove('cache-control');
        }
    }

        public function setTargetUrl($url)
    {
        if (empty($url)) {
            throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
        }

        $this->targetUrl = $url;

        $this->setContent(
            sprintf('<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="refresh" content="0;url=%1$s" />

        <title>Redirecting to %1$s</title>
    </head>
    <body>
        Redirecting to <a href="%1$s">%1$s</a>.
    </body>
</html>', htmlspecialchars($url, ENT_QUOTES, 'UTF-8')));

        $this->headers->set('Location', $url);

        return $this;
    }
}

构造函数中调用了 setTargetUrl() 函数 ,setTargetUrl() 设置响应的页面内容,设置 header 中的 location 为要跳转的目标地址。

回到问题上,为什么修改完 web 中的重定向路由不起作用。原因就是当我们第一次访问 mydomain/here 时,服务器返回了一个跳转到 mydomain/there 的响应,之后浏览器缓存这个响应。我们不管怎么修改web 中的重定向路由,mydomain/here 总是先访问浏览器原先缓存的响应,跳转到第一次定义的重定向的 mydomain/there。

解决办法,每次修改完 web 中的重定向路由,删除浏览器中的 mydomain/here 页面缓存。或者在 setTargetUrl() 函数中设置响应不能在浏览器缓存。

《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 9
leo

所以为什么要 Laravel 背锅?

5年前 评论

@leo 至少官方文档里应当写进去这点吧。

5年前 评论
leo

@yanyinge 浏览器的特性,和 Laravel 八竿子打不着,要是这都写文档,那文档不得成天书了

5年前 评论

@leo 其实一笔带过的事没必要这么夸张,不写的话这里真是个坑,重定向响应可以设置成不缓存,不知道这里是不是有意为之。

5年前 评论
leo

@yanyinge Laravel 的代码里从来没有给重定向做缓存,也没有告知浏览器要缓存,是浏览器本身的实现自己给缓存了,不同的浏览器表现可能还不一样。

这在我看来确实是与 Laravel 没有任何关联,如果这个都写文档,那是不是还得写写 301 和 302 跳转在不同浏览器里(甚至是不同搜索引擎爬虫里)有什么区别?

5年前 评论

尝试设置 http status code 200 试试? 或者 meta 禁用cache

5年前 评论

@Noober Phper 这是一个跳转页面,status code 设置成 200 不符合规范,其实路由跳转定义这样写:

Route::redirect('/here', '/there', 302) ;

服务器就会告诉浏览器 『不要缓存这个页面,我是暂时跳转』,修改后这条路由跳转定义就是实时更新。

5年前 评论
TimJuly

@yanyinge 还以为你不知道 301 和 302 的区别呢,既然知道区别,为啥还说是 bug 呢?

5年前 评论

哈哈哈哈哈,楼主你都写了是301,那浏览器看到301就给你缓存了真的是没毛病呀

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!