前言
在手写框架的时候,遇到了极多的问题,由于我是在参考这个项目模仿
Gin
框架,关于Gin
的一些设计还有不少要理解的路要走。对于我这个框架GoMatrix
,对他的期望就是简洁明了,就像python中的falsk一样。不过最终成品如何,只能看后续了。
Context
如果是学习过Gin框架的童鞋,看到这个上下文一定感到分外的亲切。是的,目前没有自己的想法,只能顺着Gin框架把这个上下文抄下来了。如果像上一篇文章那样,每一个函数都要根据请求*http.Request
,构造响应http.ResponseWriter
,但是这两个对象提供的接口粒度太细,比如我们要构造一个完整的响应,需要考虑消息头(Header)和消息体(Body),而 Header 包含了状态码(StatusCode),消息类型(ContentType)等几乎每次请求都需要设置的信息。
如果不进行有效的封装,那么框架的用户将需要写大量重复繁琐的代码,因此,能够简化相关接口调用,高效的构造出一个HTTP响应是一个框架应该考虑的点。
而且,我们都知道在内置的http包中,我们无法解析动态路由,比如:/hello/:name
,:name
的值放在哪儿呢。再比如中间件巴拉巴拉的,Context 随着每一个请求的出现而产生,请求的结束而销毁,和当前请求强相关的信息都应由 Context 承载。因此,设计 Context 结构,扩展性和复杂性留在了内部,而对外简化了接口。路由的处理函数,以及将要实现的中间件,参数都统一使用 Context 实例, Context 就像一次会话的百宝箱,可以找到任何东西。
新建一个文件context.go
:
package GoMatrix
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, req *http.Request) *Context {
return &Context{
Writer: w,
Req: req,
Path: req.URL.Path,
Method: req.Method,
}
}
// 访问Form参数的方法
func (c *Context) PostForm(key string) string {
return c.Req.FormValue(key)
}
// 访问Query参数的方法
func (c *Context) Query(key string) string {
return c.Req.URL.Query().Get(key)
}
// 构造响应状态码
func (c *Context) Status(code int) {
c.StatusCode = code
c.Writer.WriteHeader(code)
}
// 构造响应头
func (c *Context) SetHeader(key string, value string) {
c.Writer.Header().Set(key, value)
}
// 构造string响应
func (c *Context) String(code int, format string, values ...interface{}) {
c.SetHeader("Content-Type", "text/plain")
c.Status(code)
c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
}
// 构造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(), 500)
}
}
// 构造文件响应
func (c *Context) Data(code int, data []byte) {
c.Status(code)
c.Writer.Write(data)
}
// 构造HTML响应
func (c *Context) HTML(code int, html string) {
c.SetHeader("Content-Type", "text/html")
c.Status(code)
c.Writer.Write([]byte(html))
}
重点说下
Data
响应,因为返回的是个数据流,之前在使用Gin
的时候,踩过一个坑,就是在使用某个文件服务的时候,他没给返回文件的content-type,这就导致了一个问题,无法判断该数据流的类型,而在使用Gin的c.Data
时,contentType
又是一个必填项,这就造成了,如果该文件是pdf或者word,亦或者只是image/png,我就只能选其一。不过这也是极个别情况,因为正常文件服务一定会有
contentType
,我也只是倒霉的碰上一个没有的罢了。PS:我最后的解决办法是,使用了Gin上下文的
c.Writer.Write
方法,直接绕过了他的Data
抽离router
路由的设计是一个不小的工程,如果全挤在一个入口文件中,也是非常的不好看,而且修改也是很不易,所以直接将其在早期抽离,我也不能确定这个框架的代码量,有没有必要抽离成文件夹,后期如果实在不方便再抽离也行。
新建router.go
// 抽离router
type router struct {
handlers map[string]HandlerFunc
}
func newRouter() *router {
return &router{handlers: make(map[string]HandlerFunc)}
}
func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {
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)
}
}
同时,入口文件也要进行更改,将原来的原生http方法改成我们的上下文。
GoMatrix.go
// HandlerFunc定义使用的请求处理程序,替换成上下文
type HandlerFunc func(c *Context)
// 引擎实现ServeHTTP接口
type Engine struct {
router *router
}
// 初始化引擎
func New() *Engine {
return &Engine{router: newRouter()}
}
func (engine *Engine) addRoute(method string, pattern string, handler HandlerFunc) {
log.Printf("Route %4s - %s", method, pattern)
engine.router.addRoute(method, pattern, handler)
}
// GET定义添加GET请求的方法
func (engine *Engine) GET(pattern string, handler HandlerFunc) {
engine.addRoute("GET", pattern, handler)
}
// 定义添加POST请求的方法
func (engine *Engine) POST(pattern string, handler HandlerFunc) {
engine.addRoute("POST", pattern, handler)
}
// 定义添加PUT请求的方法
func (engine *Engine) PUT(pattern string, handler HandlerFunc) {
engine.addRoute("PUT", pattern, handler)
}
func (engine *Engine)DELETE(pattern string, handler HandlerFunc) {
engine.addRoute("DELETE", pattern, handler)
}
// 定义启动http服务器的方法
func (engine *Engine) Run(addr string) (err error) {
fmt.Printf("ListenAndServe %s \n", addr)
return http.ListenAndServe(addr, engine)
}
// 实现Handler接口
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := newContext(w, req)
engine.router.handle(c)
}
小总结
目前的上下文还是非常简单的,只是将其原来的对象包含进去,抽离了路由之后,文件的层次分的还算不错了。在调用 router.handle 之前,构造了一个 Context 对象,通过实现了 ServeHTTP 接口,接管了所有的 HTTP 请求,理解了Gin的一些底层原理,也是非常具有实质性进展的。
Unfinished to be continued…
- Post link: https://www.godhearing.cn/shou-xie-zi-ji-de-go-web-kuang-jia-2/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.