自定义路由实现方式核心需要用到http包提供的ListenAndServe方法,那就从ListenAndServe源码开始入手。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
//从入参可知第一个参数是地址,第二个参数是一个Handler,Handler是一个interface,包含一个需要实现的ServeHTTP方法
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
//继续深入看一下这个addr是什么
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
//从此处可以看出,addr是一个监听端口,写法为":7670",有兴趣的朋友可以继续深入
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
//继续深入
func (srv *Server) Serve(l net.Listener) error {
//...
//...
for {
rw, err := l.Accept()
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
}
//继续深入
func (c *conn) serve(ctx context.Context) {
//...
//...
//核心是使用c.server实现的ServeHTTP,c.server是ListenAndServe中初始化的结构体server := &Server{Addr: addr, Handler: handler},由此可知,我们只需要自定义一个结构体去实现ServeHTTP方法,并将对应结构体当作ListenAndServe参数传入就可以实现自定义ServeHTTP
serverHandler{c.server}.ServeHTTP(w, w.req)
//...
//...
}
原理明白后,简单测试一下:
//定一个类
type test struct {
}
//自定类实现ServeHTTP
func (t *test) ServeHTTP(w http.ResponseWriter, req *http.Request) {
fmt.Printf("%+v", w)
res, _ := w.Write([]byte("sdasdasdasdsda"))
fmt.Println(res)
fmt.Printf("%+v", req)
}
//主函数启动
func main() {
a := &test{}
log.Fatal(http.ListenAndServe(":7670", a))
}
//终端测试
//curl 'http://127.0.0.1:7670/test/ob767' -v
//* Trying 127.0.0.1...
//* TCP_NODELAY set
//* Connected to 127.0.0.1 (127.0.0.1) port 7670 (#0)
//> GET /test/ob767 HTTP/1.1
//> Host: 127.0.0.1:7670
//> User-Agent: curl/7.64.1
//> Accept: */*
//>
//< HTTP/1.1 200 OK
//< Date: Tue, 19 Jul 2022 03:26:55 GMT
//< Content-Length: 14
//< Content-Type: text/plain; charset=utf-8
//<
//* Connection #0 to host 127.0.0.1 left intact
//sdasdasdasdsda* Closing connection 0
//看一下服务终端的打印
//fmt.Printf("%+v", w) &{conn:0xc000132960 req:0xc0000b0000 reqBody:{} cancelCtx:0x10cb3a0 wroteHeader:false wroteContinue:false wants10KeepAlive:false wantsClose:false canWriteContinue:0 writeContinueMu:{state:0 sema:0} w:0xc00009a100 cw:{res:0xc0000bc000 header:map[] wroteHeader:false chunking:false} handlerHeader:map[] calledHeader:false written:0 contentLength:-1 status:0 closeAfterReply:false requestBodyLimitHit:false trailers:[] handlerDone:0 dateBuf:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] clenBuf:[0 0 0 0 0 0 0 0 0 0] statusBuf:[0 0 0] closeNotifyCh:0xc0000ba000 didCloseNotify:0}
//res, _ := w.Write([]byte("sdasdasdasdsda"))
//fmt.Println(res) 14
//fmt.Printf("%+v", req) &{Method:GET URL:/test/ob767 Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[Accept:[*/*] User-Agent:[curl/7.64.1]] Body:{} GetBody:<nil> ContentLength:0 TransferEncoding:[] Close:false Host:127.0.0.1:7670 Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr:127.0.0.1:58419 RequestURI:/test/ob767 TLS:<nil> Cancel:<nil> Response:<nil> ctx:0xc00009a0c0}
通过测试发现,req能获取到http请求相关数据,那我们针对这些数据就可以进行路由方式的自定义,而w是一个回显结构体实例,通过w可以实现数据的回显。该方法可以结合反射可以实现自动设置路由,是比较优雅的路由加载方式。