寒夏摸鱼站

Live your dream, and share your passion.

关于地址重写与路由分发的研究

什么是地址重写

注:由作者本人经验总结,不保证定义准确性,只当大致了解即可

地址重写又称 URL 重写,是由服务器所支持的,可以用来隐藏真实的地址以及实现伪静态功能。

地址重写会遵循一套特定的规则,而不同的服务器框架会用不同的规则,一般常见的服务器框架有 IIS、Apache 和 Nginx,他们的重写规则不完全相同。

简单来讲,服务器在对地址进行重写时,做的就是替换工作,如把:https://xxxxxx.xxx/p/123 给换成 https://xxxxxx.xxx/passage.php?id=123,我们通过这种方法隐藏了真正起作用的 API —— passage.php,可以一定程度上防止被扒裤衩,还可以使得 URL 更加漂亮。

什么是路由分发

注:由作者本人经验总结,不保证定义准确性,只当大致了解即可

当然在网站里肯定不止有一个简简单单的 /p/123,还有很多诸如 /catagory/none/tag/none 等其他不同的 API,你当然可以直接在地址重写里面把这些 API 全部实现进去,但是还有另一种方法,那就是路由分发。

在使用路由分发机制时,我们一般会把重写规则变成如下(简单示意):

https://xxxxxx.xxx/p/123 ==> https://xxxxxx.xxx/index.php?p/123

非常简单,就是把 URL 的路径截出来,直接放在主页后面,转换成查询字符串的模式,这样我们在 index.php 里直接调用 $_SERVER['QUERY_STRING'] 就能获得 p/123

然后就是路由分发发挥作用的时候了 —— 把 p/123 解析成对应的 API 地址,然后调用。

这种处理伪静态 URL 的方法也是 Typecho(v1.2) 目前所使用的方法。

路由分发的背后

地址重写比较简单,只需要在网上找找不同服务器框架的重写规则,你也可以很快地写出一份 rewrite 文件,真正好玩的其实是路由分发。

路由分发的精髓就是解析查询字符串。访问一个 URL 的时候其实我们基本上就需要处理两个东西 —— GET 请求和 POST 请求,其中 POST 请求我们根本不需要管,因为它不会出现在查询字符串里,重要的是处理查询字符串中的 GET 请求。

因为此时我们已经完全脱离了服务器自己的 GET 查询系统(整个查询字符串都被我们用来当路由了),所以只需要根据自己的需求来解析就可以了。一般情况可以用大量的正则匹配来完成(同样是 Typecho 的做法),把查询字符串解析成标准的 URL 访问 API,或者直接在路由分发系统里面调用 API,都可以起到隐藏 API 的功能。

路由分发除了隐藏 API 以外,还可以用来把外链转换成内链,如果遇上了某些又臭又长的外链非常有用,这也是博客系统经常用的功能。

傻瓜般的路由分配系统

这个例子是傻瓜式匹配路由路径,以 @ 作为分隔符来划分路径部分和 GET 部分,最终的 URL 呈现大概是这样:https://xxxxxx.xxx/index.php?action/link@do=get&id=123

如果你再进一步进行地址重写,你可以把它以假乱真成这样:https://xxxxxx.xxx/action/link@do=get&id=123,这个有点碍眼的小问号就没了。当然你还可以修改一下代码,把 @ 给换成 ?,让这段 URL 看起来更正常一点:https://xxxxxx.xxx/action/link?do=get&id=123

我们选择在路由系统内部调用 API(include 实现)。在 API 中直接调用 rRouter::$queryStringrRouter::$getrRouter::$post 可以分别得到真正的查询字符串 do=get&id=123、get 数组(即代替原来的 $_GET)和 post 数组(原 $_POST)。

add 函数是用来添加路径的,当然理想状态是用正则匹配啥的,但是简单的查表对应也够了。

to404 函数看字面就知道是干嘛的了。

despatch 函数就是真正用来进行路由分发的,所有的解析都发生在这里。

<?php
// rRouter.php
class rRouter {
  static public $queryString, $get, $post;
  static private $p_list = array();

  static public function add($route, $path) {
    self::$p_list[$route] = $path;
  }

  static public function despatch() {
    self::$post = $_POST;
    $qry_ = $_SERVER['QUERY_STRING'];

    // 处理查询字符串
    $qpos_ = stripos($qry_, '@');
    if ($qpos_ !== false) {
      $qpth_ = substr($qry_, 0, $qpos_);
      self::$queryString = substr($qry_, $qpos_ + 1);
    }
    else {
      $qpth_ = $qry_;
      self::$queryString = '';
    }

    // 如果路径不存在,跳转404
    if (!array_key_exists($qpth_, self::$p_list))
      self::to404();

    // 处理查询字符串,把它解析成$_GET的样子
    self::$get = array();
    if ($qpos_ !== false) {
      $ar_ = explode('&', self::$queryString);
      foreach ($ar_ as $value_) {
        $pr_ = explode('=', $value_);
        self::$get[$pr_[0]] = $pr_[1];
      }
    }

    // 直接调用API
    include(self::$p_list[$qpth_]);
  }

  static public function to404() {
    header('HTTP/1.0 404 Not Found');

    // 支持自定义404页面
    if (array_key_exists('404', self::$p_list))
      include(self::$p_list['404']);
    exit;
  }
}

最后 index.php 里面用 add 把页面啥的定义好,再一个 despatch 就完事了。

<?php
// index.php
// 加载模块
include_once('rRouter.php');

// 你想把路径和哪个php页面相关联?写就是了
rRouter::add('picture', 'pic.php');
rRouter::add('audio', 'au.php');
rRouter::add('link', 'lk.php'); 

// 最后开始路由分发
rRouter::despatch();

最后

其实把路由分发模块做好了,就可以拿去到处用了。。。

写了那么多,感觉更多地是在谈路由分发呢。。。

路由分发能做的,地址重写也可以做,但是地址重写要修改 rewrite 文件,可能需要重启网站服务才能生效(?),路由分发可以有更大的自由度,当然注意一下 BUG 就行了。