首页  >  文章  >  后端开发  >  Go 中“mime”包的不确定性:信任但验证

Go 中“mime”包的不确定性:信任但验证

WBOY
WBOY原创
2024-07-29 07:26:131196浏览

Indeterminacy of the `mime` Package in Go: Trust But Verify

最初发布在我的博客:https://lazy.bearblog.dev/go-mime-wtf/

背景

一个周末,我决定为自己编写一个 Telegram 机器人来自动执行待办事项或购物清单。

这个想法很简单。我向机器人发送了要购买的商品列表,它会创建一个带有复选框的网页,我可以在一手拿着手机、另一只手拿着购物篮的情况下勾选这些复选框。

机器人写得很快,但后来我想尝试一下 OpenAI 的 Whisper 模型。因此,我决定在我的应用程序中添加语音识别。

现在,程序的工作原理如下:

  • 我向机器人发送这样的语音消息:“买一公斤土豆,一堆莳萝,一头卷心菜,几瓶啤酒,哦,不需要卷心菜,我们有”
  • 机器人识别语音并创建一个带有复选框的页面:
[ ] potatoes - 1 kg
[ ] dill - 1 bunch
[ ] beer - 2 bottles

现在,关于这篇文章。在 go-openai 中,您可以使用 openai.AudioRequest 结构发送音频,该结构允许您在 Reader 字段中指定音频数据流或在 FilePath 字段中指定文件路径。好吧,我认为这些字段是互斥的,但事实证明,即使使用音频流也需要指定 FilePath。 API 可能使用它来确定流类型。

在 Telegram 端,我们收到一个 tgbotapi.Voice 结构,其中有一个 MimeType 字段。对于语音消息,它是audio/ogg,但将来很容易改变。

因此,我们有了音频流的 MIME 类型,我们需要从中确定文件扩展名,创建一个文件名以确保 OpenAI API 不会抱怨未知的文件类型。

还有什么可以更简单呢?

代码看起来很简单:

extensions, err := mime.ExtensionsByType(mimeType)
if err != nil {
    return err
}
if len(extensions) == 0 {
    return fmt.Errorf("unsupported mime type: %s", mimeType)
}
ext := extensions[0]
log.Printf("Assumed extension: %s", ext)
fakeFileName := fmt.Sprintf("voice_message.%s", ext)

本地测试 - 它有效:

2024/07/28 17:49:06 Assumed extension: oga

部署到云端 - 不起作用:

2024/07/28 17:55:32 unsupported mime type: audio/ogg

检查本地和用于构建的 CI/CD 中的 Go 版本 - 它们是相同的。

让我们开始吧

事实证明,mime 包的行为不是确定性的,并且在运行时取决于操作系统中是否存在 /etc/mime.types 文件及其内容。

这是 mime 包读取 MIME 类型到文件扩展名映射的运行时表的地方。

我是认真的:你编译一个二进制文件,运行所有测试,一切看起来都很棒,但是这个二进制文件将在不同的环境中以不同的方式确定相同 MIME 类型的假定文件扩展名。

如何修复它

让我们获取已知的 MIME 类型的音频文件及其扩展名,并使用 mime.AddExtensionType 手动将它们添加到 init 函数中。

var mimetypes = [][]string{
    {"audio/amr", "amr"},
    {"audio/amr-wb", "awb"},
    {"audio/annodex", "axa"},
    {"audio/basic", "au", "snd"},
    {"audio/csound", "csd", "orc", "sco"},
    {"audio/flac", "flac"},
    {"audio/midi", "mid", "midi", "kar"},
    {"audio/mpeg", "mpga", "mpega", "mp2", "mp3", "m4a"},
    {"audio/mpegurl", "m3u"},
    {"audio/ogg", "oga", "ogg", "opus", "spx"},
    {"audio/prs.sid", "sid"},
    {"audio/x-aiff", "aif", "aiff", "aifc"},
    {"audio/x-gsm", "gsm"},
    {"audio/x-mpegurl", "m3u"},
    {"audio/x-ms-wma", "wma"},
    {"audio/x-ms-wax", "wax"},
    {"audio/x-pn-realaudio", "ra", "rm", "ram"},
    {"audio/x-realaudio", "ra"},
    {"audio/x-scpls", "pls"},
    {"audio/x-sd2", "sd2"},
    {"audio/x-wav", "wav"},
}

func init() {
    log.Println("init mimetypes")
    for _, v := range mimetypes {
        typ := v[0]
        extensions := v[1:]

        for _, ext := range extensions {
            err := mime.AddExtensionType("."+ext, typ)
            if err != nil {
                log.Fatalf("mime: %s", err.Error())
            }
        }
    }
}

这将使我们的应用程序的行为在其将使用的域(在我的例子中是音频文件)方面具有确定性。

结论

这个经验表明,Go 中的事物并不总是确定性的。如果有些事情看起来很奇怪,请不要犹豫,深入研究库的源代码,看看它是如何实现的。从长远来看,这可以为您节省大量时间和麻烦。

以上是Go 中“mime”包的不确定性:信任但验证的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn