Search code examples
apache.htaccesspostrequest

Making a POST to a folder are recived as a GET


I'm making an api in php without using a framework and when I send a POST using postman to a folder the $_SERVER['REQUEST_METHOD'] on index.php is a GET.

Example of my folder organization:

📂api
 ┗ 📂engine
    ┗ 📜index.php
    ┗ 📜history.php

If i make a POST to

http://localhost/project/api/engine

the $_SERVER['REQUEST_METHOD'] on index.php is GET, but if i make a POST to

http://localhost/project/api/engine/

(additional slash at end) the $_SERVER['REQUEST_METHOD'] is POST

I tried to create a .htaccess with some rules provided by chatgpt (because I don't know anything about .htaccess) but nothing worked

How can i make the POST be a POST when i request the folder without the slash at the end?

My environment :
Windows 11
XAMPP as server
PHP 8.2.4


Solution

  • By default, Apache mod_dir appends the trailing slash to the physical directory in order to "fix"*1 the URL. This is achieved with a 301 (permanent) redirect. The "problem" here is that a 301 (and 302) tends to get converted to a GET request by the user-agent/browser on the redirected (2nd) request. And this naturally loses the POST data. (Aside: A 307 and 308 were created to resolve this issue as they preserve the request method through the redirect. Although, it goes without saying, you should never be linking/requesting a URL that redirects in the first place.)

    Requests to /project/api/engine/ directly "work" because no 301 redirect occurs.

    *1 The trailing slash is required in order to serve the DirectoryIndex document (ie. index.php) from that directory (which is what you are expecting).

    You can prevent mod_dir from appending the trailing slash with the DirectorySlash Off directive. However, you still need to manually append the trailing slash (with an internal rewrite), otherwise your DirectoryIndex document (index.php) is not going to be served. You also need to ensure that auto-generated directory listings (mod_autoindex) are disabled, otherwise a directory listing will be returned in the response even if a DirectoryIndex document is present in that directory.

    So, you could do it like this in the /project/api/.htaccess file (so as to restrict the directories affected):

    # /project/api/.htaccess
    
    # Disable auto generated directory listings (mod_autoindex)
    Options -Indexes
    
    # Prevent mod_dir appending a trailing slash to directories
    DirectorySlash Off
    
    # Need to use mod_rewrite ...
    RewriteEngine On
    
    # Internally append the trailing slash to directories
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^(.*[^/])$ $1/ [L]
    

    Note that you will need to clear any local caches since 301 (permanent) redirects are generally cached by default.


    Alternative - Change your file structure

    HOWEVER, I would avoid disabling DirectorySlash as this could result in additional issues depending on your system. And using a non-descriptive index.php file is not recommended anyway. Instead I would rethink your file structure a little and perhaps just remove the .php extension from your requests.

    For example, the file structure could be like the following instead:

    📂project
      ┗ 📂api
         ┗ 📜engine.php
         ┗ 📜history.php
    

    Your API requests would then be of the form /project/api/engine (and /project/api/history as opposed to /project/api/engine/history.php).

    In your /project/api/.htaccess you just need some code to append the .php extension via an internal rewrite. For example:

    # /project/api/.htaccess
    
    # Need to use mod_rewrite ...
    RewriteEngine On
    
    # Append the ".php" file extension where necessary:
    RewriteCond $1 !(\.php|/)$
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.php -f
    RewriteRule (.*) $1.php [L]
    

    Optionally, you could also block any direct requests to the .php files themselves.