likes
comments
collection
share

go fiber 框架构建文件服务器 demo

作者站长头像
站长
· 阅读数 59

go fiber 框架介绍

官网: gofiber.io/

中文文档(略有机翻痕迹):learnku.com/docs/gofibe…

Fiber 是一个受 Express 启发的 Web 框架,使用 Fasthttp 构建。旨在通过高性能服务,使快速开发更加简便。

据说这框架是非常快的 go http 框架,看了一下文档发现和 gin 的用法差不多,而且基础功能还更丰富一些

go fiber 框架构建文件服务器 demo

用 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 详见:

learnku.com/docs/gofibe…

文件下载函数 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)
}

最后效果:

带有 /是文件夹,点击会进入该文件夹,剩下的文件点击则会直接下载

go fiber 框架构建文件服务器 demo

项目 github 地址: github.com/YanHeDoki/F…

转载自:https://juejin.cn/post/7390629662836506676
评论
请登录