这个模块让我想到了 2009 年刚刚工作的时候。最早我是做 .NET 的,而第一家公司其实是从 ASP 向 ASP.NET 转型中,因此,还是有不少的 ASP 做的页面。在那个时候,就用到了 SSI 。
这么一说,大家估计也猜到了,这个功能其实是很早的技术了。现在的年轻大佬们可能很多都不知道这个功能。它可以让静态文件,也就是 HTML 文件实现一些简单的文件包含、定义变量、条件判断之类的功能。
这个模块的名称是 ngx_http_ssi_module 模块,它是一个过滤器,用于处理通过它的响应中的 SSI(服务器端包含)命令。目前,支持的 SSI 命令列表不完整。
SSI 模块的指令都可以在 http、server、location 下进行配置。SSI 模块是默认添加的模块,直接就可以使用。我们先来看看它的配置指令。这些配置不是今天的重点,今天的是重点是演示一下如何使用 SSI 。
启用或禁用响应中 SSI 命令的处理。
ssi on | off;
默认值是 off 。要使用 SSI 当然要把这个打开啦。
允许在 SSI 处理期间保留原始响应中的“Last-Modified”标头字段,以促进响应缓存。
ssi_last_modified on | off;
默认值是 off 。默认情况下,当响应的内容在处理过程中被修改时,标头字段会被删除,并且可能包含动态生成的元素或部分,这些元素或部分会独立于原始响应而更改。
设置存储在磁盘上的响应部分的最小大小,从这里开始使用 sendfile 发送它们是有意义的。
ssi_min_file_chunk size;
默认值是 1k 。
如果启用,则在 SSI 处理期间发生错误时抑制“[an error occurred while processing the directive]”字符串的输出。
ssi_silent_errors on | off;
默认值是 off 。
除了“text/html”之外,还可以处理具有指定 MIME 类型的响应中的 SSI 命令。
ssi_types mime-type ...;
默认值是 text/html 。特殊值“*”匹配任何 MIME 类型 (0.8.29)。
设置 SSI 命令中参数值的最大长度。
ssi_value_length length;
默认值是 256 。
$date_local
本地时区的当前时间。格式由带有 timefmt 参数的 config 命令设置。
$date_gmt
格林威治标准时间的当前时间。格式由带有 timefmt 参数的 config 命令设置。
对于上面配置指令和变量的内容咱们就不多说了,直接配置一个服务器来学习 SSI 的使用吧。
server{
listen 8036;
root html;
location /ssi/ {
ssi on;
}
location ~ \.php$ {
root html;
fastcgi_pass unix:/var/sock/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
非常简单,就是监听了 8036 端口,然后定义了一个 /ssi/ 目录,然后再打开 ssi 功能。因为我们还会用到 PHP ,所以也加上了一个 PHP 的 FastCGI 配置。然后我们去 html 目录下创建一个 ssi 目录,在这个目录下面创建一个 index.html 文件。
37
young
old
this is block one.
看出来这个 SSI 的语法了吧。
它直接使用 HTML 中的注释,但是在注释中添加了一个 # 符号作为开始符号。接着就是命令以及命令相关的参数 。上面代码中,我们使用 include 命令加载文件,使用 set 定义变量,使用 echo 输出变量。使用 if 命令进行逻辑判断,最后的 block 命令是定义一个块,如果 include 加载的文件不存在时,就使用一个 stub 参数指定一个 block 显示 block 里面的内容。
接下来,准备最上面两个 include 需要加载的文件。
this is header.html!
header.html 就是显示一句话。
title is ''!
header.php 文件里面则是接收一个 title 参数 ,然后再把这个 title 参数打印出来。
好了,咱们访问一下这个页面试下吧。
➜ ~ curl http://192.168.56.88:8036/ssi/
this is header.html!
title is 'testssi'!
zyblog
37
123456
37
this is block one.
this is block one.
中间的空行我故意没有去掉,从这里可以看出,SSI 的命令行以及 PHP 代码在解析完成之后是会变成空行的。最下面的两个使用 block 的 include ,在错误日志文件中可以看到相应的错误信息。
2022/09/21 23:20:28 [error] 1513#0: *38 open() "/usr/local/nginx/html/ssi/abc.html" failed (2: No such file or directory), client: 192.168.56.1, server: , request: "GET /ssi/index.html HTTP/1.1", subrequest: "/ssi/abc.html", host: "192.168.56.88:8036", referrer: "http://xxx"
2022/09/21 23:20:28 [error] 1513#0: *38 open() "/usr/local/nginx/html/ssi/123.html" failed (2: No such file or directory), client: 192.168.56.1, server: , request: "GET /ssi/index.html HTTP/1.1", subrequest: "/ssi/123.html", host: "192.168.56.88:8036", referrer: "http://xxx"
上面例子中,if 判断貌似没啥用呀,毕竟我们的变量是写死的。然后 SSI 又不能动态接收参数,其实呀,使用 PHP 套上静态页面就可以接收参数了嘛。还是先准备一个 lcoation 来进行测试。
location ^~ /ssiphp/ {
alias html/ssi/;
fastcgi_pass unix:/var/sock/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $request_filename;
include fastcgi_params;
ssi on;
}
然后,准备一个 if.php 文件。
37
not 40
old or young? old!
测试一下吧,看看 if 的效果怎么样。
➜ ~ curl "http://192.168.56.88:8036/ssiphp/if.php?age=37"
37
➜ ~ curl "http://192.168.56.88:8036/ssiphp/if.php?age=49"
not 40
➜ ~ curl "http://192.168.56.88:8036/ssiphp/if.php?age=40"
old or young? old!
返回的结果和我们 if 条件的预期一样。不过需要注意的是,这里的 if 判断条件没有大于、小于,只有等于、不等于、空或非空判断,但判断值可以是正则表达式。
有意思吧,哈哈,早期的我们就是靠这个,实现 ASP 开发中头文件和脚文件的拆分的。不过现在真的很少见到了,毕竟一是纯静态网站已经很少了,二是各种语言框架都已经自带这些功能了。即使是做文章站那种生成纯静态页面的,也是直接去生成整张页面,和这个嵌套也没啥关系。
因此,它的应用场景现在确实很有限了。大家了解一下就好,特别是各位年轻的大佬,如果没见过的话,自己试试,其实也挺好玩的。
参考文档:
http://nginx.org/en/docs/http/ngx_http_ssi_module.html