基础篇更新了11篇了,我们介绍了 go 的基本语法,数据类型,go 并发工具链
等知识,现在到了实践的时间的。任何的编程语言都是用来解决实际问题
今天我们用go 来实现一个基础的http静态文件服务,不需要引入第三方模块并部署到函数计算上,开始吧
flag 和 net/http 包
这两个是go 标准库中的包可以轻松实现一个静态文件服务,并加命令行参数
以下是代码:
package mainimport ( "flag" "fmt" "net/http")funcmain() { //命令行参数 port:= flag.String("port", ":9000", "服务端口,例如:9000") dir:= flag.String("dir", "./public", "静态文件目录") flag.Parse() //创建文件服务器 fs := http.FileServer(http.Dir(*dir)) //设置路由 http.Handle("/", fs) //启动服务 fmt.Printf("静态文件服务已启动 http://localhost%s\n", *port) fmt.Printf("静态文件目录:%s\n", *dir) http.ListenAndServe(*port, nil)}
flag 是用来解析命令行参数:
flag.String 接收三个参数,分别是参数名,默认值和使用说明
这里我们接收两个参数一个是服务的根目录一个是启动的端口,注意端口前有 :
创建静态服务一共有3部
- 调用http.FileServer() 输入一个目录
- 设置一个路由 http.Handle 把 / 以/开始的请求path , 交给 fs Server 来处理
- 启动你的服务使用 http.ListenAndServe 即可
我编译后生成一一个 static-server.exe 文件,加上 -h 参数就可以看到这个消息
支持br静态压缩
做过前端的朋友都知道,在 webpack 和 vite 这样的构建工具中,是可以为 js,css 文件生成静态的压缩文件的,常见的压缩格式有两种,一个是 gzip, 一个是brotliCompress
Gzip 相对比较古老,br 的压缩率相对gzip 会高 15-20% ,所 br 应该是比较先进的,浏览器支持也不错,静态文件服务推荐加上
下图是前端项目中的配置:
打包完成之后中,就是这个样子:
OK , 再说回我们的静态文件服务
核心思想是检查请求头中的Accept-Encoding字段,如果有 br 就去本地查找对应的br后缀文件
找到就返回文件内容,并在响应头字段加Content-Encoding: br
如果没找到再去检查gzip 找对应的 gz 后缀的文件
找到就返回文件内容,并在响应头字段加Content-Encoding: gzip
两个文件都没找到,就返回原内容
以下是代码实现:
package mainimport ( "flag" "fmt" "mime" "net/http" "os" "path/filepath" "strings")funcmain() { port := flag.String("port", ":9000", "服务端口") dir := flag.String("dir", "./public", "静态文件目录") flag.Parse() fs := http.FileServer(http.Dir(*dir)) http.Handle("/", cacheAndCompressHandler(fs, *dir)) fmt.Printf("服务启动 http://localhost%s\n", *port) http.ListenAndServe(*port, nil)}funccacheAndCompressHandler(next http.Handler, root string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { path := filepath.Join(root, r.URL.Path) if r.URL.Path == "/" { path = filepath.Join(root, "index.html") } if _, err := os.Stat(path); os.IsNotExist(err) { next.ServeHTTP(w, r) return } // --- 2. 压缩逻辑 --- enc := r.Header.Get("Accept-Encoding") if strings.Contains(enc, "br") { if _, err := os.Stat(path + ".br"); err == nil { w.Header().Set("Content-Encoding", "br") w.Header().Set("Vary", "Accept-Encoding") w.Header().Set("Content-Type", getContentType(path)) http.ServeFile(w, r, path+".br") return } } if strings.Contains(enc, "gzip") { if _, err := os.Stat(path + ".gz"); err == nil { w.Header().Set("Content-Encoding", "gzip") w.Header().Set("Vary", "Accept-Encoding") w.Header().Set("Content-Type", getContentType(path)) http.ServeFile(w, r, path+".gz") return } } next.ServeHTTP(w, r) })}funcgetContentType(path string) string { ext := filepath.Ext(path) if ct := mime.TypeByExtension(ext); ct != "" { return ct } return "application/octet-stream"}
加上缓存控制
做过前端的朋友应该清楚,网站的首页是不可以被浏览器缓存的,index.html 文件
中包含 css,js 的路径,如果首页缓存,用户刷新浏览器将不会请求新的文件
前端项目发布,用户无法第一时间感知,只能强制清缓存
由于现代的前端构建工具,在js 文件变化后,会生成新的文件名,文件名是
基于内容 hash 的,所以只要首页不缓存,发布后用户一刷新就能拿到新发布的js ,css
只要首页不更新,用户的js 就可以使用旧的,浏览器缓存的那一份,减少网络流量
也可以提升用户的下次打开的体验
OK,这就是静态文件服务加上缓存控制的理由,代码在这,重复的部分就不贴了
加在 35行 之后就可以
// --- 1. 缓存策略逻辑 --- // 精确判断是否为 .js 或 .css 文件 if strings.HasSuffix(r.URL.Path, ".js") || strings.HasSuffix(r.URL.Path, ".css") { // JS/CSS:缓存 30 天 w.Header().Set("Cache-Control", "public, max-age=2592000") } else { // 其他文件(如 html, json 等):不缓存 w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Header().Set("Pragma", "no-cache") w.Header().Set("Expires", "0") }
我试了一下,是可以的,如图 1 是首页 2 是js 文件的响应,有压缩有也缓存控制
最后
这就是基于 Go语言的第一篇实战,我们没有使用任何第三方包,工具全部来自标准库,编译后生成一个可执行文件,启动即可,是不是很方便
感谢你看到这里,提前祝你周末愉快~