Golang实践录:使用gin框架实现转发功能:管理后端服务
Posted 李迟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang实践录:使用gin框架实现转发功能:管理后端服务相关的知识,希望对你有一定的参考价值。
近段时间需要实现一个转发 post 请求到指定后端服务的小工具,由于一直想学习 gin 框架,所以就使用这个框架进行尝试,预计会产生几篇文章。本文研究如何管理后端服务。
思路
在启动 gin 服务前,先启动所有的后端服务进程,并且分配好端口,为简单起见,本文根据请求时间带的月份来转发,后端服务端口从 9000 开始。因此,需启动 13 个服务,端口从 9000 到 9012,如请求时间为9月份,则转发到 9009 端口的服务,对于非法月份,则统一转发到 9000 端口。
实现
- 添加
/
的响应,主要是为了后续方便使用 nginx。 - 分配好端口,启动后端服务。
- 根据请求时间选择一个后端 URL。
代码
主要接口代码
func RunWebServer(args []string) {
runWebOnlyPost()
}
func runWebOnlyPost() {
// 先执行其它业务,再到http
restartAll()
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)
// 注:此处直接响应端口的访问,因为实际中使用nginx转发的
r.POST("/", fee_test_back)
}
在上一版本基础上添加restartAll
函数。下面给出实现。
实现代码
restartAll
函数实现:
/*
停止后端服务(如有),
启动后端服务,同时分配端口
*/
func restartAll() {
// 通过端口限定,仅作测试
thePort, _ := strconv.Atoi(conf.Port)
if thePort >= 9000 {
return
}
klog.Println("restartAll...")
appname := "./httpforward_back"
klog.Printf("try to kill backend server %s\\n", appname[2:])
// exec.Command("sh", "-c", fmt.Sprintf("pkill -SIGINT %s", appname[2:])).Output()
exec.Command("sh", "-c", fmt.Sprintf("killall %s", appname[2:])).Output()
// os.Exit(0)
// 假定有12个端口,即12个后端服务,但额外有一个防止出错的端口
startport := 9000
conf.BackPorts = []int{}
for i := 0; i < 13; i++ {
port := startport + i
klog.Println("run in port: ", port)
conf.BackPorts = append(conf.BackPorts, port)
// note:使用系自带的接口,只启动,不等待,必须用'sh -c'格式,且不能合并
cmd := exec.Command("sh", "-c", fmt.Sprintf("%s -p %d -i \\"run in port %d\\" &", appname, port, port))
err := cmd.Start()
if err != nil {
klog.Printf("!! NOTE !! server on port %d start failed: %v\\n", port, err.Error())
}
}
fmt.Printf("run %d backend server ok\\n", len(conf.BackPorts))
}
转发实现函数:
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
}
// 拿到文件和长度,后面使用到
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文件
jsonbuf := make([]byte, mysize)
_, err = file.Read(jsonbuf)
// 注:读取了文件,要回到文件头,否则就没有内容了,因此这里用seek
file.Seek(0, 0)
var data map[string]interface{}
err = json.Unmarshal(jsonbuf, &data)
//fmt.Println("unmarshal: ", err, data)
var exTime string
exTime1 := data["exTime"]
// 如果出口时间没有,出错
if exTime1 == nil {
fmt.Println("exTime not found!")
ctx.JSON(
http.StatusOK,
gin.H{
"code": -1,
"msg": "failed",
"data": gin.H{
"result": "exTime not found",
},
},
)
return
}
exTime = exTime1.(string)
// exTime不合法
if len(exTime) == 0 {
ctx.JSON(
http.StatusOK,
gin.H{
"code": -1,
"msg": "failed",
"data": gin.H{
"result": "exTime is empty",
},
},
)
return
}
fmt.Printf("exTime: %s\\n", exTime)
// 此处选择一个URL
url := getOneServerUrl(exTime)
// 返回空,可能后端服务未启动或内部错误
if len(url) == 0 {
ctx.JSON(
http.StatusOK,
gin.H{
"code": -1,
"msg": "failed",
"data": gin.H{
"result": "ant get backend server url",
},
},
)
return
}
resp, err := post_data_gin(url, jsonfilename, file)
// 返回空,可能后端服务未启动或内部错误
if len(resp) == 0 {
ctx.JSON(
http.StatusOK,
gin.H{
"code": -1,
"msg": "failed",
"data": gin.H{
"result": fmt.Sprintf("backend server error: %s", err.Error()), //"backend server error: " + err.Error(),
},
},
)
return
}
// 解析返回字符切片,得到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
}
getOneServerUrl
函数实现如下:
// 根据时间选择一个后端服务器URL
func getOneServerUrl(exTime string) (url string) {
url = ""
if len(exTime) == 0 {
return
}
// 时间有2个格式,这里都判断一下
mytime, _ := time.Parse("2006-01-02T15:04:05", exTime)
themonth := int(mytime.Month())
// 如果不合法,年月日均为1
if mytime.Year() == 1 && themonth == 1 && mytime.Day() == 1 {
mytime, _ = time.Parse("2006-01-02 15:04:05", exTime)
// 还是不合法
if mytime.Year() == 1 && themonth == 1 && mytime.Day() == 1 {
themonth = 0
} else {
themonth = int(mytime.Month())
}
}
if themonth >= len(conf.BackPorts) {
themonth = 0
}
url = fmt.Sprintf("http://127.0.0.1:%d", conf.BackPorts[themonth])
fmt.Println("got url: ", url)
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")
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, port:" + conf.Port,
},
},
)
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, port: " + conf.Port,
},
},
)
return
}
// 此处可保存文件
/
// 处理json文件
jsonbuf := make([]byte, mysize)
_, err = file.Read(jsonbuf)
// 注:读取了文件,要回到文件头,否则就没有内容了,因此这里用seek
file.Seek(0, 0)
//fmt.Printf("read %d %v\\n%v\\n", n, err, string(jsonbuf));
var data map[string]interface{}
err = json.Unmarshal(jsonbuf, &data)
//fmt.Println("unmarshal: ", err, data)
var exTime string
exTime1 := data["exTime"]
// 如果出口时间没有,出错
if exTime1 == nil {
fmt.Println("exTime not found!")
ctx.JSON(
http.StatusOK,
gin.H{
"queryState": 0, // 0表示失败
"massage": "exTime not found",
"provinceFees": gin.H{},
},
)
return
}
exTime = exTime1.(string)
fmt.Println("extime: ", exTime)
/
//保存成功返回正确的Json数据
ctx.JSON(
http.StatusOK,
gin.H{
"code": 0,
"msg": "ok",
"data": gin.H{
"result": "ok in back end server, port: " + conf.Port + " info: " + conf.BackInfo,
},
},
)
return
}
测试
本文使用 sample.json 文件测试,内容如下:
{
"enID": "ID250",
"exID": "ID251",
"exTime": "2020-09-17T20:00:27",
"type": 1,
"money": 250.44,
"distance": 274050
}
为简单起见,将可执行文件拷贝一份,命名为httpforward_back
。
先运行 84 端口服务,会自动启动所有的后端进程。打印如下:
# ./httpforward.exe -p 84
[2021-09-09 14:56:40.691 restart.go:26] restartAll...
[2021-09-09 14:56:40.691 restart.go:38] try to kill backend server httpforward_back
[2021-09-09 14:56:40.700 restart.go:55] run in port: 9000
[2021-09-09 14:56:40.707 restart.go:55] run in port: 9001
[2021-09-09 14:56:40.712 restart.go:55] run in port: 9002
[2021-09-09 14:56:40.729 restart.go:55] run in port: 9003
[2021-09-09 14:56:40.732 restart.go:55] run in port: 9004
[2021-09-09 14:56:40.761 restart.go:55] run in port: 9005
[2021-09-09 14:56:40.768 restart.go:55] run in port: 9006
[2021-09-09 14:56:40.783 restart.go:55] run in port: 9007
[2021-09-09 14:56:40.803 restart.go:55] run in port: 9008
[2021-09-09 14:56:40.807 restart.go:55] run in port: 9009
[2021-09-09 14:56:40.809 restart.go:55] run in port: 9010
[2021-09-09 14:56:40.811 restart.go:55] run in port: 9011
[2021-09-09 14:56:40.849 restart.go:55] run in port: 9012
run 13 backend server ok
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
test post...
[GIN-debug] POST /foobar/test --> goweb/cmd/gin.fee_test (3 handlers)
[GIN-debug] POST /foobar/test_back --> goweb/cmd/gin.fee_test_back (3 handlers)
[GIN-debug] POST / --> goweb/cmd/gin.fee_test_back (3 handlers)
[2021-09-09 14:56:40.885 busy.go:77] Server started at 84
[GIN-debug] Listening and serving HTTP on :84
启动一终端,执行测试命令:
curl http://127.0.0.1:84/foobar/ -X POST -F "file=@sample.json"
可以修改sample.json
文件的exTime
观察转发的端口和返回值
84 服务打印:
filename: sample.json size: 133
exTime: 2020-09-17T20:00:27
got url: http://127.0.0.1:9009
[GIN] 2021/09/09 - 14:56:44 | 200 | 3.664936ms | 192.168.28.5 | POST "/foobar/test"
filename: sample.json size: 133
exTime: 2020-12-17T20:00:27
got url: http://127.0.0.1:9012
[GIN] 2021/09/09 - 15:04:30 | 200 | 2.465313ms | 192.168.28.5 | POST "/foobar/test"
测试命令返回:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 434 100 98 100 336 16333 56000 --:--:-- --:--:-- --:--:-- 72333{"code":0,"data":{"result":"ok in back end server, port: 9009 info: run in port 9009"},"msg":"ok"}
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 434 100 98 100 336 5764 19764 --:--:-- --:--:-- --:--:-- 27125{"code":0,"data":{"result":"ok in back end server, port: 9012 info: run in port 9012"},"msg":"ok"}
也可直接向后端服务请求:
$ curl http://127.0.0.1:85/foobar/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.18 夜
以上是关于Golang实践录:使用gin框架实现转发功能:管理后端服务的主要内容,如果未能解决你的问题,请参考以下文章
Golang实践录:使用gin框架实现转发功能:利用nginx转发
Golang实践录:使用gin框架实现转发功能:上传文件并转
Golang实践录:使用gin框架实现转发功能:上传文件并转
Golang实践录:使用gin框架实现转发功能:管理后端服务