本文介绍Gin的路由控制。

上一篇文章中直接用Golang自带的net/http实现了一个Web服务,并且具备了路由功能。但是,net/http在功能和性能上都有所欠缺。 比如从功能上来说, 现在的很多RESTful接口的定义方式中,习惯使用不同的请求方法来实现不同的语义:GET、POST、HEAD、DELETE、OPTION等等。 如何用net/http单独的对请求方法注册特定的处理函数也变成了一个麻烦的问题。再比如RESTful风格的API重度依赖请求路径。会将很多参数放在请求URI中,支持这些URI的参数也是一个问题。

route实现原理

首先看一下net/http是如何实现路由功能的。上一篇的例子中

http.HandleFunc("/", handler)

这条语句通过http.HandleFunc函数注册了对路径 / 处理的函数handler。看一下它内部的实现

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

可以看到是调用了 DefaultServeMux 的 HandleFunc 函数。再看 DefaultServeMux 的定义:

var defaultServeMux ServeMux

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	hosts bool // whether any patterns contain hostnames
}

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

mu 是读写的信号量,m 是一个map结构,用来存储路径与handler的对应关系。

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	if pattern == "" {
		panic("http: invalid pattern")
	}
	if handler == nil {
		panic("http: nil handler")
	}
	if _, exist := mux.m[pattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}

	if mux.m == nil {
		mux.m = make(map[string]muxEntry)
	}
	mux.m[pattern] = muxEntry{h: handler, pattern: pattern}

	if pattern[0] != '/' {
		mux.hosts = true
	}
}

从源码上可以看到,实际上添加路由信息的过程先判断对应的handler是否为空,不为空的话,将对应的路径和handler添加到ServeMux.m中。

在处理请求的时候,实际是调用了ServerHTTP的函数:

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler
	
	//这个判断成立,因为我们传递的是nil
	if handler == nil {
		handler = DefaultServeMux
	}
	//省略了一些代码
	handler.ServeHTTP(rw, req)
}

也就是说,在我们启动Web服务的时候

http.ListenAndServe(":8080", nil)

如果第二个handler参数传的是nil,就用启用默认的DefaultServeMux , 而我们之前已经把对应的路由加到了DefaultServeMux的map里边,来了请求之后直接去map里边找对应的关系就行。

换句话说,如果我们要实现自己的路由功能,只需要按照默认的格式定义路由,在启动的时候,将我们的自定义路由传入到ListenAndServe的第二个参数即可。

httprouter

httprouter就是一个现成的高效率的路由实现,现在不少go web的框架使用了httprouter对路由进行支持,对应的源码地址: https://github.com/julienschmidt/httprouter 。 我们先用httprouter来改写上一篇的web服务。

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

func handler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprintf(w, "Hi there")
}

func main() {
	router := httprouter.New()
	router.GET("/", handler)
	log.Fatal(http.ListenAndServe(":8080", router))
}

我们首先生成了一个新的路由服务 httprouter.New(), 然后添加了对于 / 的GET请求的处理。启动的时候把这个router作为第二个参数传入到ListenAndServe然后就生效了。

同样,利用httprouter,我们能够简单有效对其他比如POST、HEAD等操作提供支持,也能快速的实现路由参数的功能,具体的可以参考httprouter的使用。

下一篇仿照Gin实现前缀路由的功能。