做好每一件事,读好每一本书,天道酬勤
七天实现web框架--路由映射
2022-04-12 / 5 min read

最近在学习一个七天系列课程,然后在这里对自己学习到的东西进行一个总结。今天实现的是web框架中的路由映射。

Http-Net包

在实现这个路由解析器的时候,我们首先要明白go原本的http/net包是如何实现路由解析定向的。
其中重要的函数

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
 DefaultServeMux.HandleFunc(pattern, handler)
}

这里的函数参数,第一个是路径的函数,第二个是处理函数handle,这个函数最后调用的其实就是

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

也就是说实现了handler的接口,那么就有了处理请求的能力。那么我们来看实现handler这个接口的条件是什么。这里肤浅的解释一下这个go接口的实现,在go语言中是支持鸭子类型的,所以我们只要实现了接口里面的方法,那么就实现了这个接口。
handler接口:

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

实现这个接口我们就有能力处理网络的请求了。但是其中我们还有东西需要我们去了解
首先就是我们如何获取网络请求中的路径呢?
其实这个很简单,在我们的Request中其实是包含了请求的路径的,获取的方式:
req.URL.Path
获取了过后我们就可以将请求的路径和对应的函数的建立起映射的关系。
在这里我们简单的演示了一下上面提到的基本的使用,也就是实现基本的网络请求处理。

type engine struct {
}

func (e *engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	switch req.URL.Path {
	case "/":
		fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
	case "/hello":
		for k, v := range req.Header {
			fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
		}
	default:
		fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
	}
}
func main() {
	en := new(engine)
	log.Fatal(http.ListenAndServe(":9999", en))
}

然后我们就可以开始实现框架的第一步,路由映射关系。

框架映射结构

最后实现的源码:

package gee

import (
	"fmt"
	"net/http"
)

// HandlerFunc defines the request handler used by gee
type HandlerFunc func(http.ResponseWriter, *http.Request)

// Engine implement the interface of ServeHTTP
type Engine struct {
	router map[string]HandlerFunc
}

// New is the constructor of gee.Engine
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
}

// GET defines the method to add GET request
func (engine *Engine) GET(pattern string, handler HandlerFunc) {
	engine.addRoute("GET", pattern, handler)
}

// POST defines the method to add POST request
func (engine *Engine) POST(pattern string, handler HandlerFunc) {
	engine.addRoute("POST", pattern, handler)
}

// Run defines the method to start a http server
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)
	}
}

在这里我们可以看到的是我们这里的实现的效果和gin很像,这里我们建立了一个map,用来映射路由和函数,然后这里我们在存储的时候,我们还将请求的方法进行了一个存储,也就是说相同的路径但是不同的请求方式他最后调用的函数也是不一样的。