Framework: Public directory breaks urls structure

Created on 30 Jul 2019  路  10Comments  路  Source: laravel/framework

  • Laravel Version: 5.8.29
  • PHP Version: 7.1.19
  • Database Driver & Version: -

Description:

Hi, while building my website I noticed that a specific menu link didn't redirect correctly to the assigned path. By looking on the network section of the browser I saw that each time I click on that specific link the server respond with a "301 Permanently redirect" message and convert the path from an https to an http, causing also, a "mixed-content error message".

I start investigating the problem and find that the only place where there is a 301 redirect is inside the public .htaccess file:

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} (.+)/$
    RewriteRule ^ %1 [L,R=301]

Here I notice that these rules checks if the path is a folder then, if true, the rule take place.

Well, the link I was using:

 Route::get( '/videos', 'VideosController@index' )->name( 'videos.index' );

and here is the problem, I have created a resources folder called /videos where I put all website generic video files then in webpack.mix.js

mix.copyDirectory('resources/videos', 'public/videos');

Et voil脿! The url structure break!
I'm not sure if it's a known issue or if it's intended to work like this but it's a strange behaviour.

I don't know how to fix it.. :(
Thank you guys

Steps To Reproduce:

  1. Install a fresh new laravel instance
  2. Force url scheme in AppProvider.php
    public function boot()
    {
        URL::forceScheme( 'https' );
    }
  1. Open the route on web.php
Route::get( '/videos', function() {} )->name( 'videos.index' );
  1. Add a folder inside your resources path with the same name as the route, for example: resources/videos

  2. publish the folder with webpack:

mix.copyDirectory('resources/videos', 'public/videos');
  1. composer install
  2. npm install
  3. npm run dev
  4. visit the route:

GET - https://you.com/videos
REDIRECT - http://you.com/videos/

All 10 comments

This does not sound like a Laravel bug, but rather you trying to use the same HTTP Request to serve two different resources; the first is a resource folder managed by apache (via .htaccess) and the other a Laravel route.
You cannot have one HTTP Request serve two different resources.

To fix your problem either
1) Change your Laravel route to something else
2) Move your public resources to a nested folder (Ex: 'public/resources/videos')

Hi @gofish543

is /videos the same as /videos/ ?

In apache's eyes, yes.

Your .htaccess fille

# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]

This is the apache thought process with your rules
A request for https://you.com/videos/

  • Request comes in at https://you.com/videos/
  • Apache translates https://you.com/ -> /var/www/you/ (Via VirtualHost)
  • Apache loads that directories .htaccess file if have Allow Override
  • The Rewrite Rules Are Encountered
    Note REQUEST_FILENAME = /var/www/you/videos/
    Note REQUEST_URI = https://you.com/videos/

RewriteCond %{REQUEST_FILENAME} !-d // Is it not a directory?
RewriteCond %{REQUEST_URI} (.+)/$ // And Is it ending in a '/'?

In this case it ends in a '/' AND it is a directory so a redirect occurs, and we are sent to https://you.com/videos
The process is repeated, but due to additional rules in Laravel's default installation, the directory is loaded rather than Laravel's index.php

This is the apache thought process with your rules
A request for https://you.com/videos

  • Request comes in at https://you.com/videos
  • Apache translates https://you.com/ -> /var/www/you/ (Via VirtualHost)
  • Apache loads that directories .htaccess file if have Allow Override
  • The Rewrite Rules Are Encountered
    Note REQUEST_FILENAME = /var/www/you/videos
    Note REQUEST_URI = https://you.com/videos

RewriteCond %{REQUEST_FILENAME} !-d // Is it not a directory?
RewriteCond %{REQUEST_URI} (.+)/$ // And Is it ending in a '/'?

In this case it is neither so a redirect does not occur and we continue down the .htaccess condition list. Due to additional rules in Laravel's default installation the directory is loaded rather than Laravel's index.php

Then, for example, serving images from a folder called /images/branding/googlelogo/2x/googlelogo_color_92x30dp.png (like google) makes that my website cannot have a section called /images to list somebody images. A bit forced imho.

Keep in mind, throughout our whole process, we have not once touched Laravel.

To solve your problem quick and simple and maintain the conflicting folder spaces you could implement a dual virtual host setup.
Your first v-host: https://you.com can force all traffic directly into index.php (Directing it to Laravel)
Your second v-host https://static.you.com can attempt to load up the directory/file

For your most recent example with Google's images... they probably don't use apache, but rather a custom solution.
If you actually go to https://google.com/images it redirects you to https://www.google.com/imghp
Meaning they have a redirect rule saying if you go to /image/... and the file does not exist, redirect to /imghp (Or something like that)

I thank you for the time you spend on answering.

Does FallbackResource directive changes the matter of the question?

vhosts.conf

<Directory "/var/www/vhosts">
 FallbackResource "/index.php"
</Directory>

this should force each request on /index.php front controller, that is Laravel.

I am updating my previous answer. When I was looking at my own personal .htaccess files and my documentation I found discrepancies. Give me a little bit.
https://httpd.apache.org/docs/2.2/mod/mod_rewrite.html#rewritecond

I apologize, I got mixed up myself. I forgot that multiple RewriteConds are implicit AND and only become 'OR' when explicitly stated, causing all of my previous logic to fall apart in glorious flames

Now let's try to solve your problem, serving both out of the same virtual host

Because you are serving a conflicting HTTP Request space, Laravel's default .htaccess will not work for you (but we already know that)

.htaccess file

<IfModule mod_rewrite.c> // Makes sense, don't use something that doesn't exist
    <IfModule mod_negotiation.c> // Security, don't list indexes, etc.
        Options -MultiViews -Indexes // Don't list indexes, and whatever multi views is (I actually don't know)
    </IfModule> // Ending tag

    RewriteEngine On // Turn on the rewrite engine

    // We don't care about the below rule
    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} . 
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    // End of rule we don't care about

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d // If the target is not a directory 
    RewriteCond %{REQUEST_URI} (.+)/$ // And if the target ends in a slash
    RewriteRule ^ %1 [L,R=301] // Redirect removing the trailing slash

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d // If the target is not a directory
    RewriteCond %{REQUEST_FILENAME} !-f // And if the target is not a file
    RewriteRule ^ index.php [L] // Give the request to Laravel 
</IfModule>

It is now that we see the underlying issue, not the redirect, but rather the secondary set of rules. Since our target is a directory, index.php is never loaded causing Laravel to never load

To solve this, remove RewriteCond %{REQUEST_FILENAME} !-d // If the target is not a directory
This will break directory listings because any directory request will be rewritten to index.php.

<IfModule mod_rewrite.c> // Makes sense, don't use something that doesn't exist
    <IfModule mod_negotiation.c> // Security, don't list indexes, etc.
        Options -MultiViews -Indexes // Don't list indexes, and whatever multi views is (I actually don't know)
    </IfModule> // Ending tag

    RewriteEngine On // Turn on the rewrite engine

    // We don't care about the below rule
    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} . 
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    // End of rule we don't care about

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d // If the target is not a directory 
    RewriteCond %{REQUEST_URI} (.+)/$ // And if the target ends in a slash
    RewriteRule ^ %1 [L,R=301] // Redirect removing the trailing slash

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-f // And if the target is not a file
    RewriteRule ^ index.php [L] // Give the request to Laravel 
</IfModule>

So now let's go all the way back to the start and chunk through what will happen with each request

Request: https://you.com/videos/ ->
Is Directory, short circuit RewriteRule > Nothing Happens
Is not a file -> https://you.com/index.php

Request: https://you.com/videos ->
Is Directory, short circuit RewriteRule -> Nothing Happens
Is not a file -> https://you.com/index.php

Request: https://you.com/videos/personal/5.mov
Is Not Directory, Does not end in slash -> Nothing Happens
Is a file -> https://videos/personal/5.mov Served

If you want to list directory contents, like a content server and then they download the video contents or something like that, this solution will not work.
You could consider...

  • Moving your /videos to another folder
  • Moving /storage/app/videos/ and using Laravel's to list directory contents and it's built in $this>download(storage_path('app/video/')) to download the video
  • Moving content serving to a separate virtual host, and make sure to update the .htaccess via the above answer

Hi there,

Thanks for reporting but it looks like this is a question which can be asked on a support channel. Please only use this issue tracker for reporting bugs with the library itself. If you have a question on how to use functionality provided by this repo you can try one of the following channels:

However, this issue will not be locked and everyone is still free to discuss solutions to your problem!

Thanks.

Thank you @gofish543 , I choose to move the public folders to a different location on webpack.mix.js for now, it's the least invasive solution.

mix.copyDirectory('resources/images', 'public/resources/images');
mix.copyDirectory('resources/videos', 'public/resources/videos');

I want to leave the framework clean, I'll address the problem when necessary in the future.

Was this page helpful?
0 / 5 - 0 ratings