go fiber 框架构建文件服务器 demo
go fiber 框架介绍
官网: gofiber.io/
中文文档(略有机翻痕迹):learnku.com/docs/gofibe…
Fiber 是一个受 Express 启发的 Web 框架,使用 Fasthttp 构建。旨在通过高性能服务,使快速开发更加简便。
据说这框架是非常快的 go http 框架,看了一下文档发现和 gin 的用法差不多,而且基础功能还更丰富一些
用 fiber 写了一个文件服务器的 demo 玩了一下这个框架
启动服务
基本写法和其他 go 的 http 框架基本一致,构造出实例后编写路由即可,同时用 flag 用来指定一下启动端口,甚至都是 use 使用中间件
mian.go:
func main() {
// 指定端口
port := flag.Int("p", 4000, "启动端口")
flag.Parse()
// 模板引擎
engine := html.New("./tmpl", ".html")
// 实例并且载入模板
app := fiber.New(fiber.Config{
Views: engine,
})
// 中间件
app.Use(recover.New())
// 路由
app.Get("/download/+", logic.MyDownloadFile)
app.Get("/*", logic.Tmpl)
// 监听服务
app.Listen(fmt.Sprintf(":%d", *port))
}
路由
fiber 的路由参数功能非常丰富
// 参数
app.Get("/user/:name/books/:title", func(c *fiber.Ctx) error {
fmt.Fprintf(c, "%s\n", c.Params("name"))
fmt.Fprintf(c, "%s\n", c.Params("title"))
return nil
})
// 加号 - 贪婪 - 必填
app.Get("/user/+", func(c *fiber.Ctx) error {
return c.SendString(c.Params("+"))
})
// 可选参数
app.Get("/user/:name?", func(c *fiber.Ctx) error {
return c.SendString(c.Params("name"))
})
// 通配符 - 贪婪 - 可选
app.Get("/user/*", func(c *fiber.Ctx) error {
return c.SendString(c.Params("*"))
})
// 如下路由将匹配 "/v1/some/resource/name:customVerb" 请求,因为参数符号被转义了
app.Get("/v1/some/resource/name\\:customVerb", func(c *fiber.Ctx) error {
return c.SendString("Hello, Community")
})
建立两个路由,一个用来下载服务器中的文件,使用 + 号来贪婪匹配后续的所有路径
app.Get("/download/+", logic.MyDownloadFile)
一个用来渲染模板查看服务器文件列表,使用 * 号, * 代表可选,因为会有根目录的情况
app.Get("/*", logic.Tmpl)
均使用 get 方法
路由处理器
文档中对有路由处理器的介绍:
每个路由都可以具有多个处理程序函数,在路由匹配时执行。
这点也基本是 go http 框架都有的特性了吧,也就是写在路由后的函数类型,fiber 的类型是这样:
func(c *fiber.Ctx) error {
return nil
}
比较有意思的是 fiber 的路由处理函数是可以返回一个错误的,而且 fiber 也支持自定义处理路由错误的情况,可以出现错误了做一些额外操作并且给前端相应一个 500 的 code 详见:
文件下载函数 MyDownloadFile
逻辑非常简单,获取路由请求中的参数,找到是否存在这个文件,存在就使用流式下载文件, fiber 本身是直接就支持流式的文件下载,已经提供了 sendStream 和 sendFile 方法给前端返回文件的,这里自己实现了一个流式响应的方法来返回响应。
// 自定义文件流下载文件
func MyDownloadFile(c *fiber.Ctx) error {
// 确认文件是否存在
file_name := c.Params("+")
_, err := os.Stat(file_name)
if os.IsNotExist(err) {
return c.SendStatus(fiber.StatusNotFound)
}
// 文件存在,流式返回文件
file, err := os.OpenFile(file_name, os.O_RDONLY, 0666)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
fs := utils.New(*file, 1024)
defer fs.Close()
// 设置HTTP响应头
c.Set("Content-Type", "application/octet-stream")
read_size, err := fs.Stream(c)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
fmt.Println("file read size:", read_size)
return nil
}
自定义的文件流
type FileStream struct {
file os.File
size int
readSize int
}
func New(file os.File, size int) *FileStream {
return &FileStream{
file: file,
size: size,
readSize: 0,
}
}
func (fs *FileStream) Stream(w io.Writer) (int, error) {
// 写入计算
file_info, err := fs.file.Stat()
if err != nil {
return 0, err
}
// 如果缓存的容量已经大于了文件的大小就调整为当前文件的大小
if fs.size > int(file_info.Size()) {
fs.size = int(file_info.Size())
}
// 构造缓存
buf := make([]byte, fs.size)
// 不断读取写入文件
for fs.readSize < int(file_info.Size()) {
// 读取缓存大小的文件
_, err := fs.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return fs.readSize, err
}
// 写入并且累计写入的字节
read_size, err := w.Write(buf)
if err != nil {
return fs.readSize, err
}
fs.readSize += read_size
}
return fs.readSize, nil
}
func (fs *FileStream) Read(p []byte) (int, error) {
return fs.file.Read(p)
}
func (fs *FileStream) Close() error {
return fs.file.Close()
}
渲染模板方法:
渲染模板的方法也很简单,前面是模板中需要使用到的函数,通过 FuncMaP 的方式定义好,之后传入模板引擎中即可,需要注意的事,如果是使用 fiber 框架来渲染模板则可以使用
c.Render("index", fiber.Map{});
如果是使用标准库的模板引擎渲染的话,则需要标注一下 "Content-Type", "text/html",使用 fiber.Ctx 的 set 方法设置,不然会以普通的文本返回,导致模板失效。后续采集路由中的参数并且注入到模板中 然后找到当前文件夹中的所有的文件和文件夹,一并传入模板渲染。
func Tmpl(c *fiber.Ctx) error {
// 定义startsWith函数
funcMap := template.FuncMap{
"starts_with": func(s, prefix string) bool {
return strings.HasPrefix(s, prefix)
},
"add_path": func(p string) string {
original_url := c.OriginalURL()
if original_url == "/" {
original_url = ""
}
return c.BaseURL() + original_url + p
},
"download_path": func(p string) string {
original_url := c.OriginalURL()
if original_url == "/" {
original_url = ""
}
return c.BaseURL() + "/download" + original_url + "/" + p
},
}
t1, err := template.New("index.html").Funcs(funcMap).ParseFiles("./tmpl/index.html")
if err != nil {
panic(err)
}
// 检查参数
in_path := c.Params("*")
get_path := "./"
// 有期望进入的目录
if in_path != "" {
get_path += in_path
}
// 获取当前请求的路径
uri := string(c.Request().URI().Path())
parent_patch := getParentPath(uri)
file_list, err := utils.GetFiles(get_path)
if err != nil {
fmt.Println(err)
return c.SendStatus(fiber.StatusNotFound)
}
dateMap := map[string]any{"file_list": file_list, "current_path": uri, "parent_patch": parent_patch}
c.Set("Content-Type", "text/html")
return t1.Execute(c.Response().BodyWriter(), dateMap)
}
最后效果:
带有 /是文件夹,点击会进入该文件夹,剩下的文件点击则会直接下载
项目 github 地址: github.com/YanHeDoki/F…
转载自:https://juejin.cn/post/7390629662836506676