阅读本文大概需要 4 分钟。
mimetype 是一个快速的 Golang 库,用于根据 magic number 来检测媒体类型和文件扩展名。magic number 是文件开头的一些特定字节,用于标识文件的格式。mimetype 库可以根据这些字节来判断文件的 MIME 类型和扩展名,而不需要依赖文件名或其他元数据。
工作中,我们需要检测文件类型,用的就是这个库。
mimetype 库有以下几个特性:
mimetype 库使用了一个分层的结构来存储和检测文件格式。这个结构类似于一棵树,根节点是 application/octet-stream,表示任意的二进制数据。每个节点都有一个或多个子节点,表示更具体的文件格式。每个节点都有一个或多个 magic number,用于匹配文件开头的字节,这么设计的原因是有些文件格式用作其他文件格式的容器。例如,Microsoft Office 文件只是 zip 存档,包含特定的元数据文件。一旦文件被识别为 zip 文件,就没有必要检查它是否是文本文件,但值得检查它是否是 Microsoft Office 文件。当检测一个文件时,mimetype 库会从根节点开始,依次比较每个节点的 magic number,如果匹配成功,则继续检测其子节点,直到找到最匹配的节点为止。这样可以减少检测所需的调用次数,并且可以避免一些格式之间的冲突。
注:根节点为什么是 application/octet-stream,可以在源码中看到解释:https://github.com/gabriel-vasile/mimetype/blob/master/tree.go,application/octet-stream 是一个通用的 MIME 类型,表示任意的二进制数据,mimetype 库中定义了一个名为 root
的变量,它是一个 mimetype.MIME
类型的值,它的 MIME 类型和扩展名都是 application/octet-stream,它没有 magic number,它是所有其他节点的父节点。
下图是 mimetype 库中部分节点的示意图:
image当检测一个 PNG 文件时,mimetype 库会先比较根节点 application/octet-stream 的 magic number(没有),然后比较其子节点 image/png 的 magic number(89 50 4E 47),如果匹配成功,则返回 image/png 作为 MIME 类型和 .png 作为扩展名。如果不匹配,则继续比较其他子节点,直到找到最匹配的节点或者没有更多子节点为止。
要使用 mimetype 库,首先需要安装它:
go get github.com/gabriel-vasile/mimetype
然后可以使用以下三种方法来检测一个文件的 MIME 类型和扩展名:
mimetype.Detect
函数,传入一个字节数组作为参数:mtype := mimetype.Detect([]byte)
fmt.Println(mtype.String(), mtype.Extension())
mimetype.DetectReader
函数,传入一个 io.Reader 接口作为参数:mtype, err := mimetype.DetectReader(io.Reader)
if err != nil {
// handle error
}
fmt.Println(mtype.String(), mtype.Extension())
mimetype.DetectFile
函数,传入一个文件路径作为参数:mtype, err := mimetype.DetectFile("/path/to/file")
if err != nil {
// handle error
}
fmt.Println(mtype.String(), mtype.Extension())
以上三种方法都会返回一个 mimetype.MIME
类型的值,它有两个方法:String
和 Extension
,分别返回 MIME 类型和文件扩展名的字符串表示。
如果要支持一些 mimetype 库没有内置的文件格式,可以使用 mimetype.Extend
函数来扩展树结构。以下是一个示例,使用了 mimetype.Lookup
函数来查找 text/plain 节点,然后使用了 Extend
方法来添加一个子节点。这个子节点使用了一个自定义的检测函数 foobarDetector
,它判断文件是否以 "foobar" 开头。如果是,就返回 text/foobar 作为 MIME 类型和 .fb 作为扩展名。
package main
import (
"bytes"
"fmt"
"github.com/gabriel-vasile/mimetype"
)
func main() {
foobarDetector := func(raw []byte, limit uint32) bool {
return bytes.HasPrefix(raw, []byte("foobar"))
}
mimetype.Lookup("text/plain").Extend(foobarDetector, "text/foobar", ".fb")
mtype := mimetype.Detect([]byte("foobar file content"))
fmt.Println(mtype.String(), mtype.Extension())
}
这样,当检测一个 foo 格式的文件时,mimetype 库会先检测其是否是 text/plain,然后再检测其是否是 text/foo,并返回相应的结果。
mimetype 支持更多的文件格式,可以自定义扩展,可以区分文本和二进制文件,而 filetype 支持更多的媒体格式,可以提取元数据和缩略图。
在性能方面,mimetype 和 filetype 都有较快的检测速度,但具体的差异可能取决于测试的文件集合和环境。
根据 mimetype 库的 README 中提供的一个基准测试结果,mimetype 更快(这里包括了标准库的 http.DetectContentType)。你也可以自己实际测试下。
mimetype http.DetectContentType filetype
BenchmarkMatchTar-24 250 ns/op 400 ns/op 3778 ns/op
BenchmarkMatchZip-24 524 ns/op 351 ns/op 4884 ns/op
BenchmarkMatchJpeg-24 103 ns/op 228 ns/op 839 ns/op
BenchmarkMatchGif-24 139 ns/op 202 ns/op 751 ns/op
BenchmarkMatchPng-24 165 ns/op 221 ns/op 1176 ns/op
陆续介绍了标准库、filetype 和 mimetype 这三种文件类型检测方法。我个人觉得,通常选择 mimetype 即可满足你的需求。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8