【服务计算】开发web服务程序

本文详细分析了Golang的net/http库源码,讲解了HTTP服务的基础要求,包括主要概念如HTTP、Handler、ServeMux和http.HandlerFunc。还介绍了创建HTTP服务的过程,如注册路由、开启监听和处理请求。通过源码解析,阐述了ServeMux的工作原理以及如何使用HandlerFunc创建处理函数。最后总结了http服务器的工作流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础要求:

用GIN框架实现了一个简单的类似cloudgo的应用,地址:cloudgo

扩展任务:

本文分析net/http库源码,gorilla/mux源码分析地址:【服务计算】gorilla/mux源码分析


net/http库源码分析

目录
一、概念
HTTP
Handler
ServeMux
http.HandlerFunc

二、创建http服务
注册路由
开启监听
处理请求

总结

参考资料

对于Golang,实现一个最简单的http server用不着几行,却能带来极具杀伤力的性能。

一个Go最简单的http服务器:

package main

import (
    "fmt"
    "net/http"
)

func IndexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello world")
}

func main() {
    http.HandleFunc("/", IndexHandler)
    http.ListenAndServe("127.0.0.0:8000", nil)
}

一、主要概念

HTTP

除去细节,理解 HTTP 构建的网络应用只要关注两个端—客户端(clinet)和服务端(server),两个端的交互来自 clinet 的 request,以及server端的response。所谓的http服务器,主要在于如何接受 clinet 的 request,并向client返回response。

接收request的过程中,最重要的莫过于路由(router),即实现一个Multiplexer器。Go中既可以使用内置的mutilplexer — DefautServeMux,也可以自定义。Multiplexer路由的目的就是为了找到处理器函数(handler),后者将对request进行处理,同时构建response。

Handler

Golang没有继承,类多态的方式可以通过接口实现。任何结构体,只要实现了ServeHTTP方法,这个结构就可以称之为handler对象。ServeMux会使用handler并调用其ServeHTTP方法处理请求并返回响应。

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

ServeMux

ServeMux是HTTP请求的多路转接器(路由),它负责将每一个接收到的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。它内部用一个map来保存所有处理器Handler。

ServeMux的源码很简单:

type ServeMux struct {
    mu    sync.RWMutex//mu是锁,由于请求涉及到并发处理,因此这里需要一个锁机制。
    m     map[string]muxEntry//ServeMux结构中最重要的字段为m,m是一个map,一个string对应一个mux实体,这里的string就是注册的路由表达式。
    hosts bool //hosts表示是否在任意的规则中带有host信息。
}

下面看一下muxEntry

type muxEntry struct {
    explicit bool//explicit表示是否精确匹配
    h        Handler//h表示这个路由表达式对应的handler
    pattern  string//pattern是匹配字符串
}

ServeMux的ServeHTTP方法不是用来处理request和respone的,而是用来找到路由注册的handler.

http.HandlerFunc

http.HandlerFunc函数类型满足Handler接口

type HandlerFunc func(ResponseWriter, *Request)
//实现Handler接口的ServeHTTP方法
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r) //调用自身
}

二、创建http服务

创建一个http服务,大致需要经历两个过程,首先需要注册路由,即提供url模式和handler函数的映射,其次就是实例化一个server对象,并开启对客户端的监听。

注册路由

在这里,我们是这样注册路由的:

http.HandleFunc("/", IndexHandler)

http.HandleFunc选取了DefaultServeMux作为multiplexer:

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

此外,我们还可以使用自定义的路由来注册处理函数,也可以直接自定义一个Server实例(这种模式可以很方便的管理服务端的行为)。

DefaultServeMux的HandleFunc(pattern, handler)方法实际是定义在ServeMux下的:

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}

上述代码中,HandlerFunc是一个函数类型。同时实现了Handler接口的ServeHTTP方法。使用HandlerFunc类型包装一下路由定义的indexHandler函数,其目的就是为了让这个函数也实现ServeHTTP方法,即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。

type HandlerFunc func(ResponseWriter, *Request)

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

此外,ServeMux的Handle方法,将会对pattern和handler函数做一个map映射,把handler和pattern模式绑定到map[string]muxEntry的map上,此时,pattern和handler的路由注册完成。

开启监听

注册好路由之后,启动web服务还需要开启服务器监听:

http.ListenAndServe("127.0.0.1:8000", nil)

http的ListenAndServe方法中可以看到创建了一个Server对象,并调用了Server对象的同名方法:

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

func (srv Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(net.TCPListener)})
}

server.ListenAndServe()方法内部调用net.Listen(“tcp”, addr),该方法内部又调用net.ListenTCP()创建并返回一个监听器net.Listener,然后把监听器 ln 断言转换为 TCPListener 类型,并根据它构造一个tcpKeepAliveListener 对象并传递给server.Serve()方法:

func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
...

baseCtx := context.Background()
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
for {
    rw, e := l.Accept()
    ...
    c := srv.newConn(rw)
    c.setState(c.rwc, StateNew) // before Serve can return
    go c.serve(ctx)
}
}

Serve方法比较长,其主要职能就是,创建一个上下文对象,然后调用Listener的Accept方法用来获取连接数据并使用newConn方法创建连接对象。与我们一般编写的http服务器不同, Go为了实现高并发和高性能, 使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。

处理请求

监听开启之后,一旦客户端请求到底,go就开启一个go程处理请求,主要逻辑都在serve方法之中。而conn.serve()方法会读取请求,然后根据conn内保存的server来构造一个serverHandler类型,并调用它的ServeHTTP()方法:serverHandler{c.server}.ServeHTTP(w, w.req),该方法的源码如下:

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
   handler := sh.srv.Handler
   if handler == nil {
      handler = DefaultServeMux
   }
   if req.RequestURI == "*" && req.Method == "OPTIONS" {
      handler = globalOptionsHandler{}
   }
   handler.ServeHTTP(rw, req)
}

如上源码可以看到,当 handler == nil 时使用默认的DefaultServeMux路由,否则使用在第1步中为Serve指定了的Handler;然后调用该Handler的ServeHTTP方法(该Handler一般被设置为路由ServeMux类型).

而路由ServeMux的ServeHTTP方法则会根据当前请求提供的信息来查找最匹配的Handler(这里为):

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        if r.ProtoAtLeast(1, 1) {
            w.Header().Set("Connection", "close")
        }
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r) //规范化请求的路径格式,查找最匹配的Handler
    h.ServeHTTP(w, r)
}

以上查找到的Handler接口值h就是我们事先注册到路由中与请求匹配的Handler;而h的动态类型是HandlerFunc类型(它也满足Handler接口),所以,以上 h.ServeHTTP(w, r) 实际上调用的是接口值h中持有的动态值。

至此,Golang中一个完整的http服务介绍完毕,包括注册路由,开启监听,处理连接,路由处理函数。

总结

通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。

首先调用Http.HandleFunc

按顺序做了几件事:

1 调用了DefaultServeMux的HandleFunc

2 调用了DefaultServeMux的Handle

3 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则

其次调用http.ListenAndServe(“127.0.0.0:8000”, nil)

按顺序做了几件事情:

1 实例化Server

2 调用Server的ListenAndServe()

3 调用net.Listen(“tcp”, addr)监听端口

4 启动一个for循环,在循环体中Accept请求

5 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()

6 读取每个请求的内容w, err := c.readRequest()

7
判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux

8 调用handler的ServeHttp

9 在这个例子中,下面就进入到DefaultServeMux.ServeHttp

10 根据request选择handler,并且进入到这个handler的ServeHTTP

mux.handler(r).ServeHTTP(w, r)
11 选择handler:

   A 判断是否有路由能满足这个request(循环遍历ServeMux的muxEntry)

   B 如果有路由满足,调用这个路由handler的ServeHTTP

   C 如果没有路由满足,调用NotFoundHandler的ServeHTTP

参考资料

Golang构建HTTP服务(一)— net/http库源码笔记
3.4 Go的http包详解
net/http包的使用模式和源码解析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值