# 流程
從不同的視角去看待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包裝起來供業務代碼使用
- 依次調用中間件和處理函數
- 輸出結果