首页 > 其他分享 >gin框架中如何实现流式下载

gin框架中如何实现流式下载

时间:2022-11-03 16:11:50浏览次数:73  
标签:框架 Writer ctx 流式 go gzip gin indirect

作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


团队中之前的文件下载做得比较复杂,因为担心量太大,是后台做异步的下载,最终生成文件,传送文件到CDN服务器,最后再告诉用户下载链接。
其实在查询接口中就可以实现流式下载,这样查询接口和下载接口可以合二为一,更加简单。

下面是我的demo:

1.建立一个download_file的文件夹作为项目文件夹

go mod init download_file

2.生成go.mod文件,并准备对应的包:

go get github.com/gin-gonic/gin@latest
go get github.com/gin-contrib/gzip

go.mod文件内容如下:

module download_file

go 1.17

require github.com/gin-gonic/gin v1.8.1

require (
	github.com/gin-contrib/gzip v0.0.6 // indirect
	github.com/gin-contrib/sse v0.1.0 // indirect
	github.com/go-playground/locales v0.14.0 // indirect
	github.com/go-playground/universal-translator v0.18.0 // indirect
	github.com/go-playground/validator/v10 v10.10.0 // indirect
	github.com/goccy/go-json v0.9.7 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/leodido/go-urn v1.2.1 // indirect
	github.com/mattn/go-isatty v0.0.14 // indirect
	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
	github.com/modern-go/reflect2 v1.0.2 // indirect
	github.com/pelletier/go-toml/v2 v2.0.1 // indirect
	github.com/ugorji/go/codec v1.2.7 // indirect
	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
	golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
	golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
	golang.org/x/text v0.3.6 // indirect
	google.golang.org/protobuf v1.28.0 // indirect
	gopkg.in/yaml.v2 v2.4.0 // indirect
)

3.main.go文件:

3.1 初始化gin框架

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	engine := gin.New()
	// engine.Use(gzip.Gzip(gzip.DefaultCompression))  //如果需要开启gzip压缩,取消这一行的注释
	engine.Handle("POST", "/query", downloadFile)  // 假定查询和下载接口都是这条接口实现
	engine.Handle("GET", "/", homepage)
	engine.Run(":8080")
}

3.2 下载链接页,模拟post到新窗口的场景

func homepage(ctx *gin.Context) {
	ctx.Header("Content-Type", "text/html")
	ctx.Writer.WriteString(`
<html>
<body>
open window and to download:
<a href="javascript:download()">download</a>
<script>
function download(){
    var handle = window.open("about:blank", "my_download_window");
	document.forms[0].target = "my_download_window";
	document.forms[0].json.value="ahfu test";
	document.forms[0].submit();
}
</script>
<form action="/query" method="POST" enctype="multipart/form-data">
<input type="hidden" name="json" value=""/>
</form>
</body>
</html>
`)
}

点击链接后,弹出新窗口,在新窗口中POST json数据

3.3 流式下载功能

func downloadFile(ctx *gin.Context) {
	reqData, has := ctx.GetPostForm("json")
	if !has {
		ctx.Data(400, "text/plain","not found json form data")
		return
	}
        // 此处省略查询的业务逻辑
        //  todo: 
	// 下面开始下载的准备
	ctx.Writer.WriteHeader(200)
	ctx.Header("Content-Type", "text/plain; charset=utf-8")
	ctx.Header("Transfer-Encoding", "chunked")  // 告诉浏览器,分段的流式的输出数据
	//   ctx.Header("Content-Encoding", "gzip") // 输出不是gzip内容,又加上这个头,浏览器会拒收。这里是个实验,不要加这行代码
	now := time.Now()
	fileName := now.Format("20060102_150405.csv")
	ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName))  // 设置下载的文件名
	ctx.Writer.WriteHeaderNow()
	// 下面模拟一个周期非常长的数据处理和下载过程
	for i := 0; i < 100; i++ {
		ctx.Writer.WriteString("\"")
		ctx.Writer.WriteString(str)
		ctx.Writer.WriteString("\"\t")
		ctx.Writer.WriteString("\"")
		ctx.Writer.WriteString(time.Now().Format("2006-01-02 15:04:05"))
		ctx.Writer.WriteString("\"\n")
		ctx.Writer.Flush()  // 产生一定的数据后, flush到浏览器端
		time.Sleep(time.Duration(500) * time.Millisecond)
	}
}

打开浏览器,输入:http://127.0.0.1:8080
然后点击链接,过一会儿后会出现文件下载框。点击保存后,可以看见陆续下载文件的过程。
注意:为什么过了一会儿才出现文件下载框?这是由于浏览器的缓冲机制导致的。如果一开始下载的字节数很多,就会很快出现下载框

3.4 启用gzip压缩

大流量的文本下载,可能很占带宽,我们可以开启GZIP压缩:

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	engine := gin.New()
	engine.Use(gzip.Gzip(gzip.DefaultCompression))  //如果需要开启gzip压缩,取消这一行的注释
	engine.Handle("POST", "/query", downloadFile)  // 假定查询和下载接口都是这条接口实现
	engine.Handle("GET", "/", homepage)
	engine.Run(":8080")
}

gin框架中已经提供gzip压缩的能力。

3.5 完整代码:

// main.go
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/gin-contrib/gzip"
	"github.com/gin-gonic/gin"
)

func useGzip(engine *gin.Engine) {
	engine.Use(gzip.Gzip(gzip.DefaultCompression))
}

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	engine := gin.New()
	// engine.Use(gzip.Gzip(gzip.DefaultCompression))  //如果需要开启gzip压缩,取消这一行的注释
	engine.Handle("POST", "/query", downloadFile)
	engine.Handle("GET", "/", homepage)
	engine.Run(":8080")
}

func downloadFile(ctx *gin.Context) {
	reqData, has := ctx.GetPostForm("json")
	if !has {
		ctx.Data(400, "text/plain","not found json form data")
		return
	}
        // 此处省略查询的业务逻辑
        //  todo: 
	// 下面开始下载的准备
	ctx.Writer.WriteHeader(200)
	ctx.Header("Content-Type", "text/plain; charset=utf-8")
	ctx.Header("Transfer-Encoding", "chunked")  // 告诉浏览器,分段的流式的输出数据
	//   ctx.Header("Content-Encoding", "gzip") // 输出不是gzip内容,又加上这个头,浏览器会拒收。这里是个实验,不要加这行代码
	now := time.Now()
	fileName := now.Format("20060102_150405.csv")
	ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName))  // 设置下载的文件名
	ctx.Writer.WriteHeaderNow()
	// 下面模拟一个周期非常长的数据处理和下载过程
	for i := 0; i < 100; i++ {
		ctx.Writer.WriteString("\"")
		ctx.Writer.WriteString(str)
		ctx.Writer.WriteString("\"\t")
		ctx.Writer.WriteString("\"")
		ctx.Writer.WriteString(time.Now().Format("2006-01-02 15:04:05"))
		ctx.Writer.WriteString("\"\n")
		ctx.Writer.Flush()  // 产生一定的数据后, flush到浏览器端
		time.Sleep(time.Duration(500) * time.Millisecond)
	}
}

func homepage(ctx *gin.Context) {
	ctx.Header("Content-Type", "text/html")
	ctx.Writer.WriteString(`
<html>
<body>
open window and to download:
<a href="javascript:download()">download</a>
<script>
function download(){
    var handle = window.open("about:blank", "my_download_window");
	document.forms[0].target = "my_download_window";
	document.forms[0].json.value="ahfu test";
	document.forms[0].submit();
}
</script>
<form action="/query" method="POST" enctype="multipart/form-data">
<input type="hidden" name="json" value=""/>
</form>
</body>
</html>
`)
}

have fun.

标签:框架,Writer,ctx,流式,go,gzip,gin,indirect
From: https://www.cnblogs.com/ahfuzhang/p/16854798.html

相关文章

  • .NET MAUI (微软 .Net 6 跨多平台应用 UI)框架的研究学习
    针对.NETMAUI(微软.Net6跨多平台应用UI)框架的研究学习,使用VS2022 C#和XAML创建本机移动和桌面应用,开发一套代码可以发布在Android、iOS、macOS和Windo......
  • nginx前后端代理配置
    #前端页面代理location/{roothtml/h5;indexindex.htmlindex.htm;}#后端静态文件代理location/age/static{......
  • 写给关系数据库开发者的 TDengine 入门指南
    MySQL是中国开发者最熟悉的开源数据库产品,在很多开发者心中MySQL就是关系数据库的代名词。开发者们对MySQL数据库的的特性已经非常熟悉了。TDengine (https://github......
  • NGINX的编译安装
    实现步骤:1.安装编译工具2.创建运行NGINX的转有程序3.下载源码包并解压4.使用configure脚本生成makefile文件5.编译安装6.创建service文件说明:  源......
  • LVS + keepalived + nginx + tomcat 实现主从热备 + 负载均衡
    前言首先声明下,由于这两天找资料,看了不少博客,但是出于不细心,参考者的博客地址没有记录下来,所有文中要是出现了与大家博客相同的地方,那么请大家在评论区说明并附上博客......
  • nginx实现请求的负载均衡 + keepalived实现nginx的高可用
    前言使用集群是网站解决高并发、海量数据问题的常用手段。当一台服务器的处理能力、存储空间不足时,不要企图去换更强大的服务器,对大型网站而言,不管多么强大的服务器,都满......
  • 浅谈限流式保护器应用于防范电动自行车火灾的解决方案
    陈盼安科瑞电气股份有限公司上海嘉定201801【摘要】为了进一步研究电动自行车火灾事故发生机理,提升安全防范的针对性,文章分析了低压电气系统发生火灾的原理,解构了电动自行......
  • CentOS7下Nginx卸载
    目录1、停止Nginx服务2、查找根下所有名字包含nginx的文件3、删除nignx安装的相关文件4、停用开机自启动服务5、删除服务脚本1、停止Nginx服务/usr/local/nginx/sbin/ngi......
  • nginx “403 Forbidden” 错误的原因及解决办法
    多数是权限问题root/home/jd/code/dapingdist;这里要从home---->jd----->code,从外向内,一层层排查,是否有可读权限1.权限配置不正确这个是nginx出现403forbidden......
  • cpp 并行编程 pthread 框架 绑定核心运行
    1#include<stdio.h>2#include<math.h>3#include<pthread.h>4#include<stdio.h>5#include<iostream>6#include<stdlib.h>7#include<time.h>......