3

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';
xfra35
  • 3,833
  • 20
  • 23
NHG
  • 35
  • 4
  • You always have to use a leading slash / in your route definition. It’s $f3->route('GET /brew/@count',...), which is also in the samples – ikkez Aug 30 '18 at 22:47
  • @ ikkez Thanks. I did follow the sample in the first place, which route is $f3->route('GET /brew/@count',. what I got is the same: received uri: /brew/12 Not Found HTTP 404 (GET /12) – NHG Aug 31 '18 at 00:37
  • @ ikkez, I just updated the route in my post. But the result is the same. Thanks. – NHG Aug 31 '18 at 00:54
  • I just tried the same exact code with PHP 5.6 built-in web server and it's working fine. I suspect your error to originate from `$_SERVER['SCRIPT_NAME']`, which is `/index.php` in my case. NB: I'm using the framework [edge version](https://github.com/bcosca/fatfree-core/) and no `.htrouter.php` at all. What's the contents of your `.htrouter.php`? Have you tried skipping it? – xfra35 Aug 31 '18 at 07:02
  • Thanks xfra35. I just followed the guide and this should be very simple and straightforward, not sure what's going wrong. my .htrouter.php is – NHG Aug 31 '18 at 13:24
  • Thanks xfra35. I added the .htrouter.php in my post above. BTW, I'm using PHP 7.2. I include fatfree's base.php only in my index.php ($f3 = require_once(dirname(dirname(__FILE__)). '/lib/base.php');) – NHG Aug 31 '18 at 13:43
  • I just uploaded my code to http://xmjli.ihostfull.com/old/fatfree.zip – NHG Aug 31 '18 at 14:02
  • Ok I could reproduce your issue. Your public folder is `public/` so your should replace `-t ./` with `-t public/`. Also drop the `.htrouter.php`, it's not of any help. So: `php -S 0.0.0.0:8090 -t public/`. – xfra35 Aug 31 '18 at 16:17
  • Thanks xfra35! But I just wonder does this mean fatfree doesn't support URL rewrite? I thought it is pretty standard to use .htrouter.php or .htaccess to rewrite the URL. If I have sub folders for multiple microapps, then I can use .htaccess to do the request dispatch without starting multiple servers. – NHG Aug 31 '18 at 16:47
  • This is not related to the framework, but to the PHP built-in server itself. I'll write an answer to detail it. – xfra35 Sep 01 '18 at 18:02

3 Answers3

4

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__.

Community
  • 1
  • 1
xfra35
  • 3,833
  • 20
  • 23
  • xfra35, really appreciate your effort and the detail explanation. You are right. Since F3 uses $_SERVER['SCRIPT_NAME'], we'd better change it to 'index.php' instead of changing the code of f3. I updated my code and it works. Also, add chdir(__DIR__); to index.php is definitely a good idea as it solves the issue when I add the configuration file (unfortunately, f3 doesn't report any issue if the config file path/name provided to $f3->config is invalid). – NHG Sep 03 '18 at 08:23
0

OK, I checked the base.php and found out when f3 calculates the base URI, it uses $_SERVER['SCRIPT_NAME'].

$base='';
if (!$cli)
    $base=rtrim($this->fixslashes(
        dirname($_SERVER['SCRIPT_NAME'])),'/');

if we have web server directly forward all requests to index.php, then _SERVER['SCRIPT_NAME'] = /index.php, and in this this case, base is ''.

if we use URL rewriting via .htrouter.php to index.php, then _SERVER['SCRIPT_NAME'] = /brew/12, and in this this case, base is '/brew' which causes the issue.

Since I'm going to use the URL rewrite, I have to comment out the if statement and make sure base =''.

Thanks xfra35 for providing the clue.

NHG
  • 35
  • 4
  • This is not exactly the issue. I'll write an answer to detail it. Also I'll rename your question has the issue is clearly related to the behaviour of the PHP built-in server. – xfra35 Sep 01 '18 at 18:03
0

Apache like php router here: It can url rewrite. https://github.com/kyesil/QPHP/blob/master/router.php

Usage: php -S localhost:8081 router.php

JeWoPeR
  • 95
  • 6