跳到主要内容

流程

从不同的视角去看待web应用的流程:

  • 使用者视角:程序员如何使用gin来编写业务逻辑
  • 应用初始化:当进程启动时gin内部是如何初始化的
  • 请求生命周期:当一个HTTP请求来到服务器时后如何转化为响应

说这些之前了解一下Context这个结构体

Context结构体

简单介绍一下gin框架里面最重要的结构体Context,另外一个最重要的结构体是Engine,它作为单例存在;而Context是从对象池中得到。

// Context作为一个数据结构在中间件中传递本次请求的各种数据、管理流程,进行响应
// context.go:40
type Context struct {
// ServeHTTP的第二个参数: request
Request *http.Request

// 用来响应
Writer ResponseWriter
writermem responseWriter

// URL里面的参数,比如:/xx/:id
Params Params
// 参与的处理者(中间件 + 请求处理者列表)
handlers HandlersChain
// 当前处理到的handler的下标
index int8

// Engine单例
engine *Engine

// 在context可以设置的值
Keys map[string]interface{}

// 一系列的错误
Errors errorMsgs

// Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string
}

// response_writer.go:20
type ResponseWriter interface {
http.ResponseWriter //嵌入接口
http.Hijacker //嵌入接口
http.Flusher //嵌入接口
http.CloseNotifier //嵌入接口

// 返回当前请求的 response status code
Status() int

// 返回写入 http body的字节数
Size() int

// 写string
WriteString(string) (int, error)

//是否写出
Written() bool

// 强制写htp header (状态码 + headers)
WriteHeaderNow()
}

// response_writer.go:40
// 实现 ResponseWriter 接口
type responseWriter struct {
http.ResponseWriter
size int
status int
}


type errorMsgs []*Error


// 每当一个请求来到服务器,都会从对象池中拿到一个context。其函数有:
// **** 创建
reset() //从对象池中拿出来后需要初始化
Copy() *Context //克隆,用于goroute中
HandlerName() string //得到最后那个处理者的名字
Handler() //得到最后那个Handler

// **** 流程控制
Next() // 只能在中间件中使用,依次调用各个处理者
IsAborted() bool
Abort() // 废弃
AbortWithStatusJson(code int, jsonObj interface{})
AbortWithError(code int, err error) *Error

// **** 错误管理
Error(err error) *Error // 给本次请求添加个错误。将错误收集然后用中间件统一处理(打日志|入库)是一个比较好的方案

// **** 元数据管理
Set(key string, value interface{}) //本次请求用户设置各种数据 (Keys 字段)
Get(key string)(value interface{}, existed bool)
MustGet(key string)(value interface{})
GetString(key string) string
GetBool(key string) bool
GetInt(key string) int
GetInt64(key string) int64
GetFloat64(key string) float64
GetTime(key string) time.Time
GetDuration(key string) time.Duration
GetStringSlice(key string) []string
GetStringMap(key string) map[string]interface{}
GetStringMapString(key string) map[string]string
GetStringMapStringSlice(key string) map[string][]string

// **** 输入数据
//从URL中拿值,比如 /user/:id => /user/john
Param(key string) string

//从GET参数中拿值,比如 /path?id=john
GetQueryArray(key string) ([]string, bool)
GetQuery(key string)(string, bool)
Query(key string) string
DefaultQuery(key, defaultValue string) string
GetQueryArray(key string) ([]string, bool)
QueryArray(key string) []string

//从POST中拿数据
GetPostFormArray(key string) ([]string, bool)
PostFormArray(key string) []string
GetPostForm(key string) (string, bool)
PostForm(key string) string
DefaultPostForm(key, defaultValue string) string

// 文件
FormFile(name string) (*multipart.FileHeader, error)
MultipartForm() (*multipart.Form, error)
SaveUploadedFile(file *multipart.FileHeader, dst string) error

// 数据绑定
Bind(obj interface{}) error //根据Content-Type绑定数据
BindJSON(obj interface{}) error
BindQuery(obj interface{}) error

//--- Should ok, else return error
ShouldBindJSON(obj interface{}) error
ShouldBind(obj interface{}) error
ShouldBindJSON(obj interface{}) error
ShouldBindQuery(obj interface{}) error

//--- Must ok, else SetError
MustBindJSON(obj interface{}) error

ClientIP() string
ContentType() string
IsWebsocket() bool

// **** 输出数据
Status(code int) // 设置response code
Header(key, value string) // 设置header
GetHeader(key string) string

GetRawData() ([]byte, error)

Cookie(name string) (string, error) // 设置cookie
SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)

Render(code int, r render.Render) // 数据渲染
HTML(code int, name string, obj interface{}) //HTML
JSON(code int, obj interface{}) //JSON
IndentedJSON(code int, obj interface{})
SecureJSON(code int, obj interface{})
JSONP(code int, obj interface{}) //jsonp
XML(code int, obj interface{}) //XML
YAML(code int, obj interface{}) //YAML
String(code int, format string, values ...interface{}) //string
Redirect(code int, location string) // 重定向
Data(code int, contentType string, data []byte) // []byte
File(filepath string) // file
SSEvent(name string, message interface{}) // Server-Sent Event
Stream(step func(w io.Writer) bool) // stream


// **** 实现 context.Context 接口(GOROOT中)

使用者视角

编程其实是尝试的过程,通过反馈不断的修正。

简单的例子

// step1: 得到gin
go get github.com/gin-gonic/gin

// step2: 编辑main.go

import (
"net/http"

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

func main() {
// 创建一个Engine
r := gin.New()

// 定义一个处理者
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello World!")
})

// 再定义一个处理者
r.POST("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})

// 让其运行起来
r.Run("0.0.0.0:8888)
}

// step3: 运行
go run main.go

使用路由组和中间件

import (
"net/http"

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

func main() {
r := gin.New()

// 使用日志插件
r.Use(gin.Logger())

r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello world")
})

r.POST("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})

// 使用路由组
authGroup := r.Group("/auth", func(c *gin.Context) {
token := c.Query("token")
if token != "123456" {
c.AbortWithStatusJSON(200, map[string]string{
"code": "401",
"msg": "auth fail",
})
}

c.Next()
})

// 注册 /auth/info 处理者
authGroup.GET("/info", func(c *gin.Context) {
c.JSON(200, map[string]string{
"id": "1234",
"name": "name",
})
})

r.Run("0.0.0:8910")
}

很简单的注册就可以让应用跑起来了。

应用初始化

当我们运行 go run main.go 时gin都做了什么呢?

其实golang原生就支持http请求应用开发,任何golang web框架的本质只能是作为工具集存在的。

官方文档 Writing Web Applications介绍了如何写一个web应用。

示例代码:

// demo1
import (
"net/http"
)

func main() {
http.HandleFunc("/info", func(response http.ResponseWriter, request *http.Request) {
response.Write([]byte("info"))
})

http.ListenAndServe(":8888", nil)
}


// demo2
import (
"net/http"
)

type Handle struct{}

func (h Handle) ServeHTTP(response http.ResponseWriter, request *http.Request) {
switch request.URL.Path {
case "/info":
response.Write([]byte("info"))
default:

}
}

func main() {
http.ListenAndServe(":8888", Handle{})
}


上面两个代码非常简单,但是就可以在服务器上开始一个web应用。

而gin的本质也就是使用demo2的代码,进行封装,提供工具函数,方便业务开发。

回到本章的主题,应用初始化大概的过程包括:

  • 创建一个 Engine 对象
  • 注册中间件
  • 注册路由(组)

请求生命周期

因为golang原生为web而生而提供了完善的功能,用户需要关注的东西大多数是业务逻辑本身了。

gin能做的事情也是去把 ServeHTTP(ResponseWriter, *Request) 做得高效、友好。

一个请求来到服务器了,ServeHTTP 会被调用,gin做的事情包括:

  • 路由,找到handle
  • 将请求和响应用Context包装起来供业务代码使用
  • 依次调用中间件和处理函数
  • 输出结果