Golang http服务梳理

84 阅读4分钟

引子

刚开始学习使用net/http包搭建http服务时,基本都会看到如下代码:

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *Request) {
        fmt.Fprintln(w, "Hello World")
    })
    http.ListenAndServe()
}

代码非常简单,但想要知道它的内部是怎么处理的,我们可以查看源码,打开源码后发现,有很多相似的方法、结构体、接口等,刚开始的时候会感到云里雾里,不好屡清,下面,我将从一段简单的代码开始,逐步梳理其中的逻辑

一个最简单的http服务器

func main() {
    server := &http.Server{}
    server.ListenAndServe()
}

这两行代码(如果你想,甚至可以把它整理为一行),构造了一个最简单的http服务器,它会默认监听80端口,但不会处理任何请求,任何请求都将返回404。 这其实也是http.ListenAndServe源码,但更加精简,省略传递给Server结构体的参数。 Serverhttp包中定义的一个结构体,具体结构如下

type Server struct {
    Addr    string
    Handler Handler
    // 其它字段省略
}

其中Addr表示要监听的地址,格式为host:port,如:8080127.0.0.1:8080等;Handlerhttp包中定义的一个接口,只要实现了该接口,便可以作为Server中的Handler字段,具体看一下Handler接口的定义

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

非常简单,只要实现了ServeHTTP方法,便是Hanler接口类型。 我们来实现一个极简版的

package main

import (
    "fmt"
    "net/http"
)

type MyHandler struct{}

func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello World")
}

func main() {
    server := &http.Server{}
    server.Handler = new(MyHandler)
    server.ListenAndServe()
}

这时的服务器可以接收任何请求,任何请求都会返回Hello World

路由处理

上面的例子,不会区分路由,任何路由都会返回相同的结果,如何处理路由,最简单的方式是直接改造MyHandler中的ServeHTTP方法,如下

func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    switch r.URL.Path {
        case "/":
        fmt.Fprintln(w, "Hello World")
        default:
        fmt.Fprintln(w, "404")
    }
}

简单粗暴! 当然,也可以实现一个类似引子中的HandleFunc的方式,直接上代码

package main

import (
    "fmt"
    "net/http"
)

type MyHandler struct {
    m map[string]http.Handler
}

func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if _, ok := h.m[r.URL.Path]; !ok {
        fmt.Fprintln(w, "404")
        return
    }
    h.m[r.URL.Path].ServeHTTP(w, r)
}

func (h *MyHandler) HandleFunc(pattern string, handler func(w http.ResponseWriter, r *http.Request)) {
    if h.m == nil {
        h.m = map[string]http.Handler{}
    }
    h.m[pattern] = http.HandlerFunc(handler)
}

func main() {
    server := &http.Server{}
    handler := new(MyHandler)
    handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello World")
    })
    server.Handler = handler
    server.ListenAndServe()
}

这样一来,注册路由就比较优雅了。这里有个地方需要注意,就是MyhandlerHandleFunc方法中,使用了http.HandlerFunc进行了类型转换(注意并不是函数调用,而是类型转换),将func(http.ResponseWriter, *http.Request)函数转换成了http.Handler接口类型,它是怎么实现的呢?直接看源码

type HandlerFunc func(ResponseWriter, *Request)

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

http包中直接将HandleFunc定义为func(ResponseWriter, *Request)函数类型,而且HandleFunc又实现http.Handler接口的ServeHTTP方法,所以HandleFunc又是http.Handler接口类型的,所以以在执行http.HandlerFunc(handler)时,会将handler转换成http.HandleFunc类型,可以赋值给MyHandler中的m字段map的值(htttp.Handler类型)

封装

如果每次都要自己实现http.Handler接口,代码会比较啰嗦,官方http包中其实已经进行了封装,其内部定义了一个ServeMux类型

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}

type muxEntry struct {
    h       Handler
    pattern string
}

ServeMux对标上面自己定义的MyHandler,它内部也实现了HandleFuncServeHTTP方法以及一些其它方法,为了文章逻辑顺畅,具体不再描述。我们可以使用该结构体简化前面的代码

package main

import (
    "fmt"
    "net/http"
)

func main() {
    server := &http.Server{}
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello World")
    })
    server.Handler = mux
    server.ListenAndServe()
}

这样一来,不需要自己实现http.Handler了,直接使用官方提供的ServeMux即可,非常方便,但和引子中的示例一比,还是不够简单,那引子中的示例是如何实现的呢,稍微追一个源码便会清楚。http包中定义了一个全局变量DefaultServeMux,它是一个指向ServeMux类型实例的指针

var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

http.HandleFunc中正是使用了这个全局变量,代码非常简单

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

再看一下http.ListenAndServe

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

这样一来,引子中的示例为什么如此简单,也就十分清楚了

结尾

本文只是简单梳理了下golang搭建http服务的流程,以及它内部的一些原理,但其实源码还有很多值得说的地方,如http.ServerListenAndServe方法的一些细节,这里不再详述。有了大致的逻辑,再去详细看源码也会比较清楚了