手把手和你一起实现一个Web框架实战——EzWeb框架(二)[Go语言笔记]Go项目实战

代码仓库:
github
gitee
中文注释,非常详尽,可以配合食用
本篇代码,请选择demo2

上一篇文章我们实现了框架的雏形,基本地实现了将原来的处理方法和监听处理的实例指向我们自定义的实例。封装出了GET,POST处理方法。完成了框架雏形。

本篇文章,我们将原本的handler方法中的参数 w http.ResponseWriter, req *http.Request 封装到一个新的结构体Context中,同时替代掉原来的参数,并在该结构体提供响应的一些简单的请求数据查询功能和响应处理方法。能够让我们快速获取数据以及构造响应。

一、设计这个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)
	}
}func (c *Context) Data(code int,data []byte)  {


// Data 同上 ,但是直接写入字节数组,不再构建消息头
	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/17 下午3:31
@Author : Mrxuexi
@File : router
@Software: GoLand
*/
package Ez

import (
	"log"
	"net/http"
)

type router struct {
	handlers map[string]HandlerFunc
}

func newRouter() *router {
	return &router{handlers: make(map[string]HandlerFunc)}
}

// router 中 addRoute 方法,在 handlers map[string]HandlerFunc 中存入路由对应处理方法
//存入形式为例如:{ "GET-/index" : 定义的处理方法 }
func (r *router) addRoute(method string, path string, handler HandlerFunc)  {
	log.Printf("Route %4s  -  %s",method,path)
	key := method + "-" + path
	r.handlers[key] = handler
}

//根据context中存储的 c.Method 和 c.Path 拿到对应的处理方法,进行执行,如果拿到的路由没有注册,则返回404
func (r *router) handle(c *Context)  {
	key := c.Method + "-" + c.Path
	if handler, ok := r.handlers[key]; ok {
		handler(c)
	}else {
		c.String(http.StatusNotFound,"404 NOT FOUND")
	}
}

测试:

/*
@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:

GET请求hello:

POST请求hello:

成功!

接上期灵魂画手环节:

参考:

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

版权声明:本文为Mrxuexi原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/Mrxuexi/p/15153047.html