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

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

问题提出

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

思路

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

实现

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

转发函数:

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

再次转发函数:

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

代码

主要接口代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

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

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

truetestRouter(router)

trueklog.Println("Server started at ", conf.Port)
truerouter.Run(":" + conf.Port)
}


func testRouter(r *gin.Engine) {
truefmt.Println("test post...")

r.POST("/foobar/test", foobar_test)
r.POST("/foobar/test_back", foobar_test_back)
}

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

/*
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(
truetruehttp.StatusOK,
truetruegin.H{
truetruetrue"code": 0,
truetruetrue"msg": "ok",
truetruetrue"data": gin.H{
truetruetruetrue"result": "ok in back end server",
truetruetrue},
truetrue},
true)

return
}

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

测试

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

1
2
3
4
5
6
7
8
{
true"enID": "ID250",
true"exID": "ID251",
"exTime": "2020-09-17T20:00:27",
true"type": 1,
true"money": 250.44,
true"distance": 274050
}

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

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

84 服务打印:

1
2
3
4
5
[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 服务打印:

1
2
3
[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"

测试命令返回:

1
2
3
  % 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"}

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

1
2
3
4
$ 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.9.17 夜