Go 因为其性能被很多人青睐,当然和所有语言一样难免也会有让人深恶痛绝的地方。最近在学习这块的东西,鉴于之前仔细了解过Python 的TCPServer以及HTTPServer 的实现,本文打算对Go的http 模块做一些简单梳理,以便了解Go语言的一些特性,主要是想从Go的源码中吸收一些代码组织的方法和思路。
我们知道HTTP 协议是建立在TCP 之上的,Python 很好的利用了传统的面向对像的编程思想。HTTPServer 是对SocketServer里面TCPServer 的继承。在Python 的源代码里最最基础的HTTPServer 叫 BaseHTTPServer
。
先来看如何启动一个Server简单的HTTPServer
import BaseHTTPServer def run(server_class=BaseHTTPServer.HTTPServer, handler_class=BaseHTTPServer.BaseHTTPRequestHandler): server_address = ('', 8000) httpd = server_class(server_address, handler_class) httpd.serve_forever() run()
下面对Python 的HTTPServer 的启动到请求处理过程中发生的事情做简单说明。
server_forever()
方法,源码,就会发现这其实就是一个死循环,不断监听着端口等待请求的到来。 def serve_forever(self, poll_interval=0.5): self.__is_shut_down.clear() try: while not self.__shutdown_request: r, w, e = _eintr_retry(select.select, [self], [], [], poll_interval) if self in r: self._handle_request_noblock() finally: self.__shutdown_request = False self.__is_shut_down.set()
当收到一个请求的时候,Python 会利用系统的select 或者thread的方式来实现非阻塞服务。具体是哪种方式就看Handler 的实现方式。为什么又跳出来一个Handler .我们知道所有请求最终处理的逻辑都在Handler 里。那是用什么方式来组织Server 和Handler 的?这就要说到混入(MixIn)。这种代码的组织的好处在于,把变化的东西(对请求的处理逻辑)独立出来,相同的东西都在Server 内部。简单说下混入,我们看到上面启动一个HTTPServer 的时候传了两个参数
httpd = server_class(server_address, handler_class)
第一个是server 监听地址,第二个是handler ,下面的函数是最底层的Server 定义,可以看到最最基础的Server 的初始化方法:
def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False
在Server 里面会调用RequestHandlerClass 的 process_request
方法,当你在自己的Handler 的实现里面传入不同的Handler 自然就会有不同的逻辑。
上面介绍的Python的实现方法,Go的实现逻辑又会怎样,而且Go实现了一个更加强大的功能,相当于做好了一个类似Tornado,Flask 这样的框架。 在看之前有下面几个问题:
先来实现一个简单的RestFul 的Server
package main import ( "net/http" "fmt" "log" ) func sayHelloName(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello astaxie!") } func main() { http.HandleFunc("/", sayHelloName) err := http.ListenAndServe(":9090", nil) if err != nil { log.Fatal("ListenAndServe: ", err) } }
ListenAndServer
函数,这个函数源码如下,看到它 会new 一个Server 然后调用Server 的ListenAndServer方法 func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
ListenAndServer
方法又会发生什么?它会调用自己的 Server
方法(主要代码如下),这个方法有点类似Python 的 server_forever
方法,里面是个死循环,当收到请求就会新建一个conn,然后启动一个goroutine 调用conn的 serve
方法,来处理请求, serve
就收的参数是一个context(什么是context后面解释)。 baseCtx := context.Background() ctx := context.WithValue(baseCtx, ServerContextKey, srv) ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr()) for { rw, e := l.Accept() if e != nil { if ne, ok := e.(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", e, tempDelay) time.Sleep(tempDelay) continue } return e } tempDelay = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve(ctx)
ServerHTTP
方法。 我们先来看serrverHandler 的定义:
type serverHandler struct { srv *Server }
就是有个srv 是Server 类型。其 ServerHTTP
方法的实现如下:
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) }
会看到它或从Server 的Handler 方法来找对应url的handler ,它会判断handler 是否为空,如果为空就使用默认的 DefaultServeMux
,然后调用handler 的ServerHTTP方法 4. 我们自己定义的 sayHelloName
方法去哪里呢?玄机都在 DefaultServeMux
里面了。这就要涉及到路由了,看 DefaultServerMux
的 ServerHttp
方法,
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) h.ServeHTTP(w, r) }
h, _ := mux.Handler(r)
一句会会根据请求路径从Map里找一个handler 来调用其ServerHTTP方法。
sayHelloName
函数,然后调用了下面这句 http.HandleFunc("/", sayHelloName)
玄机就在里面,它会把sayHelloName 函数转化成HandlerFunc,而HandlerFunc实现了ServerHTTP方法
type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
上面这个感觉很奇怪,HandlerFunc 是个函数......自行体会吧 我们定义的路由怎么会被Server 访问到呢?,全是因为这个DefaultSerMux 他是http 模块下的全局变量,定义的路由都会被 他保存。
var DefaultServeMux = &defaultServeMux
我们来看看 build web applicatin with golang 这本书提供的流程图,在我眼里,简直哎.....,你不会懂的。
总的感觉Go对Server, conn, handler, mux 这些对象直接的关系没有Python 那样清洗,他们直接的关系总是感觉有点微妙,对Go的特性比较属性了或许就会很好吧。
那么问题来了,如果我要实现自己的路由机制,该如何做?也就是我要实现一个第三方路由该如何写?又该如何实现自己的HTTP 框架?
参考: build-web-application-with-golang