请求路由

一个HTTP请求由哪个PHP文件响应,实际上是Nginx或Apache决定的,默认情况下,服务器会去对应的文件目录找对应的PHP文件代码,让PHP解释器去执行。比如我们的请求为/backend/blogs.php,那它就需要真的在工程目录下,建立backend文件夹和blogs.php文件,对于只有几个页面的小项目,这种做法没什么问题,但这不符合现代网站的需求。比如我们的一个REST风格的请求形式/api/blogs/1,其中1只是个查询参数,我们不可能创建一个1.php

现在的Web框架,都有一个可配置的路由功能,对应URL由哪段代码相应,都应该是可配置的,而不是靠文件夹目录结构决定的。

Nginx配置

我们以Nginx为例进行配置,Apache的配置不同,但是原理是一样的。这里我们直接参考Laravel的路由配置:

server {
    listen 80;
    server_name example.com;
    root /example.com/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

其中我们主要关注下面:

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

上面配置的功能其实就是把所有请求连同请求参数发给index.php,而具体加载哪些代码,执行哪个函数或方法,则是我们代码决定的。

路由匹配和逻辑执行

我们代码包含如下两个文件:

其中index.php是我们代码的入口文件,Router.php是包含路由功能的一个工具类,由index.php引用。

Router.php

<?php

class Router
{
    private $arr = array();

    public function register($uri, $callback)
    {
        array_push($this->arr, array('uri' => $uri, 'callback' => $callback));
    }

    public function dispatch($uri)
    {
        for ($i = 0; $i < count($this->arr); $i++) {
            $rule = $this->arr[$i];
            if (strpos($uri, $rule['uri']) === 0) {
                $rule['callback']();
                break;
            }
        }
    }
}

其中register($uri, $callback)方法用于注册路由,被注册的路由配置维护在数组$arr中,函数参数是请求需要匹配的URL和回调函数。dispatch($uri)用于转发请求到对应的函数上执行。

index.php

<?php
include_once 'Router.php';

// 定义处理函数
$func1 = function () {
    echo '页面1';
};

$func2 = function () {
    echo '页面2';
};

// 注册路由
$router = new Router();
$router->register('/page1', $func1);
$router->register('/page2', $func2);

// 请求处理
$req_uri = $_SERVER['REQUEST_URI'];
$router->dispatch($req_uri);

上面代码首先定义了处理函数,这里只是个例子,在实际开发中,处理请求的应该是MVC的控制器组件,不应该出现在框架的入口函数中。

之后注册了两个路由/page1/page2,将它们对应到入口函数上。

最后要注意的是我们的请求虽然被Nginx重写到index.php上,但我们依然可以通过Nginx为我们设定的REQUEST_URI这个HTTP请求头来获取真实的请求URI,我们根据这个URI使用代码进行判断处理转发到哪个函数上就可以了。

总结

实际上,上面代码功能还很不全面,路由只能靠前缀匹配,也没做REST参数的处理,但实际上,各大框架路由实现的基本原理都是一样的,我们自己写路由组件可能漏洞比较多,但路由其实有很多现成的轮子可用,这里就不多介绍了。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。