Go Programming Language http

2017-01-01 08:18

Go Programming Language http

Go 因为其性能被很多人青睐,当然和所有语言一样难免也会有让人深恶痛绝的地方。最近在学习这块的东西,鉴于之前仔细了解过Python 的TCPServer以及HTTPServer 的实现,本文打算对Go的http 模块做一些简单梳理,以便了解Go语言的一些特性,主要是想从Go的源码中吸收一些代码组织的方法和思路。

Python HTTPServer 的简单逻辑

我们知道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 的启动到请求处理过程中发生的事情做简单说明。

  1. 启动一个Server首先调用Server 的 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 自然就会有不同的逻辑。

Go http 模块解读

上面介绍的Python的实现方法,Go的实现逻辑又会怎样,而且Go实现了一个更加强大的功能,相当于做好了一个类似Tornado,Flask 这样的框架。 在看之前有下面几个问题:

  1. Go 是如何组织Server 和Handler 的。这个问题主要是由于Go的语言特性,说MixIn这种东西好像不存在。
  2. Go 原生的路由机制如何?Go,在Python 最 基础的实现根本就没有看到路由的设计。
  3. Go的非阻塞实现机制是什么?我们都知道Go的性能主要是有其独特的goroutine ,goroutine 的细节我可以不知道,大致机理能否有简单了解?

先来实现一个简单的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)
        }
    }
  1. 启动一个Server 的时候调用的是http 模块下的 ListenAndServer 函数,这个函数源码如下,看到它 会new 一个Server 然后调用Server 的ListenAndServer方法
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
  1. 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)
  1. 那conn 是什么?其server 方法逻辑如何。server 方法会调用下面这句,意思是用conn中的server 来产生了一个serverHandler 同时调用其 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方法。

  1. 问题又来了,我们只定义了一个 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 Programming Language http0

总的感觉Go对Server, conn, handler, mux 这些对象直接的关系没有Python 那样清洗,他们直接的关系总是感觉有点微妙,对Go的特性比较属性了或许就会很好吧。

那么问题来了,如果我要实现自己的路由机制,该如何做?也就是我要实现一个第三方路由该如何写?又该如何实现自己的HTTP 框架?

参考: build-web-application-with-golang