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() 函数中设置响应不能在浏览器缓存。
所以为什么要 Laravel 背锅?
@leo 至少官方文档里应当写进去这点吧。
@yanyinge 浏览器的特性,和 Laravel 八竿子打不着,要是这都写文档,那文档不得成天书了
@leo 其实一笔带过的事没必要这么夸张,不写的话这里真是个坑,重定向响应可以设置成不缓存,不知道这里是不是有意为之。
@yanyinge Laravel 的代码里从来没有给重定向做缓存,也没有告知浏览器要缓存,是浏览器本身的实现自己给缓存了,不同的浏览器表现可能还不一样。
这在我看来确实是与 Laravel 没有任何关联,如果这个都写文档,那是不是还得写写 301 和 302 跳转在不同浏览器里(甚至是不同搜索引擎爬虫里)有什么区别?
尝试设置 http status code 200 试试? 或者 meta 禁用cache
@Noober Phper 这是一个跳转页面,status code 设置成 200 不符合规范,其实路由跳转定义这样写:
服务器就会告诉浏览器 『不要缓存这个页面,我是暂时跳转』,修改后这条路由跳转定义就是实时更新。
@yanyinge 还以为你不知道 301 和 302 的区别呢,既然知道区别,为啥还说是 bug 呢?
哈哈哈哈哈,楼主你都写了是301,那浏览器看到301就给你缓存了真的是没毛病呀