Golang实践录:使用gin框架实现文件上传转发功能

Posted 李迟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang实践录:使用gin框架实现文件上传转发功能相关的知识,希望对你有一定的参考价值。

近段时间需要实现一个转发 post 请求到指定后端服务的小工具,由于一直想学习 gin 框架,所以就使用这个框架进行尝试,预计会产生几篇文章。本文先研究如何在 gin 框架中实现上传和转发功能。

问题提出

一后台 web 服务,有众多历史版本,本身运行无问题,但后来需求变更,需将不同的历史版本单独运行,并指定不同端口。对外相当于有众多的服务。在请求 post 的文件中带有日期时间,需要根据时间转发到不同的端口的服务。

思路

nginx,可以根据端口做转发,但本文是根据请求的内容转发的,因此需要实现一个转发工具。即先读取外部请求的文件内容,解析得到时间,再根据时间,转发到不同的端口服务中。要解决的问题:
如何做到既解析 post 请求,又要将该请求原封不动地发到后端服务?后端服务返回的数据,如何原封不动地返回请求者?
如何管理后端服务?如果使用额外的脚本,则添加了运维部署的步骤,略有麻烦。故考虑在转发工具中实现。

实现

  • 使用工具进行 post 请求,并且指定文件名。可用 postman 或 curl,本文使用后者。

转发函数:

  • 利用 ctx.Request.FormFile 得到文件名称、文件内容,此时,可以使用 gin 提供的 SaveUploadedFile 函数保存文件,也可以调用 io.Copy 保存。前者省事。
  • 调用再次转发函数。
  • 将再次转发函数返回值转换成 json 形式,返回 post 请求工具。

再次转发函数:

  • 利用 multipart 包创建文件,将上一步得到的文件拷贝进去。
  • 再用 http 库发送请求。注意需要设置格式。
  • 最后读取请求的返回值,再返回,注意,内容为字节形式。

代码

主要接口代码


func RunWebServer(args []string) {
    runWebOnlyPost()
}

func runWebOnlyPost() {
	router := gin.New()
	router.Use(gin.Logger())
	router.Use(gin.Recovery())

	testRouter(router)

	klog.Println("Server started at ", conf.Port)
	router.Run(":" + conf.Port)
}


func testRouter(r *gin.Engine) {
	fmt.Println("test post...")
    
    r.POST("/foobar/test", foobar_test)
    r.POST("/foobar/test_back", foobar_test_back) 
}

实现代码


/*
 curl http://127.0.0.1:84/foobar/test -X POST -F  "file=@sample.json"
 
 临时:
 file读取了一次,再读就没有内容了,字节数为0
*/
func foobar_test(ctx *gin.Context) {
    
    // 2种方式都可,但 ctx.Request.FormFile 可以得到文件句柄,可直接拷贝
    //file, err := ctx.FormFile("file")
    file, header, err := ctx.Request.FormFile("file")
    if err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
        "error": err,
        })
        return
    } 
    
    //fmt.Printf("Request: %+v\\n", ctx.Request);
    //fmt.Printf("Formfile: %+v | %+v |||  %v %v\\n", file, header, err, reflect.TypeOf(file));
    
    // 拿到文件和长度,后面使用到
    var jsonfilename string = header.Filename
    mysize := header.Size
    fmt.Printf("filename: %s size: %d\\n", jsonfilename, mysize);
    
    if err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
        "error": err,
        })
        return
    }

    // 处理json文件 
    // 注:如果读取了文件,再转发,就没有内容了,所以这里不读取
    
    // 指向后端的服务URL
    url := "http://127.0.0.1:85/foobar/test_back"
    resp := post_data_gin(url, jsonfilename, file);

    // 解析返回字符切片,得到map,当成json,赋值给gin
    var data1 map[string]interface{}
    err = json.Unmarshal(resp, &data1)
    //fmt.Println("muti unmarshal: ", err, data1)
    
    ctx.JSON(http.StatusOK, data1)
    
    return
}

/*
 模拟后台的仅获取file字段的json,不作其它处理
 curl http://127.0.0.1:84/foobar/test_back -X POST -F  "file=@sample.json"
*/
func foobar_test_back(ctx *gin.Context) {
    
    // 2种方式都可,但 ctx.Request.FormFile 可以得到文件句柄,可直接拷贝
    //file, err := ctx.FormFile("file")
    _, header, err := ctx.Request.FormFile("file")
    if err != nil {
        ctx.JSON(
            http.StatusBadRequest,
            gin.H{
                "code": -1,
                "msg":  "failed",
                "data": gin.H{
                    "result": "failed in back end server",
                },
            },
        )

        return
    }

    // 拿到文件和长度,后面使用到
    var myfile string = header.Filename
    mysize := header.Size
    fmt.Printf("filename: %s size: %d\\n", myfile, mysize);
    
    if mysize <= 0 {
        ctx.JSON(
            http.StatusBadRequest,
            gin.H{
                "code": -1,
                "msg":  "failed",
                "data": gin.H{
                    "result": "failed in back end server, json size 0",
                },
            },
        )

        return
    }
    
    // 此处可保存文件

    //保存成功返回正确的Json数据
    ctx.JSON(
		http.StatusOK,
		gin.H{
			"code": 0,
			"msg":  "ok",
			"data": gin.H{
				"result": "ok in back end server",
			},
		},
	)

    return
}

为测试方便,文中实现的 gin 框架程序在运行时可指定端口。因此,代码中实现了2个 url 的响应函数。

测试

本文使用 sample.json 文件测试,内容如下:

{
	"enID": "ID250",
	"exID": "ID251",
	"type": 1,
	"money": 250.44,
	"distance": 274050
}

先运行 84 端口服务(称为 84 服务),此为对外的服务。再运行 85 端口服务(称为 85 服务),此为模拟后端的服务。
启动一终端,执行测试命令:

curl http://127.0.0.1:84/foobar/ -X POST -F  "file=@sample.json"

84 服务打印:

[2021-08-25 23:51:19.424 busy.go:79] Server started at  84
[GIN-debug] Listening and serving HTTP on :84
filename: sample.json size: 95
io copy: 95 <nil>
[GIN] 2021/08/25 - 23:53:59 | 200 |      3.0002ms |       127.0.0.1 | POST     "/foobar/test"

85 服务打印:

[GIN-debug] Listening and serving HTTP on :85
filename: sample.json size: 95
[GIN] 2021/08/25 - 23:53:59 | 200 |            0s |       127.0.0.1 | POST     "/foobar/test_back"

测试命令返回:

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   361  100    63  100   298  31500   145k --:--:-- --:--:-- --:--:--  352k{"code":0,"data":{"result":"ok in back end server"},"msg":"ok"}

也可直接向后端服务请求:

$ curl http://127.0.0.1:85/fee/test_back -X POST -F  "file=@sample.json"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   361  100    63  100   298  63000   291k --:--:-- --:--:-- --:--:--  352k{"code":0,"data":{"result":"ok in back end server"},"msg":"ok"}

2021.8.25 夜

以上是关于Golang实践录:使用gin框架实现文件上传转发功能的主要内容,如果未能解决你的问题,请参考以下文章

Golang实践录:使用gin框架实现转发功能:上传文件并转

Golang实践录:使用gin框架实现转发功能:利用nginx转发

Golang实践录:使用gin框架实现转发功能:利用nginx转发

Golang实践录:使用gin框架实现转发功能:管理后端服务

Golang实践录:使用gin框架实现转发功能:管理后端服务

Golang实践录:使用gin框架实现转发功能:一些负载均衡算法的实现