一套代码,同时启动 7 种 Golang Web 框架

2022-01-03 20:01:08 浏览数 (4)

今天我们来做一个有趣的 Go 实践。使用同一套代码,在一个进程中,同时启动 7种不同的 Go Web 框架。

为什么做这么无聊的事儿?

主要目的就是介绍 rookie-ninja/rk-boot 库。

启动哪些 Go Web 框架?

我们同时启动如下几个 Go Web 框架。

Web 框架

rk-boot 依赖

版本

gin-gonic/gin

go get github.com/rookie-ninja/rk-boot/gin

v1.2.14 (Stable)

gRPC

go get github.com/rookie-ninja/rk-boot/grpc

v1.2.18 (Stable)

labstack/echo

go get github.com/rookie-ninja/rk-boot/echo

v0.0.8 (Stable)

gogf/gf

go get github.com/rookie-ninja/rk-boot/gf

v0.0.6 (Stable)

gofiber/fiber

go get github.com/rookie-ninja/rk-boot/fiber

v0.0.4 (Testing)

zeromicro/go-zero

go get github.com/rookie-ninja/rk-boot/zero

v0.0.2 (Testing)

gorilla/mux

go get github.com/rookie-ninja/rk-boot/mux

v0.0.2 (Testing)

快速开始

1.安装

我们通过 go get 安装如下依赖。

代码语言:txt复制
go get github.com/rookie-ninja/rk-boot/grpc
go get github.com/rookie-ninja/rk-boot/gin
go get github.com/rookie-ninja/rk-boot/echo
go get github.com/rookie-ninja/rk-boot/gf
go get github.com/rookie-ninja/rk-boot/fiber
go get github.com/rookie-ninja/rk-boot/zero
go get github.com/rookie-ninja/rk-boot/mux

2.创建 boot.yaml

Web 框架

端口

gRPC

8081

gin-gonic/gin

8082

labstack/echo

8083

gogf/gf

8084

gofiber/fiber

8085

zeromicro/go-zero

8086

gorilla/mux

8087

除了指定端口,我们还开启了如下两个选项:

  • commonService: 提供 /rk/v1/healthy 这类通用 API。
  • loggingZap: RPC 日志
代码语言:txt复制
---
grpc:
  - name: grpc
    port: 8081
    enabled: true
    commonService:
      enabled: true                   # Optional, default: false
    interceptors:
      loggingZap:
        enabled: true                 # Optional, default: false
gin:
  - name: gin
    port: 8082
    enabled: true
    commonService:
      enabled: true                   # Optional, default: false
    interceptors:
      loggingZap:
        enabled: true                 # Optional, default: false
echo:
  - name: echo
    port: 8083
    enabled: true
    commonService:
      enabled: true                   # Optional, default: false
    interceptors:
      loggingZap:
        enabled: true                 # Optional, default: false
gf:
  - name: gf
    port: 8084
    enabled: true
    commonService:
      enabled: true                   # Optional, default: false
    interceptors:
      loggingZap:
        enabled: true                 # Optional, default: false
fiber:
  - name: fiber
    port: 8085
    enabled: true
    commonService:
      enabled: true                   # Optional, default: false
    interceptors:
      loggingZap:
        enabled: true                 # Optional, default: false
zero:
  - name: zero
    port: 8086
    enabled: true
    commonService:
      enabled: true                   # Optional, default: false
    interceptors:
      loggingZap:
        enabled: true                 # Optional, default: false
mux:
  - name: mux
    port: 8087
    enabled: true
    commonService:
      enabled: true                   # Optional, default: false
    interceptors:
      loggingZap:
        enabled: true                 # Optional, default: false

3.创建 main.go

所有 Web 框架都是用 handleRequest() 函数来处理请求。

gRPC 例外,我们没有使用 protocol buffer,因为 PB 的协议不同。我们使用 grpc-gateway 来模拟。

grpcEntry 默认会开启 grpc-gateway,通过 grpcEntry.HttpMux 往 grpc-gateway 里注册 API。

代码语言:txt复制
// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gofiber/adaptor/v2"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/labstack/echo/v4"
	"github.com/rookie-ninja/rk-boot"
	"github.com/rookie-ninja/rk-boot/echo"
	"github.com/rookie-ninja/rk-boot/fiber"
	"github.com/rookie-ninja/rk-boot/gf"
	"github.com/rookie-ninja/rk-boot/gin"
	"github.com/rookie-ninja/rk-boot/grpc"
	"github.com/rookie-ninja/rk-boot/mux"
	"github.com/rookie-ninja/rk-boot/zero"
	"github.com/tal-tech/go-zero/rest"
	"net/http"
)

const handlePath = "/v1/hello"

// Application entrance.
func main() {
	// Create a new boot instance.
	boot := rkboot.NewBoot()

	// 6: go-zero @8086, must adds route before bootstrap
	rkbootzero.GetZeroEntry("zero").Server.AddRoute(rest.Route{
		Method:  http.MethodGet,
		Path:    handlePath,
		Handler: handleRequest,
	})

	// Bootstrap
	boot.Bootstrap(context.Background())

	// 1: grpc-gateway @8081
	rkbootgrpc.GetGrpcEntry("grpc").HttpMux.HandleFunc(handlePath, handleRequest)
	// 2: gin @8082
	rkbootgin.GetGinEntry("gin").Router.Handle(http.MethodGet, handlePath, gin.WrapF(handleRequest))
	// 3: echo @8083
	rkbootecho.GetEchoEntry("echo").Echo.GET(handlePath, echo.WrapHandler(http.HandlerFunc(handleRequest)))
	// 4: GoFrame @8084
	rkbootgf.GetGfEntry("gf").Server.BindHandler(handlePath, ghttp.WrapF(handleRequest))
	// 5: Fiber @8085, must call RefreshFiberRoutes()
	rkbootfiber.GetFiberEntry("fiber").App.Get(handlePath, adaptor.HTTPHandler(http.HandlerFunc(handleRequest)))
	rkbootfiber.GetFiberEntry("fiber").RefreshFiberRoutes()
	// 7: mux @8087
	rkbootmux.GetMuxEntry("mux").Router.HandleFunc(handlePath, handleRequest)

	// Wait for shutdown sig
	boot.WaitForShutdownSig(context.Background())
}

// Handle request for all web frameworks
func handleRequest(writer http.ResponseWriter, req *http.Request) {
	// 1: get query
	name := req.URL.Query().Get("name")

	// 2: marshal response
	bytes, _ := json.Marshal(&Response{
		Message: fmt.Sprintf("Hello %s", name),
	})

	// 3: write response
	writer.WriteHeader(http.StatusOK)
	writer.Write(bytes)
}

type Response struct {
	Message string `json:"message"`
}

4.文件夹结构 & go.mod

  • 文件夹结构
代码语言:txt复制
.
├── boot.yaml
├── go.mod
├── go.sum
└── main.go

0 directories, 4 files
  • go.mod 文件
代码语言:txt复制
module github.com/rookie-ninja/rk-demo

go 1.16

require (
	github.com/gin-gonic/gin v1.7.7
	github.com/gofiber/adaptor/v2 v2.1.15
	github.com/gogf/gf/v2 v2.0.0-beta
	github.com/labstack/echo/v4 v4.6.1
	github.com/rookie-ninja/rk-boot v1.4.0
	github.com/rookie-ninja/rk-boot/echo v0.0.8
	github.com/rookie-ninja/rk-boot/fiber v0.0.4
	github.com/rookie-ninja/rk-boot/gf v0.0.6
	github.com/rookie-ninja/rk-boot/gin v1.2.14
	github.com/rookie-ninja/rk-boot/grpc v1.2.18
	github.com/rookie-ninja/rk-boot/mux v0.0.2
	github.com/rookie-ninja/rk-boot/zero v0.0.2
	github.com/tal-tech/go-zero v1.2.4
)

5.启动 main.go

代码语言:txt复制
$ go run main.go

2022-01-03T19:15:14.016 0800    INFO    boot/gf_entry.go:1050   Bootstrap gfEntry       {"eventId": "264033ff-be9c-4147-871c-e4873ea1510d", "entryName": "gf"}
------------------------------------------------------------------------
endTime=2022-01-03T19:15:14.016473 08:00
startTime=2022-01-03T19:15:14.016057 08:00
elapsedNano=416178
timezone=CST
ids={"eventId":"264033ff-be9c-4147-871c-e4873ea1510d"}
app={"appName":"rk","appVersion":"","entryName":"gf","entryType":"GfEntry"}
env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"}
payloads={"commonServiceEnabled":true,"commonServicePathPrefix":"/rk/v1/","gfPort":8084}
error={}
counters={}
pairs={}
timing={}
remoteAddr=localhost
operation=Bootstrap
resCode=OK
eventStatus=Ended
EOE
...

调用 /rk/v1/healthy 来确定服务是否启动。

代码语言:txt复制
$ curl localhost:8081/rk/v1/healthy
{"healthy":true}

$ curl localhost:8082/rk/v1/healthy
{"healthy":true}

$ curl localhost:8083/rk/v1/healthy
{"healthy":true}

$ curl localhost:8084/rk/v1/healthy
{"healthy":true}

$ curl localhost:8085/rk/v1/healthy
{"healthy":true}

$ curl localhost:8086/rk/v1/healthy
{"healthy":true}

$ curl localhost:8087/rk/v1/healthy
{"healthy":true}

6.验证

我们将会发送 /v1/hello 请求到各个端口,并查看 RPC 日志。

  • grpc-gateway@8081 (grpc-gateway 目前暂无 RPC 日志输出)
代码语言:txt复制
$ curl "localhost:8081/v1/hello?name=rk-dev"
{"message":"Hello rk-dev"}
  • gin@8082
代码语言:txt复制
$ curl "localhost:8082/v1/hello?name=rk-dev"
{"message":"Hello rk-dev"}

# RPC log from server side
------------------------------------------------------------------------
endTime=2022-01-03T19:18:26.23983 08:00
startTime=2022-01-03T19:18:26.239805 08:00
elapsedNano=25460
timezone=CST
ids={"eventId":"0d284016-f714-4c85-8af8-9715dc9ed35f"}
app={"appName":"rk","appVersion":"","entryName":"gin","entryType":"GinEntry"}
env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"}
payloads={"apiMethod":"GET","apiPath":"/v1/hello","apiProtocol":"HTTP/1.1","apiQuery":"name=rk-dev","userAgent":"curl/7.64.1"}
error={}
counters={}
pairs={}
timing={}
remoteAddr=localhost:54102
operation=/v1/hello
resCode=200
eventStatus=Ended
EOE
  • echo@8083
代码语言:txt复制
$ curl "localhost:8082/v1/hello?name=rk-dev"
{"message":"Hello rk-dev"}

# RPC log from server side
------------------------------------------------------------------------
endTime=2022-01-03T19:19:11.109838 08:00
startTime=2022-01-03T19:19:11.109817 08:00
elapsedNano=21242
timezone=CST
ids={"eventId":"34419c7c-1a78-484f-ba7a-bfca69178b82"}
app={"appName":"rk","appVersion":"","entryName":"echo","entryType":"EchoEntry"}
env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"}
payloads={"apiMethod":"GET","apiPath":"/v1/hello","apiProtocol":"HTTP/1.1","apiQuery":"name=rk-dev","userAgent":"curl/7.64.1"}
error={}
counters={}
pairs={}
timing={}
remoteAddr=localhost:56995
operation=/v1/hello
resCode=200
eventStatus=Ended
EOE
  • gf@8084
代码语言:txt复制
$ curl "localhost:8084/v1/hello?name=rk-dev"
{"message":"Hello rk-dev"}

# RPC log from server side
------------------------------------------------------------------------
endTime=2022-01-03T19:19:39.905883 08:00
startTime=2022-01-03T19:19:39.905858 08:00
elapsedNano=24703
timezone=CST
ids={"eventId":"58bf8706-09ff-434e-b405-d6cdb8dbe8c2"}
app={"appName":"rk","appVersion":"","entryName":"gf","entryType":"GfEntry"}
env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"}
payloads={"apiMethod":"GET","apiPath":"/v1/hello","apiProtocol":"HTTP/1.1","apiQuery":"name=rk-dev","userAgent":"curl/7.64.1"}
error={}
counters={}
pairs={}
timing={}
remoteAddr=localhost:58802
operation=/v1/hello
resCode=200
eventStatus=Ended
EOE
  • fiber@8085
代码语言:txt复制
$ curl "localhost:8085/v1/hello?name=rk-dev"
{"message":"Hello rk-dev"}

# RPC log from server side
------------------------------------------------------------------------
endTime=2022-01-03T19:20:48.834567 08:00
startTime=2022-01-03T19:20:48.834425 08:00
elapsedNano=142332
timezone=CST
ids={"eventId":"a98a6e9f-6519-4ded-971e-0b6e59f66096"}
app={"appName":"rk","appVersion":"","entryName":"fiber","entryType":"FiberEntry"}
env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"}
payloads={"apiMethod":"GET","apiPath":"/v1/hello","apiProtocol":"http","apiQuery":"name=rk-dev","userAgent":"curl/7.64.1"}
error={}
counters={}
pairs={}
timing={}
remoteAddr=127.0.0.1:63237
operation=/v1/hello
resCode=200
eventStatus=Ended
EOE
  • zero@8086
代码语言:txt复制
$ curl "localhost:8086/v1/hello?name=rk-dev"
{"message":"Hello rk-dev"}

# RPC log from server side
------------------------------------------------------------------------
endTime=2022-01-03T19:21:20.835415 08:00
startTime=2022-01-03T19:21:20.835391 08:00
elapsedNano=24495
timezone=CST
ids={"eventId":"a6a53d21-4cf4-4b45-97ca-2b190e438e9c","traceId":"bf7a2359d0813de4388dd11c4f161321"}
app={"appName":"rk","appVersion":"","entryName":"zero","entryType":"ZeroEntry"}
env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"}
payloads={"apiMethod":"GET","apiPath":"/v1/hello","apiProtocol":"HTTP/1.1","apiQuery":"name=rk-dev","userAgent":"curl/7.64.1"}
error={}
counters={}
pairs={}
timing={}
remoteAddr=localhost:65299
operation=/v1/hello
resCode=200
eventStatus=Ended
EOE
  • mux@8087
代码语言:txt复制
$ curl "localhost:8086/v1/hello?name=rk-dev"
{"message":"Hello rk-dev"}

# RPC log from server side
------------------------------------------------------------------------
endTime=2022-01-03T19:22:13.13191 08:00
startTime=2022-01-03T19:22:13.131889 08:00
elapsedNano=21449
timezone=CST
ids={"eventId":"8a0f2db6-8e13-4773-bedd-962060adbe41"}
app={"appName":"rk","appVersion":"","entryName":"mux","entryType":"MuxEntry"}
env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"}
payloads={"apiMethod":"GET","apiPath":"/v1/hello","apiProtocol":"HTTP/1.1","apiQuery":"name=rk-dev","userAgent":"curl/7.64.1"}
error={}
counters={}
pairs={}
timing={}
remoteAddr=127.0.0.1:52277
operation=/v1/hello
resCode=200
eventStatus=Ended
EOE

rk-boot 介绍

rk-boot 是一个可通过 YAML 启动多种 Web 服务的框架。

有点类似于 Spring boot。通过集成 rk-xxx 系列库,可以启动多种 Web 框架。当然,用户也可以自定义 rk-xxx 库集成到 rk-boot 中。

rk-boot 亮点

  • 通过同样格式的 YAML 文件,启动不同 Web 框架。
  • 即使是不同框架,统一日志,Metrics, Tracing 等格式
  • 用户可以通过实现 rkentry.Entry 来自定义 YAML 文件。

rk-boot 支持的 Web 框架

欢迎贡献新的 Web 框架到 rk-boot 系列中。

参考 docs & rk-gin 作为例子。

框架

开发状态

安装

依赖

Gin

Stable

go get github.com/rookie-ninja/rk-boot/gin

rk-gin

gRPC

Stable

go get github.com/rookie-ninja/rk-boot/grpc

rk-grpc

Echo

Stable

go get github.com/rookie-ninja/rk-boot/echo

rk-echo

GoFrame

Stable

go get github.com/rookie-ninja/rk-boot/gf

rk-gf

Fiber

Testing

go get github.com/rookie-ninja/rk-boot/fiber

rk-fiber

go-zero

Testing

go get github.com/rookie-ninja/rk-boot/zero

rk-zero

GorillaMux

Testing

go get github.com/rookie-ninja/rk-boot/mux

rk-mux

0 人点赞