golang http server源码解读

1. 初识

http 是典型的 C/S 架构,客户端向服务端发送请求(request),服务端做出应答(response)。

golang 的标准库 net/http 提供了 http 编程有关的接口,封装了内部TCP连接和报文解析的复杂琐碎的细节,使用者只需要和 http.request 和 http.ResponseWriter 两个对象交互就行。也就是说,我们只要写一个 handler,请求会通过参数传递进来,而它要做的就是根据请求的数据做处理,把结果写到 Response 中。废话不多说,来看看 hello world 程序有多简单吧!

package main

import (
    "io"
    "net/http"
)

type helloHandler struct{}

func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello, world!"))
}

func main() {
    http.Handle("/", &helloHandler{})
    http.ListenAndServe(":12345", nil)
}

运行 go run hello_server.go ,我们的服务器就会监听在本地的 12345 端口,对所有的请求都会返回 hello, world! :

正如上面程序展示的那样,我们只要实现的一个 Handler,它的 接口原型 是(也就是说只要实现了 ServeHTTP 方法的对象都可以作为 Handler):

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

然后,注册到对应的路由路径上就 OK 了。

http.HandleFunc 接受两个参数:第一个参数是字符串表示的 url 路径,第二个参数是该 url 实际的处理对象。

http.ListenAndServe 监听在某个端口,启动服务,准备接受客户端的请求(第二个参数这里设置为 nil ,这里也不要纠结什么意思,后面会有讲解)。每次客户端有请求的时候,把请求封装成 http.Request ,调用对应的 handler 的 ServeHTTP 方法,然后把操作后的 http.ResponseWriter 解析,返回到客户端。

2. 封装

上面的代码没有什么问题,但是有一个不便:每次写 Handler 的时候,都要定义一个类型,然后编写对应的 ServeHTTP 方法,这个步骤对于所有 Handler 都是一样的。重复的工作总是可以抽象出来, net/http 也正这么做了,它提供了 http.HandleFunc 方法,允许直接把特定类型的函数作为 handler。上面的代码可以改成:

package main

import (
    "io"
    "net/http"
)

func helloHandler(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "hello, world!\n")
}

func main() {
    http.HandleFunc("/", helloHandler)
    http.ListenAndServe(":12345", nil)
}

其实, HandleFunc 只是一个适配器,

// 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 object that calls f.
type HandlerFunc func(ResponseWriter, *Request)

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

自动给 f 函数添加了 HandlerFunc 这个壳,最终调用的还是 ServerHTTP ,只不过会直接使用 f(w, r) 。这样封装的好处是:使用者可以专注于业务逻辑的编写,省去了很多重复的代码处理逻辑。如果只是简单的 Handler,会直接使用函数;如果是需要传递更多信息或者有复杂的操作,会使用上部分的方法。

如果需要我们自己写的话,是这样的:

package main

import (
    "io"
    "net/http"
)

func helloHandler(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "hello, world!\n")
}

func main() {
    // 通过 HandlerFunc 把函数转换成 Handler 接口的实现对象
    hh := http.HandlerFunc(helloHandler)
    http.Handle("/", hh)
    http.ListenAndServe(":12345", nil)
}

3. 默认

大部分的服务器逻辑都需要使用者编写对应的 Handler,不过有些 Handler 使用频繁,因此 net/http 提供了它们的实现。比如负责文件 hosting 的 FileServer 、负责 404 的 NotFoundHandler 和 负责重定向的 RedirectHandler 。下面这个简单的例子,把当前目录所有文件 host 到服务端:

package main

import (
    "net/http"
)

func main() {
    http.ListenAndServe(":12345", http.FileServer(http.Dir(".")))
}

强大吧!只要一行逻辑代码就能实现一个简单的静态文件服务器。从这里可以看出一件事: http.ListenAndServe 第二个参数就是一个 Handler 函数(请记住这一点,后面有些内容依赖于这个)。

运行这个程序,在浏览器中打开 http://127.0.0.1:12345 ,可以看到所有的文件,点击对应的文件还能看到它的内容。

其他两个 Handler,这里就不再举例子了,读者可以自行参考文档。

4. 路由

虽然上面的代码已经工作,并且能实现很多功能,但是实际开发中,HTTP 接口会有许多的 URL 和对应的 Handler。这里就要讲 net/http 的另外一个重要的概念: ServeMux 。 Mux 是 multiplexor 的缩写,就是多路传输的意思(请求传过来,根据某种判断,分流到后端多个不同的地方)。 ServeMux 可以注册多了 URL 和 handler 的对应关系,并自动把请求转发到对应的 handler 进行处理。我们还是来看例子吧:

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值