apache.htaccessmod-rewritemod-dir

How do I make Apache use 308 for DirectorySlash?


I am having an issue with redirects when posting form data to an Apache HTTP Server. When DirectorySlash is enabled, mod_dir is signaling an HTTP 301 to my client, which is causing the HTTP method to change from POST to GET.

Instead of using the built-in directory slashing feature, I wanted to manually perform the rewrite so that I can signal HTTP 308 (thus preserving the request method):

DirectorySlash Off
RewriteEngine On

# If the path is a directory
# AND
# If the path does NOT have a trailing slash
RewriteCond %{REQUEST_FILENAME}/ -d
# Also tried this: RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_URI} !/$

# Add a trailing slash with HTTP 308
RewriteRule ^ %{REQUEST_URI}/ [R=308,L]

However, this is not resulting in a redirect of any sort. Is there a way I can configure mod_dir to use 308 instead of 301?


Solution

  • Bear in mind that 301 (permanent) redirects are cached persistently by the browser, so all caches must be cleared before testing.

    Is there a way I can configure mod_dir to use 308 instead of 301?

    Unfortunately not. Overriding with mod_rewrite is the only way. (Although form submissions to the non-canonical URL is arguably the error here.)

    In addition to setting DirectorySlash Off, you'll likely also need to set RewriteOptions AllowNoSlash to allow mod_rewrite to match against directories that do not end in a slash.

    If the path does NOT have a trailing slash
    RewriteCond %{REQUEST_FILENAME}/ -d
    

    REQUEST_FILENAME is a computed value based on the URL requested and underlying filesystem. I would not use that here. You do not need to append a slash to test for a directory (the directory name itself does not end in a slash). Instead, construct the absolute filename (ie. "directory") from the DOCUMENT_ROOT and requested URL-path.

    RewriteCond %{REQUEST_URI} !/$
    

    Testing that the requested URL-path does not end in a slash should be performed in the RewriteRule pattern, not a condition. It is the RewriteRule pattern that is processed first.

    Note that this rule needs to go at the top of the .htaccess file, before any existing rewrites.

    Try the following instead:

    DirectorySlash Off
    
    RewriteEngine On
    RewriteOptions AllowNoSlash
    
    # Add a trailing slash with HTTP 308 if a directory without a trailing slash
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
    RewriteRule [^/]$ %{REQUEST_URI}/ [R=308,L]
    

    I didn't use the pattern !/$ as we don't want to trigger the redirect for the document root (special case).

    This could be further optimised by including a condition that excludes requests that look-like files (your static assets), ie. that have file extensions. Assuming your directory names don't include dots in the last 4 characters then add the following condition before the above:

    RewriteCond %{REQUEST_URI} !\.\w{2,4}$
    :
    

    Needless to say, having to externally redirect form submissions is not optimal. Ideally the form should be "fixed" to submit to the canonical URL.


    However, why redirect at all? (This is a form submission that presumably cannot be changed in the HTML - otherwise it would be preferable to correct the URL target in the form.) You could just internally rewrite the request instead, to append the trailing slash. This does assume you are consistent in your public URLs and all form submissions are without the trailing slash. (Although, preferably, you would rewrite directly to the file that handles the request and not rely on mod_dir returning the DirectoryIndex, which is server dependent.)

    For example:

    DirectorySlash Off
    
    RewriteEngine On
    RewriteOptions AllowNoSlash
    
    # Add a trailing slash to directories with an internal rewrite
    RewriteCond $0 !\.\w{2,4}$
    RewriteCond %{DOCUMENT_ROOT}/$0 -d
    RewriteRule .+[^/]$ $0/ [L]
    

    Since this is an internal rewrite I used the $0 backreference (and changed the RewriteRule pattern so that it matches the full URL-path) instead of the REQUEST_URI server variable in order to omit the leading slash on the substitution string. This ensures that it's rewritten directly to a file-path, rather than a URL-path (same result in most cases, but more efficient).