七天用GO从零实现系列-学习

day2-上下文Context
 

将路由(router)独立出来,方便之后增强。

Gee\http-base\base1\gee\router.go
package gee

import (
	"log"
	"net/http"
)

type router struct {
	handlers map[string]HandlerFunc
}

// 创建并返回一个新的路由器实例,初始化了路由处理函数的映射
func newRouter() *router {
	return &router{handlers: make(map[string]HandlerFunc)}
}

// 添加一个路由规则,将请求方法、URL 模式和处理函数映射起来,并打印添加的路由信息。
func (r *router) addRoute(method, pattern string, handler HandlerFunc) {
	log.Printf("Route %4s - %s", method, pattern)
	key := method + "-" + pattern
	r.handlers[key] = handler
}

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:%s\n", c.Path)
	}
}

设计上下文(Context),封装 Request 和 Response ,提供对 JSON、HTML 等返回类型的支持。 

Gee\http-base\base1\gee\context.go

package gee

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

type H map[string]interface{}

type Context struct {
	Writer http.ResponseWriter
	Req    *http.Request
	//请求路径
	Path string
	//请求方法
	Method string
	//状态码
	StatusCode int
}

func newContext(w http.ResponseWriter, r *http.Request) *Context {
	return &Context{
		Writer: w,
		Req:    r,
		Path:   r.URL.Path,
		Method: r.Method,
	}
}

/*
http.Request 对象提供了一个方法 FormValue(key string) string,
用于获取请求中的表单参数。传入的 key 参数是你想要查找的表单参数的键名。
FormValue 会返回表单中对应键的值
当客户端发起一个 POST 请求,并且在请求体中包含表单数据时,你可以通过调用 PostForm 方法来获取指定键的值。

例如,如果请求体中包含数据 name=John&age=25,
调用 c.PostForm("name") 将会返回 John,而 c.PostForm("age") 将会返回 25。
*/
func (c *Context) PostForm(key string) string {
	return c.Req.FormValue(key)
}

/*
http.Request 对象的 URL 字段提供了一个 Query() 方法,用于解析 URL 中的查询参数,并返回一个 url.Values 类型的对象,其中包含了所有的查询参数。
Get(key string):url.Values 对象提供了一个 Get(key string) 方法,用于获取指定键的查询参数的值。
因此,调用 Query 方法时,它会从请求的 URL 中提取出查询参数,并返回指定键的值。

例如,如果 URL 是 http://example.com?name=John&age=25,调用 c.Query("name") 将会返回 John,而 c.Query("age") 将会返回 25
*/
func (c *Context) Query(key string) string {
	return c.Req.URL.Query().Get(key)
}

/*
这一行将输入的状态码 code 赋值给 Context 结构体中的 StatusCode 字段,以便后续可以在处理程序中访问这个状态码
WriteHeader(code) 方法用于设置 HTTP 响应的状态码,但不会写入响应体。这里用输入的状态码 code 来设置响应的状态码
*/
func (c *Context) Status(code int) {
	c.StatusCode = code
	c.Writer.WriteHeader(code)
}

/*
Header() 方法返回一个 http.Header 对象,表示 HTTP 响应的头部信息
Set(key, value string):http.Header 对象提供了一个 Set(key, value string) 方法,用于设置指定键的头部字段的值。
在这里,我们传入的 key 是要设置的头部字段的键名,value 是对应的值。
因此,调用 SetHeader 方法时,它会在 HTTP 响应的头部信息中添加指定的键值对,用于定制响应的头部
*/
func (c *Context) SetHeader(key string, value string) {
	c.Writer.Header().Set(key, value)
}

func (c *Context) String(code int, format string, values ...interface{}) {
	//调用 SetHeader 方法设置 HTTP 响应头部的 Content-Type 字段为 text/plain,表示响应的内容类型是纯文本
	c.SetHeader("Content-Type", "text/plain")
	//调用Status方法设置HTTP响应的状态码为输入的code值
	c.Status(code)
	//将格式化后的字符串转换为字节数组,并通过 c.Writer 的 Write 方法写入 HTTP 响应体中,以便发送给客户端。
	c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
}

/*
这个方法的功能是向客户端发送一个 JSON 格式的响应
obj interface{}:要序列化为 JSON 格式并发送给客户端的对象
使用 JSON 编码器将 obj 对象序列化为 JSON 格式,并写入到 c.Writer 中。
*/
func (c *Context) JSON(code int, obj interface{}) {
	//调用 SetHeader 方法设置 HTTP 响应头部的 Content-Type 字段为 application/json,表示响应的内容类型是 JSON 格式
	c.SetHeader("Content-Type", "application/json")
	//调用Status方法设置HTTP响应的状态码为输入的code值
	c.Status(code)
	//创建一个新的 JSON 编码器,将要发送的 JSON 内容写入到 c.Writer 中,
	//这里 c.Writer 是 http.ResponseWriter 接口的一个实现,用于写入 HTTP 响应
	encoder := json.NewEncoder(c.Writer)
	if err := encoder.Encode(obj); err != nil {
		//如果在序列化过程中发生错误,就使用 http.Error 函数将错误信息以 HTTP 500 的状态码发送给客户端。
		http.Error(c.Writer, err.Error(), 500)
	}
}

/*
调用 Data 方法会根据输入的状态码和字节数据,直接将数据发送给客户端。
这个方法通常用于发送文件、图片、音频等二进制数据,或者其他不需要特殊处理的数据
*/
func (c *Context) Data(code int, data []byte) {
	//调用 Status 方法设置 HTTP 响应的状态码为输入的 code 值
	c.Status(code)
	//通过 c.Writer 的 Write 方法直接将输入的字节数据 data 写入 HTTP 响应体中,以便发送给客户端。
	c.Writer.Write(data)
}

/*
这个方法的功能是向客户端发送 HTML 格式的响应
*/
func (c *Context) HTML(code int, html string) {
	//调用 SetHeader 方法设置 HTTP 响应头部的 Content-Type 字段为 text/plain,表示响应的内容类型是纯文本
	c.SetHeader("Content-Type", "text/html")
	//调用 Status 方法设置 HTTP 响应的状态码为输入的 code 值
	c.Status(code)
	//将输入的 HTML 字符串转换为字节数组,并通过 c.Writer 的 Write 方法写入 HTTP 响应体中,以便发送给客户端。
	c.Writer.Write([]byte(html))
}
  • 代码最开头,给map[string]interface{}起了一个别名gee.H,构建JSON数据时,显得更简洁
  • Context目前只包含了http.ResponseWriter和*http.Request,另外提供了对 Method 和 Path 这两个常用属性的直接访问。 提供了访问Query和PostForm参数的方法。
  • 提供了快速构造String/Data/JSON/HTML响应的方法

对Web服务来说,无非是根据请求*http.Request,构造响应http.ResponseWriter。但是这两个对象提供的接口粒度太细,比如我们要构造一个完整的响应,需要考虑消息头(Header)和消息体(Body),而 Header 包含了状态码(StatusCode),消息类型(ContentType)等几乎每次请求都需要设置的信息。因此,如果不进行有效的封装,那么框架的用户将需要写大量重复,繁杂的代码,而且容易出错。针对常用场景,能够高效地构造出 HTTP 响应是一个好的框架必须考虑的点。

最重要的还是通过实现了 ServeHTTP 接口,接管了所有的 HTTP 请求。相比第一天的代码,这个方法也有细微的调整,在调用 router.handle 之前,构造了一个 Context 对象

上图所示,使用JSON数据会更加简洁清晰

极兔老师的源代码

使用postman发送post请求给http://localhost:9999/login

我修改之后,返回了用户名和密码,这里跟我之前学的gin感觉是一样的,有更好的可以提出

package main

import (
	"net/http"

	"gee"
)

func main() {
	r := gee.New()
	r.GET("/", func(c *gee.Context) {
		c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
	})
	r.GET("/hello", func(c *gee.Context) {
		c.String(http.StatusOK, "Hello %s ,you're at %s\n", c.Query("name"), c.Path)
	})
	r.POST("/login", func(c *gee.Context) {
		var data struct {
			Username string `json:"username"`
			Password string `json:"password"`
		}
		if err := c.ShouldBindJSON(&data); err != nil {
			c.JSON(http.StatusBadRequest, gee.H{
				"error": err.Error(),
			})
			return
		}
		c.JSON(http.StatusOK, gee.H{
			"username": data.Username,
			"password": data.Password,
		})
	})
	r.Run(":9999")
}

 使用postman发送post请求给http://localhost:9999/login

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值