fat-free-frameworkphp-builtin-server

Fatfree routing with PHP built-in web server


I'm learning fatfree's route and found it behaves unexpected.

Here is my code in index.php:

$f3 = require_once(dirname(dirname(__FILE__)). '/lib/base.php');
$f3 = \Base::instance();

echo 'received uri: '.$_SERVER['REQUEST_URI'].'<br>';

$f3->route('GET /brew/@count',
    function($f3,$params) {
        echo $params['count'].' bottles of beer on the wall.';
    }
);

$f3->run();

and here is the URL which I access: http://xx.xx.xx.xx:8090/brew/12

I get a 404 error:

received uri: /brew/12
Not Found

HTTP 404 (GET /12)

the strange thing is that the URI in F3 is now "/12" instead of "/brew/12" and I guess this is the issue.

When I check the base.php (3.6.5), $this->hive['BASE'] = "/brew" and $this->hive['PATH'] = "/12". But if F3 only uses $this->hive['PATH'] to match the predefined route, it won't be able to match them.

If I change the route to:

$f3->route('GET /brew',

and use the URL: http://xx.xx.xx.xx:8090/brew, then the route matches without issue. In this case, $this->hive['BASE'] = "" and $this->hive['PATH'] = "/brew". If F3 compares the $this->hive['PATH'] with predefined route, they match each other.

BTW, I'm using PHP's built-in web server and since $_SERVER['REQUEST_URI'] (which is used by base.php) returns the correct URI, I don't think there is anything wrong with the URL rewrite in my .htrouter.php.

Any idea? What did I miss here?

add the content of .htrouter.php here

<?php

#get the relative URL
$uri = urldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));

#if request to a real file (such as a html, image, js, css) then leave it as it is
if ($uri !== '/' && file_exists(__DIR__  . $uri)) {
    return false;
}

#if request virtual URL then pass it to the bootstrap file - index.php
$_GET['_url'] = $_SERVER['REQUEST_URI'];
require_once __DIR__ . './public/index.php';

Solution

  • Your issue is directly related to the way you're using the PHP built-in web server.

    As stated in the PHP docs, here's how the server handles requests:

    URI requests are served from the current working directory where PHP was started, unless the -t option is used to specify an explicit document root. If a URI request does not specify a file, then either index.php or index.html in the given directory are returned. If neither file exists, the lookup for index.php and index.html will be continued in the parent directory and so on until one is found or the document root has been reached. If an index.php or index.html is found, it is returned and $_SERVER['PATH_INFO'] is set to the trailing part of the URI. Otherwise a 404 response code is returned.

    If a PHP file is given on the command line when the web server is started it is treated as a "router" script. The script is run at the start of each HTTP request. If this script returns FALSE, then the requested resource is returned as-is. Otherwise the script's output is returned to the browser.

    That means that, by default (without a router script), the web server is doing a pretty good job for routing unexisting URIs to your document root index.php file.

    In other words, provided your file structure is like:

    lib/
        base.php
        template.php
        etc.
    public/
        index.php
    

    The following command is enough to start your server and dispatch the requests properly to the framework:

    php -S 0.0.0.0:8090 -t public/
    

    Or if you're running the command directly from the public/ folder:

    cd public
    php -S 0.0.0.0:8090
    

    Beware that the working directory of your application depends on the folder from which you call the command. In order to leverage this value, I strongly advise you to add chdir(__DIR__); at the top of your public/index.php file. This way, all subsequent require calls will be relative to your public/ folder. For ex: $f3 = require('../lib/base.php');

    Routing file-style URIs

    The built-in server, by default, won't pass unexisting file URIs to your index.php, as stated in:

    If a URI request does not specify a file, then either index.php or index.html in the given directory are returned

    So if you plan to define some routes with dots, such as:

    $f3->route('GET /brew.json','Brew->json');
    $f3->route('GET /brew.html','Brew->html');
    

    Then it won't work because PHP won't pass the request to index.php.

    In that case, you need to call a custom router, such as the .htrouter.php you were trying to use. The only thing is that your .htrouter.php has obviously been designed for a different framework (F3 doesn't care about $_GET['url'] but cares about $_SERVER['SCRIPT_NAME'].

    Here's an exemple of .htrouter.php that should work with F3:

    // public directory definition
    $public_dir=__DIR__.'/public';
    
    // serve existing files as-is
    if (file_exists($public_dir.$_SERVER['REQUEST_URI']))
        return FALSE;
    
    // patch SCRIPT_NAME and pass the request to index.php
    $_SERVER['SCRIPT_NAME']='index.php';
    require($public_dir.'/index.php');
    

    NB: the $public_dir variable should be set accordingly to the location of the .htrouter.php file.

    For example if you call:

    php -S 0.0.0.0:8090 -t public/ .htrouter.php
    

    it should be $public_dir=__DIR__.'/public'.

    But if you call:

    cd public
    php -S 0.0.0.0:8090 .htrouter.php
    

    it should be $public_dir=__DIR__.