# 流程
从不同的视角去看待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](https://golang.org/doc/articles/wiki/)介绍了如何写一个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包装起来供业务代码使用
- 依次调用中间件和处理函数
- 输出结果