最近洒家的 GitHub Pro 教育优惠到期了。洒家担心无法再从私有仓库发布 GitHub Pages 网站,于是提前研究了其他几款主流的静态网站托管平台,发现它们的 Web 服务配置和 GitHub Pages 有一些差别,服务器的行为特性各不相同。于是洒家设计了一套测试样例进行了更深入的实验,简单分析了一下实验结果,供读者在选择托管平台、配置静态网站生成器时参考。
概述¶
- 测试时间:2022 年 11 月
- 测试对象:
- 几款主流静态网站托管平台(所有平台均使用默认配置):
- Apache:对照实验对象,代表最普通的 Web 服务器(默认配置,基于 Ubuntu 22.04.1 LTS,版本为
2.4.52-1ubuntu4.2
)
- 用于测试的静态网站的目录结构:
/
|-- 404.html
|-- index.html
|-- test1
| `-- index.html
|-- test1.html
|-- test2.html
|-- test3
| `-- test3.html
`-- test4
`-- index.html
- 测试方法:部署上述静态网站后,使用各种路径访问,记录服务器的行为,并在不同平台间进行对比
- 测试样例:
- 基本测试:一组可以在
file://
协议、Apache 服务器等最基本的场景正常使用的访问方式 - 完整测试:所有可能的访问方式
- 基本测试:一组可以在
测试结果¶
测试结果中,200 /index.html
表示 HTTP 状态码为 200
,返回了 /index.html
的文件内容;301 --> /test1/
表示 HTTP 重定向到 /test1/
;404 (default)
表示状态码为 404
,返回了服务器默认错误页面,以此类推。
下列测试结果中,每个表格都会挑选一个测试对象作为标准测试对象,其他测试对象和标准测试对象对比,行为完全相同的测试点用绿色标记,行为不同但基本上可以等价替换的测试点用黄色标记(包括不同的 3xx
状态码跳转到了相同的 URL、都是 404
状态码但是响应消息正文不同等情况),行为完全不同的测试点用红色标记。红色的格子越多,和标准行为的差别越大。标准测试对象本身不做任何彩色标记。
基本测试¶
下表为基本测试的测试结果,以 Apache 为标准测试对象:
Apache | GitHub Pages | GitLab Pages | Cloudflare Pages | Vercel | Netlify | |
---|---|---|---|---|---|---|
/ | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html |
/index.html | 200 /index.html | 200 /index.html | 200 /index.html | 308 --> / | 200 /index.html | 200 /index.html |
/test1.html | 200 /test1.html | 200 /test1.html | 200 /test1.html | 308 --> /test1 | 200 /test1.html | 200 /test1.html |
/test1/ | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 301 --> /test1 |
/test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 308 --> /test1/ | 200 /test1/index.html | 200 /test1/index.html |
/test2.html | 200 /test2.html | 200 /test2.html | 200 /test2.html | 308 --> /test2 | 200 /test2.html | 200 /test2.html |
/test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 308 --> /test3/test3 | 200 /test3/test3.html | 200 /test3/test3.html |
/test4/ | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html |
/test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 308 --> /test4/ | 200 /test4/index.html | 200 /test4/index.html |
完整测试¶
下列表格展示了完整测试的测试结果。下表以 Apache 为标准测试对象:
Apache | GitHub Pages | GitLab Pages | Cloudflare Pages | Vercel | Netlify | |
---|---|---|---|---|---|---|
/ | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html |
/index | 404 (default) | 200 /index.html | 200 /index.html | 308 --> / | 404 /404.html | 301 --> / |
/index.html | 200 /index.html | 200 /index.html | 200 /index.html | 308 --> / | 200 /index.html | 200 /index.html |
/test1 | 301 --> /test1/ | 200 /test1.html | 302 --> /test1/ | 200 /test1.html | 200 /test1/index.html | 200 /test1.html |
/test1.html | 200 /test1.html | 200 /test1.html | 200 /test1.html | 308 --> /test1 | 200 /test1.html | 200 /test1.html |
/test1/ | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 301 --> /test1 |
/test1/index | 404 (default) | 200 /test1/index.html | 200 /test1/index.html | 308 --> /test1/ | 404 /404.html | 301 --> /test1/ |
/test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 308 --> /test1/ | 200 /test1/index.html | 200 /test1/index.html |
/test2 | 404 (default) | 200 /test2.html | 200 /test2.html | 200 /test2.html | 404 /404.html | 200 /test2.html |
/test2/ | 404 (default) | 404 /404.html | 200 /test2.html | 308 --> /test2 | 404 /404.html | 301 --> /test2 |
/test2.html | 200 /test2.html | 200 /test2.html | 200 /test2.html | 308 --> /test2 | 200 /test2.html | 200 /test2.html |
/test2.html/ | 404 (default) | 404 /404.html | 200 /test2.html | 404 /404.html | 200 /test2.html | 301 --> /test2 |
/test3 | 301 --> /test3/ | 301 --> /test3/ | 302 --> /test3/ | 404 /404.html | 404 /404.html | 404 /404.html |
/test3/ | 200 (Index of /test3) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/test3/test3 | 404 (default) | 200 /test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 404 /404.html | 200 /test3/test3.html |
/test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 308 --> /test3/test3 | 200 /test3/test3.html | 200 /test3/test3.html |
/test4 | 301 --> /test4/ | 301 --> /test4/ | 302 --> /test4/ | 308 --> /test4/ | 200 /test4/index.html | 301 --> /test4/ |
/test4/ | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html |
/test4/index | 404 (default) | 200 /test4/index.html | 200 /test4/index.html | 308 --> /test4/ | 404 /404.html | 301 --> /test4/ |
/test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 308 --> /test4/ | 200 /test4/index.html | 200 /test4/index.html |
/404 | 404 (default) | 200 /404.html | 200 /404.html | 200 /404.html | 404 /404.html | 200 /404.html |
/404.html | 200 /404.html | 200 /404.html | 200 /404.html | 308 --> /404 | 200 /404.html | 200 /404.html |
/xx | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx.html | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx/ | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx/xxx | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx/xxx.html | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/test1/xx.html | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
下表以 GitHub Pages 为标准测试对象:
Apache | GitHub Pages | GitLab Pages | Cloudflare Pages | Vercel | Netlify | |
---|---|---|---|---|---|---|
/ | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html |
/index | 404 (default) | 200 /index.html | 200 /index.html | 308 --> / | 404 /404.html | 301 --> / |
/index.html | 200 /index.html | 200 /index.html | 200 /index.html | 308 --> / | 200 /index.html | 200 /index.html |
/test1 | 301 --> /test1/ | 200 /test1.html | 302 --> /test1/ | 200 /test1.html | 200 /test1/index.html | 200 /test1.html |
/test1.html | 200 /test1.html | 200 /test1.html | 200 /test1.html | 308 --> /test1 | 200 /test1.html | 200 /test1.html |
/test1/ | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 301 --> /test1 |
/test1/index | 404 (default) | 200 /test1/index.html | 200 /test1/index.html | 308 --> /test1/ | 404 /404.html | 301 --> /test1/ |
/test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 308 --> /test1/ | 200 /test1/index.html | 200 /test1/index.html |
/test2 | 404 (default) | 200 /test2.html | 200 /test2.html | 200 /test2.html | 404 /404.html | 200 /test2.html |
/test2/ | 404 (default) | 404 /404.html | 200 /test2.html | 308 --> /test2 | 404 /404.html | 301 --> /test2 |
/test2.html | 200 /test2.html | 200 /test2.html | 200 /test2.html | 308 --> /test2 | 200 /test2.html | 200 /test2.html |
/test2.html/ | 404 (default) | 404 /404.html | 200 /test2.html | 404 /404.html | 200 /test2.html | 301 --> /test2 |
/test3 | 301 --> /test3/ | 301 --> /test3/ | 302 --> /test3/ | 404 /404.html | 404 /404.html | 404 /404.html |
/test3/ | 200 (Index of /test3) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/test3/test3 | 404 (default) | 200 /test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 404 /404.html | 200 /test3/test3.html |
/test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 308 --> /test3/test3 | 200 /test3/test3.html | 200 /test3/test3.html |
/test4 | 301 --> /test4/ | 301 --> /test4/ | 302 --> /test4/ | 308 --> /test4/ | 200 /test4/index.html | 301 --> /test4/ |
/test4/ | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html |
/test4/index | 404 (default) | 200 /test4/index.html | 200 /test4/index.html | 308 --> /test4/ | 404 /404.html | 301 --> /test4/ |
/test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 308 --> /test4/ | 200 /test4/index.html | 200 /test4/index.html |
/404 | 404 (default) | 200 /404.html | 200 /404.html | 200 /404.html | 404 /404.html | 200 /404.html |
/404.html | 200 /404.html | 200 /404.html | 200 /404.html | 308 --> /404 | 200 /404.html | 200 /404.html |
/xx | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx.html | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx/ | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx/xxx | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx/xxx.html | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/test1/xx.html | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
结论¶
在最普通的 Web 服务的基础上,各个平台都加上了一些新的访问和跳转规则。测试结果反映出各个平台实现特性的逻辑和规则的优先级有一些区别(例如 /test1
既可以返回 /test1.html
,又可以跳转到 /test1/
,还可以直接返回 /test1/index.html
)。洒家横竖看了半天,发现想做个简单易懂且全面的总结并不简单,无论用文字还是伪代码都只会越搞越乱,因此只能粗略总结一下,建议有兴趣的读者直接看原始结果。
各个静态网站托管平台的特性支持情况如下:
- 列目录:全部不支持,访问
/test3/
都会返回404
- 自定义 404 页面
/404.html
:全部都支持 - 省略
.html
访问.html
文件:除 Vercel 外,全部都支持 - 访问
/test4
直接访问/test4/index.html
:仅 Vercel 支持,其他都是先跳转到/test4/
在 Cloudflare Pages 平台,访问所有 /path/to/index.html
都会 308
跳转到 /path/to/
,访问其他 .html
文件也会通过 308
跳转去掉 .html
扩展名。根据洒家的理念,静态网站应该保持最大的兼容性,而不依赖任何平台的特性。无论把代码上传到任何平台、任何服务器上都应该可以正常访问,甚至直接在本地通过 file://
协议打开也应该能正常访问。由于这种强制跳转特性,洒家不会选择 Cloudflare Pages 平台。
而 Netlify 平台在基本测试中也存在瑕疵。同时存在 /test1/index.html
和 /test1.html
的情况下,访问 /test1/
会先 301
跳转到 /test1
,然后 200
返回 /test1.html
的内容,这和一般的预期不同。访问 /test1
的结果可以存在争议,而访问 /test1/
则应该是想访问 /test1/index.html
。再观察 /test2/
的访问结果,在存在 /test2.html
的情况下,访问 /test2/
会 301
跳转到 /test2
,猜测访问 /test1/
出现的现象可能是由于这个特性的优先级过高。
在 Vercel 平台,存在 /test4/index.html
的情况下,访问 /test4
没有跳转到 /test4/
,而是直接 200
返回了 /test4/index.html
。同时,我们又可以直接访问 /test4/index.html
。同时存在两种路径层级的访问方式,这可能导致页面资源相对引用混乱。
从基本测试可以看出,只有 GitHub Pages、GitLab Pages 和 Vercel 符合洒家的基本标准。从完整测试可以看出,最接近 Apache 的是 Vercel,最接近 GitHub Pages 的则是 GitLab Pages。因此,洒家可能会用 GitLab Pages 或者 Vercel 替代 GitHub Pages。然而到最后洒家忙活的这一圈也没用上,因为洒家另外搞出了一套方案,改用 GitHub Actions 从公开仓库私密发布 GitHub Pages 网站,不用再换平台了。感兴趣的读者可以前往仓库 Phuker/phuker.github.io 查看这套方案。