Go 语言实现 HTTP 文件上传和下载
作者:小二上酒8 发布时间:2023-06-23 01:42:24
前言:
近我使用 Go 语言完成了一个正式的 Web 应用,有一些方面的问题在使用 Go 开发 Web 应用过程中比较重要。过去,我将 Web 开发作为一项职业并且把使用不同的语言和范式开发 Web 应用作为一项爱好,因此对于 Web 开发领域有一些心得体会。
总的来说,我喜欢使用 Go 语言进行 Web 开发,尽管开始一段时间需要去适应它。Go 语言有一些坑,但是正如本篇文章中所要讨论的文件上传与下载,Go 语言的标准库与内置函数,使得开发是种愉快的体验。
在接下来的几篇文章中,我将重点讨论我在 Go 中编写生产级 Web 应用程序时遇到的一些问题,特别是关于身份验证/授权的问题。
这篇文章将展示 HTTP 文件上传和下载的基本示例。我们将一个有 type 文本框和一个 uploadFile 上传框的 HTML 表单作为客户端。
让我们来看下 Go 语言中是如何解决这种在 Web 开发中随处可见的问题的。
代码示例 首先,我们在服务器端设定两个路由, /upload 用于文件上传, /files/* 用于文件下载。
const maxUploadSize = 2 * 1024 * 2014 // 2 MB
const uploadPath = "./tmp"
func main() {
http.HandleFunc("/upload", uploadFileHandler())
fs := http.FileServer(http.Dir(uploadPath))
http.Handle("/files/", http.StripPrefix("/files", fs))
log.Print("Server started on localhost:8080, use /upload for uploading files and /files/{fileName} for downloading files.")
log.Fatal(http.ListenAndServe(":8080", nil))
}
我们还将要上传的目标目录,以及我们接受的最大文件大小定义为常量。注意这里,整个文件服务的概念是如此的简单 —— 我们仅使用标准库中的工具,使用 http.FileServe 创建一个 HTTP 处理程序,它将使用 http.Dir(uploadPath) 提供的目录来上传文件。
现在我们只需要实现 uploadFileHandler 。
这个处理程序将包含以下功能:
验证文件最大值
从请求验证文件和 POST 参数
检查所提供的文件类型(我们只接受图像和 PDF)
创建一个随机文件名
将文件写入硬盘
处理所有错误,如果一切顺利返回成功消息
第一步,我们定义处理程序:
func uploadFileHandler() http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
然后,我们使用 http.MaxBytesReader 验证文件大小,当文件大小大于设定值时它将返回一个错误。错误将被一个助手程序 renderError 进行处理,它返回错误信息及对应的 HTTP 状态码。
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
if err := r.ParseMultipartForm(maxUploadSize); err != nil {
renderError(w, "FILE_TOO_BIG", http.StatusBadRequest)
return
}
如果文件大小验证通过,我们将检查并解析表单参数类型和上传的文件,并读取文件。在本例中,为了清晰起见,我们不使用花哨的 io.Reader 和 io.Writer 接口,我们只是简单的将文件读取到一个字节数组中,这点我们后面会写到。
fileType := r.PostFormValue("type")
file, _, err := r.FormFile("uploadFile")
if err != nil {
renderError(w, "INVALID_FILE", http.StatusBadRequest)
return
}
defer file.Close()
fileBytes, err := ioutil.ReadAll(file)
if err != nil {
renderError(w, "INVALID_FILE", http.StatusBadRequest)
return
}
现在我们成功的验证了文件的大小,并且读取了文件,接下来我们该检验文件的类型了。一种廉价但是并不安全的方式,只检查文件扩展名,并相信用户没有改变它,但是对于一个正式的项目来讲不应该这么做。
幸运的是,Go 标准库提供给我们一个 http.DetectContentType 函数,这个函数基于 mimesniff 算法,只需要读取文件的前 512 个字节就能够判定文件类型。
filetype := http.DetectContentType(fileBytes)
if filetype != "image/jpeg" && filetype != "image/jpg" &&
filetype != "image/gif" && filetype != "image/png" &&
filetype != "application/pdf" {
renderError(w, "INVALID_FILE_TYPE", http.StatusBadRequest)
return
}
在实际应用程序中,我们可能会使用文件元数据做一些事情,例如将其保存到数据库或将其推送到外部服务——以任何方式,我们将解析和操作元数据。这里我们创建一个随机的新名字(这在实践中可能是一个 UUID)并将新文件名记录下来。
fileName := randToken(12)
fileEndings, err := mime.ExtensionsByType(fileType)
if err != nil {
renderError(w, "CANT_READ_FILE_TYPE", http.StatusInternalServerError)
return
}
newPath := filepath.Join(uploadPath, fileName+fileEndings[0])
fmt.Printf("FileType: %s, File: %s\n", fileType, newPath)
马上就大功告成了,只剩下一个关键步骤-写文件。如上文所提到的,我们只需要复制读取的二进制文件到一个新创建的名为 newFile 的文件处理程序里。
如果所有部分都没问题,我们给用户返回一个 SUCCESS 信息。
newFile, err := os.Create(newPath)
if err != nil {
renderError(w, "CANT_WRITE_FILE", http.StatusInternalServerError)
return
}
defer newFile.Close()
if _, err := newFile.Write(fileBytes); err != nil {
renderError(w, "CANT_WRITE_FILE", http.StatusInternalServerError)
return
}
w.Write([]byte("SUCCESS"))
这样可以了. 你可以对这个简单的例子进行测试,使用虚拟的文件上传 HTML 页面,cURL 或者工具例如 postman [1] 。
这里是完整的代码示例 这里 [2]
结论 这是又一个证明了 Go 如何允许用户为 Web 编写简单而强大的软件,而不必像处理其他语言和生态系统中固有的无数抽象层。
在接下来的篇幅中,我将展示一些在我第一次使用 Go 语言编写正式的 Web 应用中其他细节,敬请期待。;)
// 根据 reddit 用户 lstokeworth 的反馈对部分代码进行了修改。谢谢:)
来源:https://blog.51cto.com/u_15773567/5652662
猜你喜欢
- 本文实例讲述了python中@property和property函数常见使用方法。分享给大家供大家参考,具体如下:1、基本的@propert
- 本文实例讲述了Python实现的摇骰子猜大小功能小游戏。分享给大家供大家参考,具体如下:最近学习Python的随机数,逻辑判断,循环的用法,
- 简介CountMinSketch是一种计数器,用来统计一个元素的计数,它能够以一个非常小的空间统计大量元素的计数,同时保证高的性能及准确性。
- 最近开始研究Python的并行开发技术,包括多线程,多进程,协程等。逐步整理了网上的一些资料,今天整理了一下greenlet相关的资料。并发
- 自动弹出窗口是一个让人讨厌的事情,为什么我们用它来显示我们的调查表呢?用弹出窗口来显示调查表,被认为是达到我们收集访问用户信息的最方便快捷的
- 在正文前,先简短介绍自己。我任职于广州的某个网站服务公司的系统开发员,主要任务是以.Net编写各种web系统,例如CMS.EIP。大家都知道
- 运行环境:win10 64位 py 3.6 pycharm 2018.1.1导入对应的包和数据import matplotlib.pyplo
- set转成list方法如下: list转成set方法如下:s = set('12342212') &n
- sql server 全文检索有两种搜索方式,一种是contains,另一种是freetext。前者是包含,类似于 like '%关
- 什么是索引 拿汉语字典的目录页(索引)打比方:正如汉语字典中的汉字按页存放一样,SQL Server中的数据记录也是按页存放的,每页容量一般
- 本文实例讲述了Python模块相关知识点。分享给大家供大家参考,具体如下:1.模块:定义:用来从逻辑上组织python代码(变量,函数,类,
- 本文实例讲述了php的RSA加密解密算法原理与用法。分享给大家供大家参考,具体如下:最近因为工作的需要,要倒腾支付宝支付相关的知识,因为支付
- 前面两章,我们分析了Linux内存分配的策略以及Linux通过使用 OOM_Killer的机制解决了“超售”引起的风险,MySQL同其他的应
- 最近帮朋友做了点东西,最后需要将结果在网页中展示,这就需要搭建个服务器,做几个网页把数据信息展示出来。网上找了一下,阿里腾讯都有租服务器的业
- 实例如下所示:>>> import pandas as pd>>> df = pd.DataFrame(
- 在python中加背景音乐的方法:1、导入pygame资源包;2、修改音乐的file路径;3、使用init()方法进行初始化;4、使用loa
- PPT链接说实话,看到这个题目时我觉得这有什么好讨论的,肯定会是场一边倒的讨论。因为个人比较倾向于短命名,简单优雅,可能是出于程序员的洁癖,
- 方法1:只保存模型的权重和偏置这种方法不会保存整个网络的结构,只是保存模型的权重和偏置,所以在后期恢复模型之前,必须手动创建和之前模型一模一
- 场景一、有一个输入金额的场景,这个金额需要验证,验证说明如下:不能为空格;不能为0;不能为汉字;不能为其它字符;不能大于200;唯一可以的是
- 1、IIS为一个死循的执行过程设定执行时间(缺省为90秒)超时事件:<%response.buffer=true%><BO