手把手和你一起實現一個Web框架實戰——EzWeb框架(二)[Go語言筆記]Go專案實戰

學習先生 發表於 2021-08-17
框架 Go

手把手和你一起實現一個Web框架實戰——EzWeb框架(二)[Go語言筆記]Go專案實戰

程式碼倉庫: github gitee 中文註釋,非常詳盡,可以配合食用
本篇程式碼,請選擇demo2 手把手和你一起實現一個Web框架實戰——EzWeb框架(二)[Go語言筆記]Go專案實戰

上一篇文章我們實現了框架的雛形,基本地實現了將原來的處理方法和監聽處理的例項指向我們自定義的例項。封裝出了GET,POST處理方法。完成了框架雛形。

本篇文章,我們將原本的handler方法中的引數 w http.ResponseWriter, req *http.Request 封裝到一個新的結構體Context中,同時替代掉原來的引數,並在該結構體提供響應的一些簡單的請求資料查詢功能和響應處理方法。能夠讓我們快速獲取資料以及構造響應。

手把手和你一起實現一個Web框架實戰——EzWeb框架(二)[Go語言筆記]Go專案實戰

一、設計這個Context

/*
@Time : 2021/8/17 下午1:46
@Author : Mrxuexi
@File : context.go
@Software: GoLand
*/
package Ez

import (
	"encoding/json"
	"net/http"
)

// H 為map[string]interface{}結構體起個別名,方便使用者在程式碼中構建JSON
type H map[string]interface{}

// Context 結構體,內部封裝了 http.ResponseWriter, *http.Request
type Context struct {
	Writer http.ResponseWriter
	Req *http.Request
	//請求的資訊,包括路由和方法
	Path string
	Method string
	//響應的狀態碼
	StatusCode int
}

//Context構造方法
func newContext(w http.ResponseWriter, req *http.Request) *Context {
	return &Context{
		Writer:     w,
		Req:        req,
		Path:       req.URL.Path,
		Method:     req.Method,
	}
}

// 訪問引數的處理方法PostForm和Query

// PostForm 根據key拿到請求中的表單內容
func (c Context) PostForm(key string) string {
	return c.Req.FormValue(key)
}

// Query 根據key獲取請求中的引數
func (c Context) Query(key string) string {
	return c.Req.URL.Query().Get(key)
}

//一些服用的前值方法Status處理響應狀態碼 SetHeader處理響應訊息頭

// Status 將狀態碼寫入context,同時將通過封裝起來的http.ResponseWriter方法,將狀態碼寫入響應頭
func (c Context) Status(code int)  {
	c.StatusCode = code
	c.Writer.WriteHeader(code)
}

// SetHeader 構造響應的訊息頭
func (c Context) SetHeader(key string,value string)  {
	c.Writer.Header().Set(key,value)
}

// String 呼叫我們的 SetHeader和Status 方法,構造string型別響應的狀態碼和訊息頭,然後將字串轉換成byte寫入到響應頭
func (c Context) String(code int,values ...interface{})  {
	c.SetHeader("Content-Type","text/plain")
	c.Status(code)
	var str = ""
	for _, value := range values {
		str += value.(string)
	}
	c.Writer.Write([]byte(str))
}

// JSON 呼叫我們的 SetHeader和Status 方法,構造JSON型別響應的狀態碼和訊息頭,根據我們傳入的物件來構造json資料寫入
func (c Context) JSON(code int,obj interface{})  {
	c.SetHeader("Content-Type","application/json")
	c.Status(code)
	encoder := json.NewEncoder(c.Writer)
	if err := encoder.Encode(obj); err != nil {
		http.Error(c.Writer, err.Error(),http.StatusInternalServerError)
	}
}

// Data 同上 ,但是直接寫入位元組陣列,不再構建訊息頭
func (c Context) Data(code int,data []byte)  {
	c.Status(code)
	c.Writer.Write(data)
}

// HTML 模版渲染 同上,訊息體傳入的是html檔案
func (c Context) HTML(code int, html string)  {
	c.SetHeader("Content-Type","text/html")
	c.Status(code)
	c.Writer.Write([]byte(html))
}

我們將這些內容全部封裝到了Context中,能夠讓我們快速獲取資料以及構造響應。

如果我們沒有進行封裝,構造一個Json響應是十分麻煩的:

1、構造一個訊息物件

//新建obj儲存資訊
obj := map[string]interface{}{
   "name" : "Mrxuexi",
   "password" : "password",
}

2、構造相應的訊息頭

//構造響應的訊息頭
w.Header().Set("Content-Type","application/json")

3、寫入響應的狀態碼

//w寫入狀態碼
w.WriteHeader(http.StatusOK)

4、根據已經做好的響應writer構建一個json編碼器

//根據w建立一個json編碼器
encoder := json.NewEncoder(w)

5、根據obj構建響應的訊息體

//將obj放入訊息體,處理報錯
if err := encoder.Encode(obj); err != nil {
   http.Error(w, err.Error(), 500)
}

如果我們進行了封裝,我們只需要這樣,省去了許多繁雜的工作:

c.JSON(http.StatusOK, context.H{
     "name" : "Mrxuexi",
   "password" : "password",
})

二、重新調整封裝入口

我們將原來的Ez.go進行重構,將原來的路由表router map 封裝到router.go。

HandlerFunc傳入的引數改為 *Context

type HandlerFunc func(*Context)

將原來的ServeHTTP 方法作為入口,將請求傳入構造context。

在router.go中進行addRoute路由註冊處理和對應的請求處理。

/*
@Time : 2021/8/16 下午4:03
@Author : Mrxuexi
@File : Ez
@Software: GoLand
*/

package Ez

import (
	"net/http"
)

// HandlerFunc 是Ez框架中定義的對請求的響應處理方法,傳入*Context針對http請求處理
type HandlerFunc func(*Context)

// Engine 實現了"net/http"標準庫中的 Handler 介面中的ServeHTTP方法
type Engine struct {
	//用於儲存路由處理方法
	//key是方法型別加路徑,value是使用者的處理方法
	router *router
}

// ServeHTTP 方法的實現,用於實現處理HTTP請求
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	//根據req和w例項一個context
	c := newContext(w, req)
	//傳入開始執行處理
	engine.router.handle(c)
}

// New 路由儲存結構的建構函式
func New() *Engine {
	return &Engine{router: newRouter()}
}

// addRoute 方法封裝在router中,在 router map[string]HandlerFunc 中存入對應處理方法
func (engine *Engine) addRoute(method string, path string, handler HandlerFunc) {
	engine.router.addRoute(method, path, handler)
}

// GET 實現的是註冊GET請求的路徑和對應方法,呼叫了addRoute,存入了route 結構體的handler中
func (engine *Engine) GET(path string, handler HandlerFunc) {
	engine.addRoute("GET", path, handler)
}

// POST 同上
func (engine *Engine) POST(path string, handler HandlerFunc) {
	engine.addRoute("POST", path, handler)
}


func (engine *Engine) Run(addr string) (err error) {
	return http.ListenAndServe(addr, engine)
}

三、重新調整封裝router,路由註冊處理和請求處理

所有的路由註冊處理和請求處理都從入口拿到這一層來處理。

/*
@Time : 2021/8/16 下午4:03
@Author : Mrxuexi
@File : Ez
@Software: GoLand
*/

package Ez

import (
	"net/http"
)

// HandlerFunc 是Ez框架中定義的對請求的響應處理方法,傳入*Context針對http請求處理
type HandlerFunc func(*Context)

// Engine 實現了"net/http"標準庫中的 Handler 介面中的ServeHTTP方法
type Engine struct {
	//用於儲存路由處理方法
	//key是方法型別加路徑,value是使用者的處理方法
	router *router
}

// ServeHTTP 方法的實現,用於實現處理HTTP請求
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	//根據req和w例項一個context
	c := newContext(w, req)
	//傳入開始執行處理
	engine.router.handle(c)
}

// New 路由儲存結構的建構函式
func New() *Engine {
	return &Engine{router: newRouter()}
}

// addRoute 方法封裝在router中,在 router map[string]HandlerFunc 中存入對應處理方法
func (engine *Engine) addRoute(method string, path string, handler HandlerFunc) {
	engine.router.addRoute(method, path, handler)
}

// GET 實現的是註冊GET請求的路徑和對應方法,呼叫了addRoute,存入了route 結構體的handler中
func (engine *Engine) GET(path string, handler HandlerFunc) {
	engine.addRoute("GET", path, handler)
}

// POST 同上
func (engine *Engine) POST(path string, handler HandlerFunc) {
	engine.addRoute("POST", path, handler)
}


func (engine *Engine) Run(addr string) (err error) {
	return http.ListenAndServe(addr, engine)
}

測試:

/*
@Time : 2021/8/16 下午4:01
@Author : mrxuexi
@File : main
@Software: GoLand
*/
package main

import (
	"Ez"
	"net/http"
)
func main() {
	r := Ez.New()
	r.GET("/", func(c *Ez.Context) {
		c.HTML(http.StatusOK,"<h1>This is the index</h1>")
	})
	r.GET("/hello", func(c *Ez.Context) {
		c.String(http.StatusOK, "hello")
	})  
	r.POST("/message", func(c *Ez.Context) {
		c.JSON(http.StatusOK,Ez.H{
			"name" : c.PostForm("name"),
			"age" : c.PostForm("age"),
		})
	})

	r.Run(":9090")
}

index:

手把手和你一起實現一個Web框架實戰——EzWeb框架(二)[Go語言筆記]Go專案實戰

GET請求hello:

手把手和你一起實現一個Web框架實戰——EzWeb框架(二)[Go語言筆記]Go專案實戰

POST請求hello:

手把手和你一起實現一個Web框架實戰——EzWeb框架(二)[Go語言筆記]Go專案實戰

成功!

接上期靈魂畫手環節:

手把手和你一起實現一個Web框架實戰——EzWeb框架(二)[Go語言筆記]Go專案實戰

參考:

[1]: https://github.com/geektutu/7days-golang/tree/master/gee-web gee [2]: https://github.com/gin-gonic/gin gin