Personal Home Page Tools
1.0.8 是我们在官网上能够下载到的最古老的版本,虽然这个时候也叫 PHP,但是和现在的 PHP 意思是完全不同的,下面这张图是在源代码中的截图。
PHP Tools - Personal Home Page Tools
个人网站工具集,严格意义上来说,这个时候的 PHP 只是一个 CGI 的工具集,它包含了一个非常简单的语法分析引擎,只能理解一些指定的宏和一些Home Page后台的常见功能,如留言本,计数器和一些其他的素材。
Rasmus Lerdorf,就是这个家伙,在 1995 年仅用了一个下午就完成了 PHP 的第一个版本,对我们的生活其实产生了深远的影响。在 2004 年 PHP 更是成为了年度语言,并且在其后很长的一段时间内保持着前五,虽然近些年来 PHP 一直在被唱衰,甚至在国内大厂不断地用 Go 代替 PHP 的环境下,在 2023 年仍然没有跌出前十。
PHP 源代码下载,历史的归档源代码我们可以到 https://museum.php.net/ 这里进行下载。
我们用 cloc
分析一下源代码发现,这个时候的 'PHP' 其实只有两千多行代码,其中真正用 C 语言实现的代码共计是 2455 行,等到了php2.0.1 的时候, C 语言的代码量就已经打到了进阶 58957 行。
和现在的 PHP 代码行数完全不是一个量级的,这是因为在当年大多数都是静态页面,而开发 PHP 的目的就是为了统计网站的访问人数。
这个时候的 PHP 是如何执行的呢,又或者 CGI 程序是怎么回事?
用 Shell 也能写网站
这里不会跟朋友们扯太多空洞洞的理论,因为理论知识很容易就可以获取到,随便看几本书就可以了,或者去百度百科,都会有非常详细的介绍。
在上世纪 90 年代互联网刚刚起步,那个时候大家都是在用 html 搭建一个个人简历或者是公司简介,这个时代就是 web1.0 时代,很快 CGI 出现了,拜 CGI 之赐,网站不再只有固定不变的图形和文字,借由程序动态产生的网页可以让网站好象『活』了起来。小从简单的网页计数器,留言版,大至处理众多资料的搜寻引擎,可做线上实时交易的电子商务、网络下单等。CGI 简单、开放、跨平台、与程序语言独立的特性,使得编写网站应用程序变得很容易。
- 用户访问apache
- apache 调用 cgi程序,在这个过程中会将请求数据发送给 cgi 程序。
- cgi 输出执行业务逻辑并且输出页面。
按照这个原理,其实任何语言都可以编写 cgi 程序,下面我们用 shell 来编写一个打印 get 参数的 echo 服务。
#!/bin/bash
echo 'Status: 200 OK'
echo 'Content-Type: text/html; charset=utf-8'
echo 'Cache-Control: no-cache'
echo 'Pragma: no-cache'
echo
echo '<html>'
echo '<head>'
echo '<h1>echo query:</h1>'
echo '<p>'
echo $QUERY_STRING
echo '</p>'
echo '</body>'
echo '</html>'
echo
PHP 的使用案例
这个时候 PHP 提供了四个 cgi 程序:phpl.cgi
, phpf.cgi
, phplmon.cgi
, phplview.cgi
。
其实这个时候的 PHP 就已经在简化 HTML 开发了,其中提供了一些内置函数可以帮我们进行模板替换。
<ul>
<li>$today = <!--$today--></li>
<li>$version = <!--$version--></li>
<li>$_GET['name'] = <!--$name--></li>
<li>system load:<pre><!--! w|head -n1 --></pre></li>
</ul>
<br>This page has been accessed a total of <a href="phplmon.cgi"><b>3</b></a> times now! <b>3</b> times today.<br>Page was last updated on Dec 22, 2021 at 7:43.</center></font>
我们可以看到 <!--$today-->
<!--$version-->
都已经被 phpl.cgi 程序给替换掉了。
PHP 源代码分析
PHP 的目录结构
这就是这个版本的 PHP 主要执行流程。我们来看一下代码目录
.
├── License
├── README
├── build
│ ├── phpf.cgi
│ ├── phpl.cgi
│ ├── phplmon.cgi
│ └── phplview.cgi
├── common.c
├── common.h
├── config.h
├── error.c
├── html_common.h
├── phpf.c
├── phpl.c
├── phplmon.c
├── phplview.c
├── post.c
├── post.h
├── subvar.c
├── version.h
├── wm.c
└── wm.h
- common.c & common.h 一些公共函数的声明,我们看到有很多函数其实是没有.h 声明的,其实就放在了这里。
- config.h : 相当于php.ini 用来存储 PHP 相关的配置信息
- html_common.h && error.c :公共函数,这个名字起的让人很迷惑,里面有处理html_error 的处理函数,也有公用头部函数。
- phpl.c
- phplmon.c
- phpview.c
- phpf.c
- subvar.c :变量替换函数
- version.h :存放了 PHP 的版本信息,作者名字。
- vm.c & vmc.h :字符串匹配函数,有趣的是这段代码并不是 Rasmus Lerdorf 写的,而是 COPY 过来的,并且他在代码中感谢了 Justin Slootsky。这说明无论是什么人都会有自己的短板。
在这里的 build 文件其实是不存在的,这是为了方便编译我自己就修改了 Makefile 文件。
PHPL.C 的执行流程
- 初始化变量
- 获取 Post 数据,如果有则初始化 Post 链表,如果没有则不执行
- 校验参数,也就是 CGI 程序发过来的 argv,校验不通过则渲染错误 html
- 渲染(其实就是标准输出)HTML,其中包括模板解析,替换等操作
- 释放Post 链表,结束程序
PHP 的配置文件
我们先来看配置文件 config.h :
define ROOTDIR "/usr/somewhere/user_id/public_html"
define HTML_DIR "html"
#define LOGDIR "logs/"
#define ACCDIR "logs/"
#define NOACCESS "NoAccess.html"
这个时候的配置文件可以说是相当简洁了,只有四个参数,所以根本不需要独立的配置文件,我们只需要在编译之前进行修改就可以了。
- ROOTDIR:项目根目录,在例子中,我们使用 phpl.cgi?var.html, var 就存储在根目录下,如果没有设置的话,回去找 apached 的默认配置,并且增加上 /public_html。
- HTMLDIR:如果你不想选择使用 rootdir 也可以利用这个进行指定目录
- LOGDIR:日志
- ACCDIR:日志
- NOACCESS:无法访问调转到指定页面。
PHP 的模板替换
在 phpl.c 中定义了两个宏。
#ifndef STARTSEP
#define STARTSEP "<!--"
#endif
#ifndef ENDSEP
#define ENDSEP "-->"
这两宏的作用其实就是代表着模板替换的开始和结尾。
在这个时候,其实 PHP 有点类似于 Smarty 模板引擎,在 PHP 中预设了很多变量,当加载 HTML 的时候,如果发现 <!-- -->
模板标记,就会将其中的内容替换成已经赋值的变量
我们可以看到截图中代码替换,其实就是检测到 $total 就替换成已经实现准备好的变量。
- 在 632 和 637 行 其实就是去截取
<!-- -->
中的内容 - 查找到对应的标记进行替换。
在 651 行中有 $today,我们以此举例。
static int todays_cnt;
在 phpl.c的最上方定义了静态变量 todays_cnt,其实该记录存储的就是今日的访问记录。
if(fgets(cntbuf,127,fp)) {
s=strchr(cntbuf,' ');
if(s) *s='\0';
cnt=atoi(cntbuf);
if(s) todays_cnt=atoi(s+1);
} else {
cnt=0;
todays_cnt=0;
}
PHP 中的 Post 数据
用链表存储 Post 数据
在 phpl.c 中第 88 行定义了一个全局静态变量, pdtop 其结构定义在 post.h 中。
typedef struct _postdata {
char var[32];
char val[128];
struct _postdata *next;
} postdata;
这是一个链表,第一个 char 存储的事 post 的 key,第二个 char 存储的是值,在这里我们可以看出,在这个时候 PHP 最大只能接受 128 个字符。
获取 post 数据
在 cgi 协议中 http 请求中的数据是通过环境变量交给 cgi 程序的。
Post 数据的释放
在我们申请内存后,必须对其进行释放,否则就会造成内存泄露,当 php 执行完成后会对已经申请过的内存地址进行释放。
其实操作非常简单,就是判断第一个节点是否为空,如果不为空则证明有 post 的数据,然后开始遍历链表,释放掉所有申请的内存地址。
参考