# 流程 从不同的视角去看待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包装起来供业务代码使用 - 依次调用中间件和处理函数 - 输出结果