建议点击 查看原文 查看最新内容。
原文链接: https://typonotes.com/posts/2025/02/09/static-sap-dockerize-customize-httpserve/
众所周知, 我们在容器化 静态网站 的时候为了实现 一次编译, 处处运行 的目标, 在 index.html
中插入了一个变量 __CONFIG__
, 在启动的时候进行替换为正式后端的地址。 可以参考
为什么要自建?还是不是被逼的!!然后在实践中经常会遇到各种条件的约束。 公司平台团队以安全为由,锁定了 K8s Pod 文件系统策略, 将目录限制为只读状态。 虽然可以使用 mount 目录方式绕过, 但是我懒啊, 不想因为不同平台再定制不同的管理策略。
对于这种情况, 我使用 gin-gonic/gin
自定义一个 HTTP Server, 使用 https://github.com/spf13/afero 创建了一个内存文件系统, 将所有静态资源全部复制 内存空间 中, 这样就随便我修改了。
除此之外还有一个好处: 自定服务器还有一个好处是可以通过 环境变量 更方便的管理各种功能, 比花实践折腾配置模版(nginx) 体验更好。
Demo 项目可以参考: https://github.com/tangx/httpstatic
在 gin 中提供了 StaticFS
, 但参数要求是 http.FileSystem
接口
func (group *gin.RouterGroup) StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes
于是使用了 http.FS()
将 fs.FS
接口进行封装。
// 定义
// func http.FS(fsys fs.FS) http.FileSystem
// 使用
r.StaticFS("/", http.FS(fsys))
但是, 使用 spf13/afero
创建的内存系统的默认返回接口是 afreo.Fs
// func afero.NewMemMapFs() afero.Fs
m := afero.NewMemMapFs()
解决方法很简单, 自定一个 结构体 mfswrapper
实现 fs.FS
接口即可。
// mfswrapper wraps afero.Fs to implement fs.FS.
// cause gin.StaticFS() requires fs.FS.
// but afero.Fs does not implement fs.FS.
type mfswrapper struct {
afero.Fs
}
func (w mfswrapper) Open(name string) (fs.File, error) {
return w.Fs.Open(name)
}
部分代码如下
m
。copywalk()
将本地文件复制到内存文件系统 m 中。index.html
并使用 inject()
函数替换 __CONFIG__
为真实变量。// mfsMode serves files from the memory directory.
// if k8s restrict modifying files in the container.
func mfsMode(dist string) fs.FS {
m := afero.NewMemMapFs()
err := copywalk(dist, m, dist)
if err != nil {
panic(err)
}
// replace index.html content.
index := filepath.Join(dist, "index.html")
if data, replaced := inject(index); replaced {
if err := afero.WriteFile(m, "index.html", data, 0644); err != nil {
panic(err)
}
}
mw := &mfswrapper{m}
return mw
}
这样就绕过了 只读文件系统 的要求。 更重要的是, 无论以后应用部署到其他任何容器平台, 都不用再为其管理专门的 部署配置。