自制Web框架:Gee框架

1.1 HTTP基础

在Go语言中实现一个HTTP服务器非常简单。

第一步需要实现一个处理器,所谓处理器就是实现了ServeHTTP方法的接口:

type hello struct {}

func (h *hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello, world!")
}

第二步是设定服务端口并将路由与处理器绑定:

helloFunc = new(hello)
// 设定端口
server := http.Server{
    Addr: "127.0.0.1:8080",
}
// 绑定路由
http.Handle("/hello", helloFunc)

最后一步启动服务:

server.ListenAndServe()

这时当我们在浏览器访问127.0.0.1:8080时,页面上会显示hello, world!

有一种将路由与相应函数绑定的简单方法:

// 该函数为处理器函数,有处理器有相同功能
func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello, world!")
}
// 绑定
http.HandleFunc("/hello", hello)

1.2 框架雏形

我们可以将上述内容进一步封装为更容易理解和使用的方式:

  • 创建处理器函数类型
type HandlerFunc func(http.ResponseWriter, *http.Request)
  • 创建框架入口
// 该结构体用于将路由与处理器函数绑定
// 使用Map结构可以轻易地得到路由对应的处理器函数
type Engine struct {
    router map[string]HandlerFunc
}
func New() *Engine {
    return &Engine{router: make(map[string]HandlerFunc)}
}
  • 路由与函数绑定
// 往Engine里添加路由
// 私有函数
func (engine *Engine) addRoute(method string, pattern string, handler HandlerFunc) {
    // method为请求方式,如get、post
    // pattern为路由,如'/hello'
    // handler为处理器函数
    key := method + "-" + pattern
    engine.router[key] = handler //绑定
}

// GET请求,公有函数
func (engine *Engine) GET(pattern string, handler HandlerFunc) {
    // 调用addRoute函数
    engine.addRoute("GET", pattern, handler)
}
func (engine *Engine) POST(pattern string, handler HandlerFunc) {
	engine.addRoute("POST", pattern, handler)
}
  • 启动服务
func (engine *Engine) Run(addr string) (err error) {
    // addr参数为服务所在的地址,如'127.0.0.1:8080'
    return http.ListenAndServe(addr, engine)
}

那么问题来了,http.ListenAndServe函数的第二个参数传了一个Engine,我们把很多的路由和相应的处理器函数都存放在engine里面,那么程序要怎么判断用户访问哪个路由并调用相应的函数呢?所以,我们要实现一个路由复用器:

func (engine *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // r.Method为用户的请求方式
    // r.URL.Path为用户访问的地址(路由)
    key := r.Method + "-" + r.URL.Path
    if handler, ok := engine.router[key]; ok {
        handler(w, r)
    } else {
        fmt.Fprintf(w, "404 NOT FOUND!")
    }
}

上述我们已经说过,所谓处理器就是实现了ServeHTTP方法的接口engine实现了ServeHTTP方法,说明engine成为了一个处理器。不过engine不处理任何路由,它只是将路由分配给相应的处理器函数去处理,它只是个路由分发器。

当用户启动服务时,会自动调用处理器engineServeHTTP方法,在该方法中会将不同的路由分配给不同的处理器函数去处理。

  • 框架雏形
// gee.go
package gee

import (
	"fmt"
	"net/http"
)

type HandlerFunc func(http.ResponseWriter, *http.Request)

type Engine struct {
	router map[string]HandlerFunc
}

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

func (engine *Engine) addRoute(method string, pattern string, handler HandlerFunc) {
	key := method + "-" + pattern
	engine.router[key] = handler
}

func (engine *Engine) GET(pattern string, handler HandlerFunc) {
	engine.addRoute("GET", pattern, handler)
}

func (engine *Engine) POST(pattern string, handler HandlerFunc) {
	engine.addRoute("POST", pattern, handler)
}

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

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	key := req.Method + "-" + req.URL.Path
	if handler, ok := engine.router[key]; ok {
		handler(w, req)
	} else {
		fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
	}
}

接下来我们来调用此框架:

package main

import (
	"fmt"
	"net/http"
	"gee"
)

func main() {
	r := gee.New()
	r.GET("/", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
	})

	r.GET("/hello", func(w http.ResponseWriter, req *http.Request) {
		for k, v := range req.Header {
			fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
		}
	})

	r.Run(":9999")
}

经验证,该程序完美的对不同的路由进行了相应的处理。

1.3 上下文

目标:封装RequestResponseWriter,提供对JSON、HTML等返回类型的支持。

前置内容

服务器在向客户端返回响应的时候,需要用到的是ResponseWriter。它里面有三个方法,分别是WriteWriteHeaderHeader

Write方法:接受一个字节数组作为参数,并将数组中的字节写入HTTP响应主体中。

WriteHeader方法:接受一个整数作为参数,并将这个整数用作HTTP响应的返回状态码。

Header用于修改响应头的内容,该方法应在其它两个方法之前使用,不然会失效。

  • 定义上下文结构体
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,
    }
}
  • 修改获取相关内容
func (c *Context) PostForm(key string) string {
    return c.Req.FormValue(key) // 获取表单字段的值
}
func (c *Context) Query(key string) string {
	// 获取get请求参数
    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, value string) {
    c.Writer.Header().Set(key, value)
}
//返回文本
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) //json在"encoding/json"包下
    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)
}

func (c *Context) HTML(code int, html string) {
	c.SetHeader("Content-Type", "text/html")
	c.Status(code)
	c.Writer.Write([]byte(html))
}
  • 对路由部分做修改
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) {
    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)
	}
}

你可能感兴趣的:(go,go)