samdark / yii2-cookbook

Yii 2.0 Community Cookbook

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Trailing slash (/): 302 redirect insted 301

sauron918 opened this issue · comments

By default Yii2 handles URLs without trailing slash and gives out 404 for URLs with it. I want handling both and doing 301 redirect from one variant to another.

I use this method

return [
    ...
    'on beforeRequest' => function () {
        $pathInfo = Yii::$app->request->pathInfo;
        $query = Yii::$app->request->queryString;
        if (!empty($pathInfo) && substr($pathInfo, -1) === '/') {
            $url = '/' . substr($pathInfo, 0, -1);
            if ($query) {
                $url .= '?' . $query;
            }
            Yii::$app->response->redirect($url, 301);
        }
    },
    ...
];

I get:

/catalog/test/ - 301 redirect to /catalog/test
/catalog/ - 302 (!) isntead 301 redirect

I noticed it is shown on some URLs, if the full path does not exist

class CatalogController extends Controller
{  
    // if you add will get 301, or forwarding will not take place
    public function actionIndex()
    {
        // ...
    }
}

But what to do with the URL like /catalog/10/20/, they always return 302 redirect instead 301?

Wait, shouldn't you get 404 in this case?

@samdark Thank's for response.

My problem:
I go to URL like /catalog/10/20/ that according to the rule
'catalog/<id:\d+>/<page:\d+>' => 'catalog/view'

And I got 302 redirect instead 301.

@samdark
Заметил что on beforeRequest отрабатывает, формируется Response с нужным ответом.

Но затем срабатывает обработчик handleException после которого 301 ответ по какой-то причине подменяется на 302.

Если URL вида /catalog/test/ и контроллер с представлением существуют, то все ОК 301 ответ.
Если URL вида /catalog/10/20/ описанное например правилом
'catalog/<id:\d+>/<page:\d+>' => 'catalog/view'
то в итоге мы получаем 302 ответ.

UPD. Поведение проявляется одинаково в dev и production окружении.

Я не вижу в контроллере выше actionView и его кода.

@samdark

public function actionView($id, $page)
{
        return $this->render('view', ['id' => $id, 'page' => $page]);
}

При вызове Yii::$app->response->redirect($url, 301); -- переадресация должна происходить через 301 редирект.

По факту, если URL на которых идет перенаправление не существует (или прописан через urlManager['rules'] как описано в примере выше), то происходит уже 302 редирект. Это поведение является не очевидным.

Воспроизводится очень просто:

'urlManager' => [
        'enablePrettyUrl' => true,
        'showScriptName' => false,
            'rules' => [                
                'version' => 'site/index',
            ],

При запросе domain.com/version/ должен произойти 301 редирект на domain.com/version который вызовет действие site/index. На деле так и происходит, но с 302 редиректом. Хотя в Yii::$app->response->redirect($url, 301); указан явно 301.

Тэк, я запутался. Можно наборчик из контроллера, конфига и URL, чтобы получить 302?

Контроллер

class CatalogController extends Controller
{  
    public function actionView($id)
    {
        return "View $id";
    }
}

Конфиг

'components' => [
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [                
                'catalog/<id:\d+>' => 'catalog/view',
            ],
        ],
    ],

    'on beforeRequest' => function () {
        $pathInfo = Yii::$app->request->pathInfo;
        $query = Yii::$app->request->queryString;
        if (!empty($pathInfo) && substr($pathInfo, -1) === '/') {
            $url = '/' . substr($pathInfo, 0, -1);
            if ($query) {
                $url .= '?' . $query;
            }
            Yii::$app->response->redirect($url, 301);
        }
    },

URL
Должно быть: /catalog/2/ -- 301 редирект на /catalog/2
Имеем: /catalog/2/ -- 302 редирект на /catalog/2

У меня наблюдается такая же проблема, не пойму как решить, а сеошники требуют 301 )

Same problem here. Something must have changed in yii2 framework between 2.0.6 and 2.0.7, because it used to work fine on 2.0.6.

Actually, 2.0.6 results in 302 redirect, while 2.0.7 results in 404. Both is not what you expect.

That's what I noticed during debugging:

  1. We set status code to 301 during on beforeRequest.
  2. After executing this callback, the framework continues evaluating the request, which results in 404 error (since original request cannot be resolved).
  3. Error handler kicks in, and even though original response object was filled with 301, it overrides it with 404. What I don't understand still, is how that changes to 302 in yii2 <= 2.0.6. Most likely there is some other event/callback triggering afterwards, but I could not find it. In 2.0.7 however, nothing magical seems to happen anymore - application just returns 404, which is quite logical according to the above.

So what we need to do, is prevent the framework from trying to evaluate original request in the first place, which is why we should add ->send(); and exit(1) in the end. Which brings me to the following code I have now, which works as expected:

'on beforeRequest' => function () {
    $pathInfo = Yii::$app->request->pathInfo;
    $query = Yii::$app->request->queryString;
    if (!empty($pathInfo) && substr($pathInfo, -1) === '/') {
        $url = '/' . substr($pathInfo, 0, -1);
        if ($query) {
            $url .= '?' . $query;
        }
        Yii::$app->response->redirect($url, 301)->send();
        exit(1);
    }
},

Would Yii::$app->end(); work instead of exit(1).

Yes, Yii::$app->end(); works as well, instead of exit(1).

Thanks for finding this out.

@samdark about this and your other article Using Redirects. I think that all can also be handled within an all-in-one custom UrlRule class. It turned out (as I never knew about) that there is SEO rules when it comes to choose between 302, 301 or canonical. I think it is better explained in this article. And a possible solution may be similar to this. What I suggest is a similar example as a second part to the Using Redirects article.

@tunecino yes. It could be handled that way but which way to use depends very much on the application. Here we're talking about trailing slashes only.