mimetype:一文读懂 Go 文件类型检测库的原理和用法

367次阅读  |  发布于10月以前

阅读本文大概需要 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 类型和扩展名:

mtype := mimetype.Detect([]byte)
fmt.Println(mtype.String(), mtype.Extension())
mtype, err := mimetype.DetectReader(io.Reader)
if err != nil {
    // handle error
}
fmt.Println(mtype.String(), mtype.Extension())
mtype, err := mimetype.DetectFile("/path/to/file")
if err != nil {
    // handle error
}
fmt.Println(mtype.String(), mtype.Extension())

以上三种方法都会返回一个 mimetype.MIME 类型的值,它有两个方法:StringExtension,分别返回 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,并返回相应的结果。

和 filetype 比的优缺点

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