Golang Http Server源码阅读

简介:

这篇文章出现的理由是业务上需要创建一个Web Server。创建web是所有语言出现必须实现的功能之一了。在nginx+fastcgi+php广为使用的今天,这里我们不妨使用Go来进行web服务器的搭建。

前言

使用Go搭建Web服务器的包有很多,大致有下面几种方法,直接使用net包,使用net.http包,使用第三方包(比如gorilla)。使用net包就需要从tcp层开始封装,耗费人力物力极大,果断舍弃。直接使用封装好的net.http和第三方包才是上策。这里我们就选择了使用官方提供的net.http包来搭建web服务。另外附带一句,gorilla的第三方包现在使用还是非常广的,文档也是比较全的,有兴趣的同学可以考虑使用一下。

 

建议看这篇文章前先看一下net/http文档 http://golang.org/pkg/net/http/

 

net.http包里面有很多文件,都是和http协议相关的,比如设置cookie,header等。其中最重要的一个文件就是server.go了,这里我们阅读的就是这个文件。

几个重要概念

ResponseWriter: 生成Response的接口

Handler: 处理请求和生成返回的接口

ServeMux: 路由,后面会说到ServeMux也是一种Handler

Conn : 网络连接

 

具体分析

(具体的说明直接以注释形式放在代码中)

几个接口:

Handler

1
2
3
type Handler  interface  {
     ServeHTTP(ResponseWriter, *Request)   // 具体的逻辑函数
}

实现了handler接口的对象就意味着往server端添加了处理请求的逻辑。

下面是三个接口(ResponseWriter, Flusher, Hijacker):

ResponseWriter, Flusher, Hijacker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ResponseWriter的作用是被Handler调用来组装返回的Response的
type ResponseWriter  interface  {
     // 这个方法返回Response返回的Header供读写
     Header() Header
 
     // 这个方法写Response的Body
     Write([] byte ) ( int , error)
     
     // 这个方法根据HTTP State Code来写Response的Header
     WriteHeader( int )
}
 
// Flusher的作用是被Handler调用来将写缓存中的数据推给客户端
type Flusher  interface  {
     // 这个方法将写缓存中数据推送给客户端
     Flush()
}
 
// Hijacker的作用是被Handler调用来关闭连接的
type Hijacker  interface  {
     // 这个方法让调用者主动管理连接
     Hijack() (net.Conn, *bufio.ReadWriter, error)
 
}
1
  

response

实现这三个接口的结构是response(这个结构是http包私有的,在文档中并没有显示,需要去看源码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// response包含了所有server端的http返回信息
type response  struct  {
     conn          *conn          // 保存此次HTTP连接的信息
     req           *Request  // 对应请求信息
     chunking       bool      // 是否使用chunk
     wroteHeader    bool      // header是否已经执行过写操作
     wroteContinue  bool      // 100 Continue response was written
     header        Header    // 返回的http的Header
     written       int64     // Body的字节数
     contentLength int64     // Content长度
     status         int       // HTTP状态
     needSniff      bool      // 是否需要使用sniff。(当没有设置Content-Type的时候,开启sniff能根据HTTP body来确定Content-Type)
     
     closeAfterReply  bool      //是否保持长链接。如果客户端发送的请求中connection有keep-alive,这个字段就设置为false。
 
     requestBodyLimitHit  bool  //是否requestBody太大了(当requestBody太大的时候,response是会返回411状态的,并把连接关闭)
 
}

 

在response中是可以看到

1
2
3
4
5
func (w *response) Header() Header
func (w *response) WriteHeader(code  int )
func (w *response) Write(data [] byte ) (n  int , err error)
func (w *response) Flush()
func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error)

这么几个方法。所以说response实现了ResponseWriter,Flusher,Hijacker这三个接口

 

HandlerFunc

handlerFunc是经常使用到的一个type

1
2
3
4
5
6
7
// 这里将HandlerFunc定义为一个函数类型,因此以后当调用a = HandlerFunc(f)之后, 调用a的ServeHttp实际上就是调用f的对应方法
type HandlerFunc func(ResponseWriter, *Request)
 
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
     f(w, r)
}

 

这里需要多回味一下了,这个HandlerFunc定义和ServeHTTP合起来是说明了什么?说明HandlerFunc的所有实例是实现了ServeHttp方法的。另,实现了ServeHttp方法就是什么?实现了接口Handler!

 

所以你以后会看到很多这样的句子:

1
2
3
4
5
func AdminHandler(w ResponseWriter, r *Request) {
     ...
}
handler := HandlerFunc(AdminHandler)
handler.ServeHttp(w,r)

 

请不要讶异,你明明没有写ServeHttp,怎么能调用呢? 实际上调用ServeHttp就是调用AdminHandler。

好吧,理解这个也花了我较长时间,附带上一个play.google写的一个小例子

http://play.golang.org/p/nSt_wcjc2u

有兴趣继续研究的同学可以继续试验下去

 

如果你理解了HandlerFunc,你对下面两个句子一定不会讶异了

1
2
3
func NotFound(w ResponseWriter, r *Request) { Error(w,  "404 page not found" , StatusNotFound) }
 
func NotFoundHandler() Handler {  return  HandlerFunc(NotFound) }

 

下面接着看Server.go

ServerMux结构

它就是http包中的路由规则器。你可以在ServerMux中注册你的路由规则,当有请求到来的时候,根据这些路由规则来判断将请求分发到哪个处理器(Handler)。

它的结构如下:

1
2
3
4
type ServeMux  struct  {
     mu sync.RWMutex    //锁,由于请求设计到并发处理,因此这里需要一个锁机制
     m  map[ string ]muxEntry   // 路由规则,一个string对应一个mux实体,这里的string就是我注册的路由表达式
}
1
  

下面看一下muxEntry

1
2
3
4
type muxEntry  struct  {
     explicit  bool    // 是否精确匹配
     h        Handler  // 这个路由表达式对应哪个handler
}
1
  

看到这两个结构就应该对请求是如何路由的有思路了:

当一个请求request进来的时候,server会依次根据ServeMux.m中的string(路由表达式)来一个一个匹配,如果找到了可以匹配的muxEntry,就取出muxEntry.h,这是个handler,调用handler中的ServeHTTP(ResponseWriter, *Request)来组装Response,并返回。

 

ServeMux定义的方法有:

1
2
3
4
5
6
func (mux *ServeMux) match(path  string ) Handler    //根据path获取Handler
func (mux *ServeMux) handler(r *Request) Handler   //根据Request获取Handler,内部实现调用match
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)  //!!这个说明,ServeHttp也实现了Handler接口,它实际上也是一个Handler!内部实现调用handler
func (mux *ServeMux) Handle(pattern  string , handler Handler)  //注册handler方法
 
func (mux *ServeMux) HandleFunc(pattern  string , handler func(ResponseWriter, *Request))   //注册handler方法(直接使用func注册)

 

在godoc文档中经常见到的DefaultServeMux是http默认使用的ServeMux

var DefaultServeMux = NewServeMux()

如果我们没有自定义ServeMux,系统默认使用这个ServeMux。

 

换句话说,http包外层(非ServeMux)中提供的几个方法:

1
2
3
4
func Handle(pattern  string , handler Handler) { DefaultServeMux.Handle(pattern, handler) }
func HandleFunc(pattern  string , handler func(ResponseWriter, *Request)) {
     DefaultServeMux.HandleFunc(pattern, handler)
}

实际上就是调用ServeMux结构内部对应的方法。

 

Server

下面还剩下一个Server结构

1
2
3
4
5
6
7
8
type Server  struct  {
     Addr            string         // 监听的地址和端口
     Handler        Handler        // 所有请求需要调用的Handler(实际上这里说是ServeMux更确切)如果为空则设置为DefaultServeMux
     ReadTimeout    time.Duration  // 读的最大Timeout时间
     WriteTimeout   time.Duration  // 写的最大Timeout时间
     MaxHeaderBytes  int            // 请求头的最大长度
     TLSConfig      *tls.Config    // 配置TLS
}

Server提供的方法有:

1
2
3
func (srv *Server) Serve(l net.Listener) error    //对某个端口进行监听,里面就是调用for进行accept的处理了
func (srv *Server) ListenAndServe() error   //开启http server服务,内部调用Serve
func (srv *Server) ListenAndServeTLS(certFile, keyFile  string ) error  //开启https server服务,内部调用Serve

 

当然Http包也直接提供了方法供外部使用,实际上内部就是实例化一个Server,然后调用ListenAndServe方法

1
2
func ListenAndServe(addr  string , handler Handler) error    //开启Http服务
func ListenAndServeTLS(addr  string , certFile  string , keyFile  string , handler Handler) error  //开启HTTPs服务

 

具体例子分析

下面根据上面的分析,我们对一个例子我们进行阅读。这个例子搭建了一个最简易的Server服务。当调用http://XXXX:12345/hello的时候页面会返回“hello world”

1
2
3
4
5
6
7
8
9
10
11
12
func HelloServer(w http.ResponseWriter, req *http.Request) {
     io.WriteString(w,  "hello, world!\n" )
}
 
func main() {
     http.HandleFunc( "/hello" , HelloServer)
     err := http.ListenAndServe( ":12345" , nil)
     if  err != nil {
         log.Fatal( "ListenAndServe: " , err)
     }
 
}

 

首先调用Http.HandleFunc

按顺序做了几件事:

1 调用了DefaultServerMux的HandleFunc

2 调用了DefaultServerMux的Handle

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

 

其次调用http.ListenAndServe(":12345", 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 判断header是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux

8 调用handler的ServeHttp

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

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

       mux.handler(r).ServeHTTP(w, r)

11 选择handler:

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

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

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

后记

对于net.http包中server的理解是非常重要的。理清serverMux, responseWriter, Handler, HandlerFunc等常用结构和函数是使用go web的重要一步。个人感觉由于go中文档较少,像这样有点复杂的包,看godoc的效果就远不如直接看代码来的快和清晰了。实际上在理解了http包后,才会对godoc中出现的句子有所理解。后续还会写一些文章关于使用net.http构建web server的。请期待之。





本文转自轩脉刃博客园博客,原文链接:http://www.cnblogs.com/yjf512/archive/2012/08/22/2650873.html,如需转载请自行联系原作者


相关文章
|
29天前
|
监控 开发者 Perl
perl use HTTP::Server::Simple 轻量级 http server
使用 **HTTP::Server::Simple** 模块,Perl 开发者可以快速创建和配置一个轻量级的HTTP服务器。通过继承和扩展 `handle_request` 方法,可以实现复杂的请求处理逻辑。结合日志记录功能,可以更好地监控服务器运行情况。无论是用于开发测试还是简单的生产环境应用,这种轻量级解决方案都能提供很好的支持。
43 2
|
1月前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
4月前
|
存储 物联网 测试技术
Golang中的HTTP请求凝聚器
Golang中的HTTP请求凝聚器
|
4月前
|
缓存 运维 Serverless
函数计算产品使用问题之怎么优化HTTP Server的启动速度
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
4月前
|
Python
【Azure 应用服务】Azure Function HTTP Trigger 遇见奇妙的500 Internal Server Error: Failed to forward request to http://169.254.130.x
【Azure 应用服务】Azure Function HTTP Trigger 遇见奇妙的500 Internal Server Error: Failed to forward request to http://169.254.130.x
|
4月前
|
网络协议 应用服务中间件 Go
[golang]使用mTLS双向加密认证http通信
[golang]使用mTLS双向加密认证http通信
100 0
|
5月前
|
Go 开发者
golang的http客户端封装
golang的http客户端封装
101 0
|
6月前
|
网络协议 PHP
Swoole 源码分析之 Http Server 模块
想要了解到 `Http Server` 的全貌,其实只要把那张整体的实现图看懂就足以了。但是,如果想要有足够的深度,那么就还需要深入 `Swoole` 的源代码中,就着源码自行分析一遍。同时,也希望这一次的分析,能够给大家带来对 `Swoole` 更多的一些了解。并不要求要深刻的掌握,因为,很多的事情都不可能一蹴而就。从自己的实力出发,勿忘初心。
86 0
Swoole 源码分析之 Http Server 模块
|
6月前
|
小程序
Failed to load local image resource Xx the server responded with a status of of 500 (HTTP/1.1 500)
Failed to load local image resource Xx the server responded with a status of of 500 (HTTP/1.1 500)
156 4
|
7月前
|
存储 网络协议 Go
7天玩转 Golang 标准库之 http/net
7天玩转 Golang 标准库之 http/net
65 2
下一篇
DataWorks