linux 使用go gin搭建web,[Go]基于Gin的Web服务端构建 · Wanmii’s Blog.

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

关于Gin的基本使用方法可移步Gin中文文档

本文以个人实际工程经验为基础,介绍了一种简易但实用的Web服务端构建方式,demo源码见文末。

包含状态的HTTPServer封装

要起一个Web服务,首先需要有一个server监听接口并接收http请求。Go在net/http包中有内置的http.server,结构包含了监听端口号Addr、处理方法Handler以及超时设置等属性。有一点不足的是,其没有一个属性或方法能返回当前server的状态。因而需要进行简单封装:1

2

3

4

5

6

7

8

9

10

11type HTTPServer struct {

server *http.Server

status string

}

const (

starting = "starting"

running = "running"

stopped = "stoped"

)

接着需要一个server的初始化方法:1

2

3

4

5

6

7

8

9

10

11

12

13

14func (addr string) *HTTPServer {

srv := &http.Server{

Addr: addr,

Handler: GetHandler(),

ReadTimeout: 10 * time.Second,

WriteTimeout: 10 * time.Second,

MaxHeaderBytes: 1 << 20,

}

return &HTTPServer{

server: srv,

status: starting,

}

}

NewServer()方法对server的Addr,Handler、读写超时、header最大容量等做了初始化,并将其初始状态设为starting。其中的GetHandler()方法下下节会有详细介绍。

在设定完初始状态后还需要有个interface方法集,用于控制server,及查看其状态:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24type IServer interface {

Start()

Running() bool

Stop()

}

func (s *HTTPServer) Start() {

s.status = running

err := s.server.ListenAndServe()

if err != nil {

s.status = stopped

os.Exit(1)

}

}

func (s *HTTPServer) Running() bool {

return s.status == running

}

func (s *HTTPServer) Stop() {

if s.server != nil {

fmt.Println("Server is closed")

}

}

Start()方法用于启动server——s.server.ListenAndServe(),并将其状态置为running。Running()用于查看server是否属于running状态。Stop()则在服务终止时打印日志通知管理员。

服务运行状态处理

服务运行过程中,除了要对网络请求进行处理外,还需要处理一些系统异常信号。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

49func Run(servers ...IServer) {

// defer panic

defer func() {

if err := recover(); err != nil {

log.Fatalf("server start err: %+v", err)

}

}()

// start servers

for _, s := range servers {

go s.Start()

}

// check servers status

for {

count := 0

for _, s := range servers {

if s.Running() {

count++

}

}

time.Sleep(1)

if count == len(servers) {

log.Println("servers starting ...")

break

}

}

// deal with signal

sig := make(chan os.Signal, 1)

signal.Notify(sig, os.Interrupt, syscall.SIGTERM)

for exitSig := range sig {

log.Println("receive exit signal ", exitSig)

switch exitSig {

case os.Interrupt:

fallthrough

case syscall.SIGTERM:

log.Println("exiting ...")

for _, s := range servers {

s.Stop()

}

log.Println("exit success")

os.Exit(0)

}

}

}

Run()方法用于server的运行。通过注释可把方法分为四个部分,首先是启动过程中的panic异常捕获;然后使用server的Start()方法启动服务;接着检查server属性,并输出日志告诉管理员管理员启动状态;最后还需要考虑的是,当发生系统中断os.Interrupt,或者管理员手动终止服务syscall.SIGTERM时,不仅要终止服务,还需要对信号进行监听并输出服务终止日志告知管理员。

处理函数的封装

前文提到的http处理Handler本质上是:1

2

3

4// package http

type Handler interface {

ServeHTTP(ResponseWriter, *Request)

}

gin框架中有对应的结构体类型gin.Engine对Handler interface进行了继承。实际上gin.Engine所包含的内容也不仅ServeHTTP()方法这么简单。1

2

3

4

5

6

7

8

9

10var handler *gin.Engine

var once sync.Once

func GetHandler() *gin.Engine {

once.Do(func() {

handler = gin.Default()

RegisteMiddleware(handler)

})

return handler

}

GetHandler()方法对*gin.Engine类型的handler进行了初始化。为保证全局的唯一性,使用了sync.Once对初始化语句以及中间件的注册进行了限定。

接下来,我们要考虑的是建立一个不同业务方法的共通模版actFunc()。1

2

3func ping(c *gin.Context) (status int, result interface{}, err error) {

return server.StatusOK, "ping success", nil

}

以上以ping()方法为例建立了个方法模版。可以看到,入参为*gin.Context,该类型包含了请求的上下文信息,开发者可以拿到所有的请求数据进行处理。回参有三个:(status int, result interface{}, err error),分别对应http请求状态码,请求结果以及error信息。以上基本包含了所有的处理结果信息,另外如“response header”可以通过中间件或直接在业务方法中对*gin.Context的指针对象进行处理。

此外,对于每一种请求,有一些共通的操作可以提前写好,以下对实际业务方法actFunc()进行了简单封装。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

35func wrapHandler(actFunc func(c *gin.Context) (status int, res interface{}, err error)) gin.HandlerFunc {

return func(c *gin.Context) {

sucChan := make(chan interface{}, 1)

var (

status int

res interface{}

err error

)

// get actFunc & recover()

go func(c *gin.Context) {

defer func() {

if r := recover(); r != nil {

status, res, err = StatusInternalServerError, nil, errors.New("unknown error")

log.Println("panic error ")

sucChan

}

}()

status, res, err = actFunc(c)

sucChan

}(c)

// time out control

select {

case

status, res, err = StatusRequestTimeout, nil, errors.New("time out")

log.Println("time out")

case

log.Println("http request success")

}

// write response

WriteResponse(c, status, res, err)

}

}

通过注释可以看出封装方法wrapHandler()进行了三步操作:首先获取actFunc()的返回值,并对方法运行过程中的异常进行捕获;其次是对每次请求进行超时设置,demo中设的是1秒;最后,对response进行编辑用于返回。

现在,server有了,server的handle有了,处理方法actFunc()及其封装wrapHandler()也有了。接下来就是要把它们串联起来:1

2

3

4

5

6

7

8

9

10// package server

// register GET method, other methods are same

func GET(path string, actFunc func(c *gin.Context) (int, interface{}, error)) {

GetHandler().GET(path, wrapHandler(actFunc))

}

// package router

func init() {

server.GET("/ping", ping)

}

上述代码注册了一个GET()方法,并调用了*gin.Engine类型的原生GET()请求方法,将path和actFunc()作为入参传入,对server和handler进行了绑定。开发者在使用过程中只需以server.GET("/ping", ping)形式进行调用即可。

中间件与返回方法

在上节GetHandler()方法中,对中间件进行了注册,以下作为示例对RegisteMiddleware()进行实现。1

2

3

4

5

6

7

8

9

10

11

12

13

14func cors() gin.HandlerFunc {

return func(c *gin.Context) {

c.Writer.Header().Set("Access-Control-Allow-Origin", "*")

c.Writer.Header().Set("Access-Control-Allow-Methods", "POST,GET,PUT,OPTIONS,DELETE")

c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")

c.Next()

}

}

func RegisteMiddleware(c *gin.Engine) {

c.Use(cors())

c.Use(gin.Recovery())

}

示例中注册了两个Middleware,一个是Gin原生的异常捕获gin.Recovery(),cors()则是对response header进行了设置以支持跨域访问。此外,常见的Middleware还包括一些统计和打点信息,开发者可根据业务所需进行定制。

实际工程中有时还需要对返回结构体进行一定封装:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24type MetaData struct {

Status int

Message string

}

type ReturnStr struct {

Meta MetaData

Result interface{}

}

func WriteResponse(c *gin.Context, status int, res interface{}, err error) {

message := "OK"

if err != nil {

message = err.Error()

}

c.JSON(StatusOK, ReturnStr{

Meta: MetaData{

Status: status,

Message: message,

},

Result: res,

})

}

在WriteResponse()中只要请求成功达到服务器,都会返回200的状态码。实际返回结构体ReturnStr分为两个部分,Meta返回定制的状态码和信息,Result返回请求结果。这样做的好处是,可以对http原生的状态码进行定制和扩展,便于发现问题和前后端业务逻辑的交互。

启动服务

万事具备,只需要在main函数中启动server就可以了。1

2

3

4func main() {

httpServer := server.NewServer(":8989")

server.Run(httpServer)

}

如上,先初始化了httpServer监听8989端口,再通过server.Run()将其跑起来就可以了。

以上就是Web服务端的基本架构方法,希望对你有帮助。

代码仓库地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值