go语言gin web框架学习笔记(1)-初始化一个gin engine

2,468 阅读5分钟

main.go

// mail.go
// 一个 go 程序中只有一个入口,main 包下的 main 函数。
package main

// 引入包。
import (
	"apiserver/router"
	"errors"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"time"
)

// 每个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
func main() {
	// Create the Gin engine.
	// 以下格式只能在函数内使用。此为定义一个变量。
	// Go语言的变量和函数的公有私有靠首字母大小写区分,首字母大写是公有的,小写的私有的。
	g := gin.New()

	// Go 语言切片是对数组的抽象。Go 数组的长度不可改变,切片为 Go 内置类型,也叫"动态数组",追加元素可能使切片容量增大。
	middlewares := []gin.HandlerFunc{}

	// Routes.
	// router 包是我们自己写的一个包。里面定义了一个 Load 函数。
	// Load 函数接收 *gin.Engine 和 ...gin.HandlerFunc 作为实参。
	// * 代表变量的指针,实际使用时通过指针引用变量地址使用。
	// ... 代表实参是个变长参数(长度不固定)。
	// gin 中的 middleware,本质是一个匿名回调函数。这和绑定到一个路径下的处理函数本质是一样的。
	// 回调函数(callback function),就是一个应用传递给一个库函数的参数,而这个参数为一个函数。
	// 打个比方,住酒店(应用)时需要使用叫醒服务(库函数),需要客人(用户在app上操作)指定叫醒的方法(回调函数)。
	// 叫醒的方法里预定义了几个方式(回调函数中的属性),比如"打电话叫醒", "敲门叫醒", "从天花板落水下来"等。
	router.Load(
		// Cores.
		g,

		// Middlewares.
		middlewares...,
	)

	// Ping the server to make sure the router is working.
	// go 语言通过 goroutine 实现高并发,而开启 goroutine 的钥匙正时 go 关键字。
	// 这里实际上是一个匿名 goroutine 函数。
	go func() {
		if err := pingServer(); err != nil {
			log.Fatal("The router has no response, or it might took too long to start up.", err)
		}
		log.Print("The router has been deployed successfully.")
	}()

	// 日志模块的基本使用方法。
	// 格式化打印的基本使用方法。
	log.Printf("Start to listening the incoming requests on http address: %s", ":8080")
	log.Printf(http.ListenAndServe(":8080", g).Error())
}

// pingServer pings the http server to make sure the router is working.
func pingServer() error {
	for i := 0; i < 2; i++ {
		resp, err := http.Get("http://127.0.0.1:8080" + "/sd/health")
		if err == nil && resp.StatusCode == 200 {
			return nil
		}

		// Sleep for a secongd to continue the next ping.
		log.Print("Waiting for the router, retry in 1 second.")
		time.Sleep(time.Second)
	}
	return errors.New("Cannot connect to the router.")
}

apiserver/router/router.go

package router

import (
	"net/http"

	"apiserver_demo1/apiserver/handler/sd"
	"apiserver_demo1/apiserver/router/middleware"

	"github.com/gin-gonic/gin"
)

// Load loads the middlewares, routes, handlers.
// 通过 gin 包的 Engine 结构体的 Use 方法来具体实现路由引擎接口。
// Go 语言没有严格的重载的概念,但是一个结构体如果拥有实现某个接口的方法,就相当于继承了该接口。
// 这样就可以达到拥有各自的内部实现的子结构体都用相同接口来创建实例。
// gin 包下的 Engine 结构体便实现了 Iroutes 等接口的方法。
func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine {
	// Middlewares.
	// Engine 实现了 IRoutes 接口下的 Use 函数。同时 Routergroup 也实现了此函数。
	// Engine 中又定义了一个 Routergroup 成员,Use 方法在 Routergroup 中具体实现是类似于数组的 append 方法,这里就是添加
	// 处理器方法的操作,而 Engine 的 Use 方法在调用完 Routergroup 的 Use 之后又再增加了自己的逻辑。
	// 此处为通过结构体中定义同父级接口的结构体来达到进一步"继承"的效果。
	g.Use(gin.Recovery())
	g.Use(middleware.NoCache)
	g.Use(middleware.Options)
	g.Use(middleware.Secure)
	g.Use(mw...)
	// 404 Handler.
	// 函数也可以通过 type function_name func( [parameter_list] ) [return_types]
	// 这样的格式来定义,并且可以作为参数传递给其他函数。
	// NoRoute 方法属于 Engine 结构体,通过下面这种方式将自定义的回调函数传递给自己的成员,达到重写方法的目的。
	g.NoRoute(func(c *gin.Context) {
		c.String(http.StatusNotFound, "The incorrect API route.")
	})

	// The health check handlers
	// Engine 的 Group 方法是 Routergroup 结构体中的实现,而 Engine 有一个 Routergroup 成员,Routergroup 又有一个 Engine
	// 对象指针成员。通过此方法在 Engine 中实现 Routergroup 的方法重写。
	svcd := g.Group("/sd")
	{
		svcd.GET("/health", sd.HealthCheck)
		svcd.GET("/disk", sd.DiskCheck)
		svcd.GET("/cpu", sd.CPUCheck)
		svcd.GET("/ram", sd.RAMCheck)
	}

	return g
}

apiserver/router/middleware/header.go

package middleware

import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

// NoCache is a middleware function that appends headers
// to prevent the client from caching the HTTP response.
func NoCache(c *gin.Context) {
	c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value")
	c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
	c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
	c.Next()
}

// Options is a middleware function that appends headers
// for options requests and aborts then exits the middleware
// chain and ends the request.
func Options(c *gin.Context) {
	if c.Request.Method != "OPTIONS" {
		c.Next()
	} else {
		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
		c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept")
		c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS")
		c.Header("Content-Type", "application/json")
		c.AbortWithStatus(200)
	}
}

// Secure is a middleware function that appends security
// and resource access headers.
func Secure(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "*")
	c.Header("X-Frame-Options", "DENY")
	c.Header("X-Content-Type-Options", "nosniff")
	c.Header("X-XSS-Protection", "1; mode=block")
	if c.Request.TLS != nil {
		c.Header("Strict-Transport-Security", "max-age=31536000")
	}

	// Also consider adding Content-Security-Policy headers
	// c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com")
}

apiserver/handler/sd/check.go

package sd

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/shirou/gopsutil/cpu"
	"github.com/shirou/gopsutil/disk"
	"github.com/shirou/gopsutil/load"
	"github.com/shirou/gopsutil/mem"
)

const (
	B  = 1
	KB = 1024 * B
	MB = 1024 * KB
	GB = 1024 * MB
)

// HealthCheck shows `OK` as the ping-pong result.
func HealthCheck(c *gin.Context) {
	message := "OK"
	c.String(http.StatusOK, "\n"+message)
}

// DiskCheck checks the disk usage.
func DiskCheck(c *gin.Context) {
	u, _ := disk.Usage("/")

	usedMB := int(u.Used) / MB
	usedGB := int(u.Used) / GB
	totalMB := int(u.Total) / MB
	totalGB := int(u.Total) / GB
	usedPercent := int(u.UsedPercent)

	status := http.StatusOK
	text := "OK"

	if usedPercent >= 95 {
		status = http.StatusOK
		text = "CRITICAL"
	} else if usedPercent >= 90 {
		status = http.StatusTooManyRequests
		text = "WARNING"
	}

	message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent)
	c.String(status, "\n"+message)
}

// CPUCheck checks the cpu usage.
func CPUCheck(c *gin.Context) {
	cores, _ := cpu.Counts(false)

	a, _ := load.Avg()
	l1 := a.Load1
	l5 := a.Load5
	l15 := a.Load15

	status := http.StatusOK
	text := "OK"

	if l5 >= float64(cores-1) {
		status = http.StatusInternalServerError
		text = "CRITICAL"
	} else if l5 >= float64(cores-2) {
		status = http.StatusTooManyRequests
		text = "WARNING"
	}

	message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores)
	c.String(status, "\n"+message)
}

// RAMCheck checks the disk usage.
func RAMCheck(c *gin.Context) {
	u, _ := mem.VirtualMemory()

	usedMB := int(u.Used) / MB
	usedGB := int(u.Used) / GB
	totalMB := int(u.Total) / MB
	totalGB := int(u.Total) / GB
	usedPercent := int(u.UsedPercent)

	status := http.StatusOK
	text := "OK"

	if usedPercent >= 95 {
		status = http.StatusInternalServerError
		text = "CRITICAL"
	} else if usedPercent >= 90 {
		status = http.StatusTooManyRequests
		text = "WARNING"
	}

	message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent)
	c.String(status, "\n"+message)
}