服務計算——開發web服務程序

源代碼閱讀

閱讀源代碼是學習Go語言的必備技能。我們知道,用Go語言,只需要幾行代碼就可以實現一個功能強大的http服務器。背後依賴的是強大的net/http庫,下面我們來了解以下net/http庫源碼的實現原理吧。

net/http庫源碼分析

HTTP

我們知道,HTTP網絡中有客戶端(clinet)和服務端(server),分別用於發送請求(request)和作出迴應(response)。在這一過程中,路由器(router)發揮重要的作用,路由器中由Multiplexer器實現路由選擇,它的目標是找到合適的處理器(handler),並由處理器對消息進行處理、構建response。

  • 客戶端發送一條請求的流程如下:

Clinet -> Requests -> [Multiplexer(router) -> handler -> Response -> Clinet

可見,理解http服務的關鍵在於理解Multiplexer和handler。
Go語言中的Multiplexer基於serverMux結構,同時實現了Handler接口。

Handler

Golang中定義如下的接口去聲明具有簽名函數的Handler處理器:

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

實現了ServerHTTP方法的結構稱之爲handler對象

ServeMux

ServeMux的源碼如下:

type ServeMux struct {
    mu    sync.RWMutex //併發機制的鎖
    m     map[string]muxEntry 
    hosts bool  //是否帶有host信息
}
type muxEntry struct {
    explicit bool  //表示是否精確匹配
    h        Handler  //該路由表達式對應的handler
    pattern  string  //匹配字符串
}

m是一個map,其中key是url模式,value是muxEntry結構(存儲了具體的url模式和handler)

Server

Server的結構如下:

type Server struct {
    Addr         string        
    Handler      Handler       
    ReadTimeout  time.Duration 
    WriteTimeout time.Duration 
    TLSConfig    *tls.Config   

    MaxHeaderBytes int

    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)

    ConnState func(net.Conn, ConnState)
    ErrorLog *log.Logger
    disableKeepAlives int32     nextProtoOnce     sync.Once 
    nextProtoErr      error     
}

Server結構存儲了服務器處理請求常見的字段,其中Handler字段保留了Handler接口。

創建HTTP服務

創建一個http服務,大致需要兩個步驟:一是註冊路由(即提供url模式和handler函數的映射);二是實例化一個server對象,並開啓對客戶端的監聽。

註冊路由

gohttp的註冊路由代碼:

http.HandleFunc("/", indexHandler)

http.HandleFunc選取DefautServeMux作爲multiplexer:

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

也可以通過NewServeMux方法創建一個ServeMux實例:

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

HandlerFunc是一個函數類型,同時實現了Handler接口的ServeHTTP方法。使用HandlerFunc類型包裝一下路由定義的indexHandler函數,其目的就是爲了讓這個函數也實現ServeHTTP方法:

type HandlerFunc func(ResponseWriter, *Request)

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

開啓監聽

註冊好路由後,啓動web服務還需要開啓服務器監聽:

http.ListenAndServe("127.0.0.1:8000", nil)
func ListenAndServe(addr string, handler Handler) error { 
server := &Server{Addr: addr, Handler: handler} //創建Server對象
return server.ListenAndServe() //調用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)})
}

處理請求

監聽開啓後,go程序就可以處理客戶端的請求。主要的處理邏輯在ServeHTTP{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)
}

路由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)
}

開發web服務程序

框架選擇

本次選擇的web開發框架是Martini,使用Go的net/http接口開發

代碼

  • main.go 基本沿用潘老師博客中的代碼,依次完成綁定端口、解析端口、啓動server
package main
import (
    "os"
    "cloudgo/service"
    flag "github.com/spf13/pflag"
)
const (
    //使用端口9000
    PORT string = "9000" 
)
func main() {
    port := os.Getenv("PORT") 
    if len(port) == 0 {
        port = PORT
    }
    //端口解析
    pPort := flag.StringP("port", "p", PORT, "PORT for httpd listening")
    flag.Parse()
    if len(*pPort) != 0 {
        port = *pPort
    }
    //啓動server
    service.NewServer(port)
}
  • server.go使用martini框架中的格式,具體定義了啓動server後的操作:
package service
import (
    "github.com/go-martini/martini" 
)
func NewServer(port string) {   
    m := martini.Classic()

    m.Get("/", func(params martini.Params) string {
        return "hello world"
    })

    m.RunOnAddr(":"+port)   
}

服務器測試

  • go run 運行main.go
    在這裏插入圖片描述
  • 在瀏覽器中訪問 localhost:9000
    在這裏插入圖片描述

curl測試

curl -v http://localhost: 9000測試:

在這裏插入圖片描述

ab抗壓測試

./ab -n 1000 -c 100 http://localhost: 9000/測試:
在這裏插入圖片描述
在這裏插入圖片描述
程序源代碼
參考博客:
Golang構建HTTP服務(一)— net/http庫源碼筆記
【服務計算】開發web服務程序

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章