个人网站背景图片,福建建设执业资格注册中心网站,wordpress手机端模板下载,推广策略图片在gin框架中实现大文件下载主要分为两个步骤#xff1a;
将文件分块读取
由于大文件一次性读取会占用大量内存#xff0c;容易导致内存溢出等问题#xff0c;需要将文件分块读取#xff0c;逐一发送给客户端。
在gin框架中#xff0c;可以使用context.File方法向客户端…在gin框架中实现大文件下载主要分为两个步骤
将文件分块读取
由于大文件一次性读取会占用大量内存容易导致内存溢出等问题需要将文件分块读取逐一发送给客户端。
在gin框架中可以使用context.File方法向客户端发送文件该方法需要传入文件路径和文件名。为了实现分块读取我们可以使用os包中的File类型的Read()方法该方法可以从文件中读取指定长度的数据。
以下是分块读取文件并发送给客户端的代码
import (osstrconvgithub.com/gin-gonic/gin
)
func DownloadFile(c *gin.Context) {filePath : path/to/filefileName : file_namefile, err : os.Open(filePath)if err ! nil {c.AbortWithError(404, err)return}defer file.Close()stat, err : file.Stat()if err ! nil {c.AbortWithError(404, err)return}c.Writer.Header().Set(Content-Disposition, attachment; filenamefileName)c.Writer.Header().Set(Content-Type, application/octet-stream)c.Writer.Header().Set(Content-Length, strconv.FormatInt(stat.Size(), 10))c.Writer.Flush()var offset int64 0var bufsize int64 1024 * 1024 // 1MBbuf : make([]byte, bufsize)for {n, err : file.ReadAt(buf, offset)if err ! nil err ! io.EOF {log.Println(read file error, err)break}if n 0 {break}_, err c.Writer.Write(buf[:n])if err ! nil {log.Println(write file error, err)break}offset int64(n)}c.Writer.Flush()
}上述代码中我们首先打开文件并获取文件状态文件大小然后设置一些响应头包括Content-Disposition告诉浏览器以附件形式下载文件、Content-Type告诉浏览器文件类型以及Content-Length告诉浏览器文件大小。
接下来我们定义一个缓冲区大小为1MB根据实际情况可调整。然后使用循环读取文件并逐一将数据块发送给客户端。
实现断点续传
断点续传是指当下载文件过程中如果网络出现问题或者用户暂停了下载下一次再进行下载时可以从上一次下载的位置继续开始下载而不需要从头开始下载。
要实现断点续传我们需要在响应头中添加一个Content-Range字段该字段表示当前响应数据的范围。例如如果当前文件大小为100MB已经下载了20MB那么响应头中就可以写成
Content-Range: bytes 20971520-104857599/104857600其中20971520-104857599表示当前响应数据的范围104857600表示文件总大小。
如何获取已下载的位置可以从请求头中的Range字段中获取。例如如果客户端已经下载了20MB那么请求头中可以写成
Range: bytes20971520-以下是实现断点续传的代码
import (ioosstrconvstringsgithub.com/gin-gonic/gin
)
func DownloadFile(c *gin.Context) {filePath : path/to/filefileName : file_namefile, err : os.Open(filePath)if err ! nil {c.AbortWithError(404, err)return}defer file.Close()stat, err : file.Stat()if err ! nil {c.AbortWithError(404, err)return}c.Writer.Header().Set(Content-Disposition, attachment; filenamefileName)c.Writer.Header().Set(Content-Type, application/octet-stream)c.Writer.Header().Set(Content-Length, strconv.FormatInt(stat.Size(), 10))c.Writer.Flush()var offset int64 0var bufsize int64 1024 * 1024 // 1MBbuf : make([]byte, bufsize)rangeHeader : c.Request.Header.Get(Range)if rangeHeader ! {parts : strings.Split(rangeHeader, )if len(parts) 2 parts[0] bytes {rangeStr : parts[1]ranges : strings.Split(rangeStr, -)if len(ranges) 2 {offset, _ strconv.ParseInt(ranges[0], 10, 64)if offset stat.Size() {c.AbortWithError(416, errors.New(Requested Range Not Satisfiable))return}if ranges[1] ! {endOffset, _ : strconv.ParseInt(ranges[1], 10, 64)if endOffset stat.Size() {endOffset stat.Size() - 1}c.Writer.Header().Set(Content-Range, bytes ranges[0]-strconv.FormatInt(endOffset, 10)/strconv.FormatInt(stat.Size(), 10))c.Writer.Header().Set(Content-Length, strconv.FormatInt(endOffset-offset1, 10))file.Seek(offset, 0)} else {c.Writer.Header().Set(Content-Range, bytes ranges[0]-strconv.FormatInt(stat.Size()-1, 10)/strconv.FormatInt(stat.Size(), 10))c.Writer.Header().Set(Content-Length, strconv.FormatInt(stat.Size()-offset, 10))file.Seek(offset, 0)}c.Writer.WriteHeader(206)}}}for {n, err : file.ReadAt(buf, offset)if err ! nil err ! io.EOF {log.Println(read file error, err)break}if n 0 {break}_, err c.Writer.Write(buf[:n])if err ! nil {log.Println(write file error, err)break}c.Writer.Flush()offset int64(n)}c.Writer.Flush()
}上述代码中在读取文件之前我们先从请求头中获取Range字段如果存在就解析出当前下载的起始位置并根据需要设置Content-Range字段和Content-Length字段。如果Range字段的值无效我们返回416状态码表示当前所请求的范围不符合要求。
之后我们按照文件分块读取的方式将数据块发送给客户端。在发送每个数据块之后我们需要及时调用Flush()方法将数据块发送给客户端否则会导致下载进度无法实时更新的问题。
参考链接