go語言中http server:
http server,顧名思義,支援http協定的伺服器,http是一個簡單的請求-響應協定,通常運作在tcp之上。通過用戶端發送請求給伺服器得到對應的響應。
http服務簡單實作
package main
import (
"fmt"
"net/http"
)
//③處理請求,傳回結果
func hello(w http.responsewriter, r *http.request) {
fmt.fprintln(w, "hello world")
}
func main() {
//①路由注冊
http.handlefunc("/", hello)
//②服務監聽
http.listenandserve(":8080", nil)
}
你以為這樣就結束了嗎,不才剛剛開始。
源碼分析
①路由注冊
func handlefunc(pattern string, handler func(responsewriter, *request)) {
defaultservemux.handlefunc(pattern, handler)
defaultservemux是什麼?
defaultservemux是servemux的一個執行個體。
servemux又是什麼?
// defaultservemux is the default servemux used by serve.
var defaultservemux = &defaultservemux
var defaultservemux servemux
type servemux struct {
mu sync.rwmutex
m map[string]muxentry
hosts bool
type muxentry struct {
explicit bool
h handler
pattern string
servemux主要通過map[string]muxentry,來存儲了具體的url模式和handler(此handler是實作handler接口的類型)。通過實作handler的servehttp方法,來比對路由(這一點下面源碼會講到)
很多地方都涉及到了handler,那麼handler是什麼?
type handler interface {
servehttp(responsewriter, *request)
此接口可以算是http server一個樞紐
func (mux *servemux) handlefunc(pattern string, handler func(responsewriter, *request)) {
mux.handle(pattern, handlerfunc(handler))
type handlerfunc func(responsewriter, *request)
func (f handlerfunc) servehttp(w responsewriter, r *request) {
f(w, r)
從代碼中可以看出handlerfunc是一個函數類型,并實作了handler接口。當通過調用handlefunc(),把hello強轉為handlerfunc類型時,就意味着 hello函數也實作servehttp方法。
servemux的handle方法:
func (mux *servemux) handle(pattern string, handler handler) {
mux.mu.lock()
defer mux.mu.unlock()
if pattern == "" {
panic("http: invalid pattern " + pattern)
}
if handler == nil {
panic("http: nil handler")
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxentry)
//把handler和pattern模式綁定到
//map[string]muxentry的map上
mux.m[pattern] = muxentry{explicit: true, h: handler, pattern: pattern}
if pattern[0] != '/' {
mux.hosts = true
//這裡是綁定靜态目錄,不作為本片重點。
n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
path := pattern
if pattern[0] != '/' {
path = pattern[strings.index(pattern, "/"):]
}
url := &url.url{path: path}
mux.m[pattern[0:n-1]] = muxentry{h: redirecthandler(url.string(), statusmovedpermanently), pattern: pattern}
上面的流程就完成了路由注冊。
②服務監聽
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
func listenandserve(addr string, handler handler) error {
server := &server{addr: addr, handler: handler}
return server.listenandserve()
//初始化監聽位址addr,同時調用listen方法設定監聽。
//最後将監聽的tcp對象傳入serve方法:
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)})
serve(l net.listener)為每個請求開啟goroutine的設計,保證了go的高并發。
func (srv *server) serve(l net.listener) error {
defer l.close()
if fn := testhookserverserve; fn != nil {
fn(srv, l)
var tempdelay time.duration // how long to sleep on accept failure
if err := srv.setuphttp2_serve(); err != nil {
return err
srv.tracklistener(l, true)
defer srv.tracklistener(l, false)
basectx := context.background() // base is always background, per issue 16220
ctx := context.withvalue(basectx, servercontextkey, srv)
ctx = context.withvalue(ctx, localaddrcontextkey, l.addr())
//開啟循環進行監聽
for {
//通過listener的accept方法用來擷取連接配接資料
rw, e := l.accept()
if e != nil {
select {
case <-srv.getdonechan():
return errserverclosed
default:
}
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
//通過獲得的連接配接資料,建立newconn連接配接對象
c := srv.newconn(rw)
c.setstate(c.rwc, statenew) // before serve can return
//開啟goroutine發送連接配接請求
go c.serve(ctx)
serve()為核心,讀取對應的連接配接資料進行配置設定
func (c *conn) serve(ctx context.context) {
c.remoteaddr = c.rwc.remoteaddr().string()
//連接配接關閉相關的處理
defer func() {
if err := recover(); err != nil && err != erraborthandler {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.stack(buf, false)]
c.server.logf("http: panic serving %v: %v\n%s", c.remoteaddr, err, buf)
if !c.hijacked() {
c.close()
c.setstate(c.rwc, stateclosed)
}()
.....
ctx, cancelctx := context.withcancel(ctx)
c.cancelctx = cancelctx
defer cancelctx()
c.r = &connreader{conn: c}
c.bufr = newbufioreader(c.r)
c.bufw = newbufiowritersize(checkconnerrorwriter{c}, 4<<10)
//讀取用戶端的請求
w, err := c.readrequest(ctx)
if c.r.remain != c.server.initialreadlimitsize() {
// if we read any bytes off the wire, we're active.
c.setstate(c.rwc, stateactive)
}
.................
//處理網絡資料的狀态
// expect 100 continue support
req := w.req
if req.expectscontinue() {
if req.protoatleast(1, 1) && req.contentlength != 0 {
// wrap the body reader with one that replies on the connection
req.body = &expectcontinuereader{readcloser: req.body, resp: w}
} else if req.header.get("expect") != "" {
w.sendexpectationfailed()
return
}
c.curreq.store(w)
if requestbodyremains(req.body) {
registeronhiteof(req.body, w.conn.r.startbackgroundread)
} else {
if w.conn.bufr.buffered() > 0 {
w.conn.r.closenotifyfrompipelinedrequest()
w.conn.r.startbackgroundread()
//調用serverhandler{c.server}.servehttp(w, w.req)
//方法處理請求
serverhandler{c.server}.servehttp(w, w.req)
w.cancelctx()
if c.hijacked() {
w.finishrequest()
if !w.shouldreuseconnection() {
if w.requestbodylimithit || w.closedrequestbodyearly() {
c.closewriteandwait()
c.setstate(c.rwc, stateidle)
c.curreq.store((*response)(nil))
if !w.conn.server.dokeepalives() {
if d := c.server.idletimeout(); d != 0 {
c.rwc.setreaddeadline(time.now().add(d))
if _, err := c.bufr.peek(4); err != nil {
return
c.rwc.setreaddeadline(time.time{})
③處理請求,傳回結果
serverhandler 主要初始化路由多路複用器。如果server對象沒有指定handler,則使用預設的defaultservemux作為路由多路複用器。并調用初始化handler的servehttp方法。
type serverhandler struct {
srv *server
func (sh serverhandler) servehttp(rw responsewriter, req *request) {
handler := sh.srv.handler
handler = defaultservemux
if req.requesturi == "*" && req.method == "options" {
handler = globaloptionshandler{}
handler.servehttp(rw, req)
這裡就是之前提到的比對路由的具體代碼
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
//比對注冊到路由上的handler函數
h, _ := mux.handler(r)
//調用handler函數的servehttp方法
//即hello函數,然後把資料寫到http.responsewriter
//對象中傳回給用戶端。
h.servehttp(w, r)
}
func (mux *servemux) handler(r *request) (h handler, pattern string) {
if r.method != "connect" {
if p := cleanpath(r.url.path); p != r.url.path {
_, pattern = mux.handler(r.host, p)
url := *r.url
url.path = p
return redirecthandler(url.string(), statusmovedpermanently), pattern
return mux.handler(r.host, r.url.path)
func (mux *servemux) handler(host, path string) (h handler, pattern string) {
mux.mu.rlock()
defer mux.mu.runlock()
// host-specific pattern takes precedence over generic ones
if mux.hosts {
//如 127.0.0.1/hello
h, pattern = mux.match(host + path)
if h == nil {
// 如 /hello
h, pattern = mux.match(path)
h, pattern = notfoundhandler(), ""
return
func (mux *servemux) match(path string) (h handler, pattern string) {
var n = 0
for k, v := range mux.m {
if !pathmatch(k, path) {
continue
//通過疊代m尋找出注冊路由的patten模式
//與實際url比對的handler函數并傳回。
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
func pathmatch(pattern, path string) bool {
if len(pattern) == 0 {
// should not happen
return false
//如果注冊模式與請求uri一樣傳回true,否則false
if pattern[n-1] != '/' {
return pattern == path
//靜态檔案比對
return len(path) >= n && path[0:n] == pattern
将資料寫給用戶端
//主要代碼,通過層層封裝才走到這一步
func (w checkconnerrorwriter) write(p []byte) (n int, err error) {
n, err = w.c.rwc.write(p)
if err != nil && w.c.werr == nil {
w.c.werr = err
w.c.cancelctx()
serverhandler{c.server}.servehttp(w, w.req)當請求結束後,就開始執行連接配接斷開的相關邏輯。
總結
go語言通過一個servemux實作了的路由多路複用器來管理路由。同時提供一個handler接口提供servehttp方法,實作handler接口的函數,可以處理實際request并傳回response。
servemux和handler函數的連接配接橋梁就是handler接口。servemux的servehttp方法實作了尋找注冊路由的handler的函數,并調用該handler的servehttp方法。
是以說handler接口是一個重要樞紐。
簡單梳理下整個請求響應過程,如下圖
作者:佚名
來源:51cto