欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang中的http.Server源碼深入分析

 更新時間:2023年05月16日 10:34:33   作者:raoxiaoya  
這篇文章主要介紹了Golang中的http.Server源碼,實現(xiàn)一個http.Server非常容易,只需要短短幾行代碼,同時有了協(xié)程的加持,Go實現(xiàn)的http.Server能夠取得非常優(yōu)秀的性能,下面我們來分析看看http.Server的源碼
func (srv *Server) Serve(l net.Listener) error {
	......
	for {
		rw, err := l.Accept()
		if err != nil {
			select {
			case <-srv.getDoneChan():
				return ErrServerClosed
			default:
			}
			if ne, ok := err.(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", err, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return err
		}
		connCtx := ctx
		if cc := srv.ConnContext; cc != nil {
			connCtx = cc(connCtx, rw)
			if connCtx == nil {
				panic("ConnContext returned nil")
			}
		}
		tempDelay = 0
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew, runHooks) // before Serve can return
		go c.serve(connCtx)
	}
}
func (c *conn) serve(ctx context.Context) {
	......
	// HTTP/1.x from here on.
	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)
	for {
		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, runHooks)
		}
		if err != nil {
			const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"
			switch {
			case err == errTooLarge:
				// Their HTTP client may or may not be
				// able to read this if we're
				// responding to them and hanging up
				// while they're still writing their
				// request. Undefined behavior.
				const publicErr = "431 Request Header Fields Too Large"
				fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
				c.closeWriteAndWait()
				return
			case isUnsupportedTEError(err):
				// Respond as per RFC 7230 Section 3.3.1 which says,
				//      A server that receives a request message with a
				//      transfer coding it does not understand SHOULD
				//      respond with 501 (Unimplemented).
				code := StatusNotImplemented
				// We purposefully aren't echoing back the transfer-encoding's value,
				// so as to mitigate the risk of cross side scripting by an attacker.
				fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)
				return
			case isCommonNetReadError(err):
				return // don't reply
			default:
				if v, ok := err.(statusError); ok {
					fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s: %s%s%d %s: %s", v.code, StatusText(v.code), v.text, errorHeaders, v.code, StatusText(v.code), v.text)
					return
				}
				publicErr := "400 Bad Request"
				fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
				return
			}
		}
		// 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}
				w.canWriteContinue.setTrue()
			}
		} 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 {
			w.conn.r.startBackgroundRead()
		}
		// HTTP cannot have multiple simultaneous active requests.[*]
		// Until the server replies to this request, it can't read another,
		// so we might as well run the handler in this goroutine.
		// [*] Not strictly true: HTTP pipelining. We could let them all process
		// in parallel even if their responses need to be serialized.
		// But we're not going to implement HTTP pipelining because it
		// was never deployed in the wild and the answer is HTTP/2.
		inFlightResponse = w
		serverHandler{c.server}.ServeHTTP(w, w.req)
		inFlightResponse = nil
		w.cancelCtx()
		if c.hijacked() {
			return
		}
		w.finishRequest()
		if !w.shouldReuseConnection() {
			if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
				c.closeWriteAndWait()
			}
			return
		}
		c.setState(c.rwc, StateIdle, runHooks)
		c.curReq.Store((*response)(nil))
		if !w.conn.server.doKeepAlives() {
			// We're in shutdown mode. We might've replied
			// to the user without "Connection: close" and
			// they might think they can send another
			// request, but such is life with HTTP/1.1.
			return
		}
		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{})
	}
}

1、c.readRequest(ctx)

放在 for 循環(huán)里面,是為了 HTTP Keep-Alive,可以復(fù)用TCP連接,并且是串行的,上一個請求處理完才會去讀取下一個請求的數(shù)據(jù),如果連接被客戶端斷開,那么c.readRequest(ctx)會因為讀取報錯而退出。

通過繼續(xù)追蹤源碼,發(fā)現(xiàn)這里只是讀取了 Header,并做一些判斷,因此會有readHeaderDeadline這樣的配置,然后設(shè)置Body的類型,Header和Body之間有一個空行,這個作為Header讀完的標(biāo)志,通過 Content-Length 可以知道是否有Body內(nèi)容,以及有多少內(nèi)容。

switch {
	case t.Chunked:
		if noResponseBodyExpected(t.RequestMethod) || !bodyAllowedForStatus(t.StatusCode) {
			t.Body = NoBody
		} else {
			t.Body = &body{src: internal.NewChunkedReader(r), hdr: msg, r: r, closing: t.Close}
		}
	case realLength == 0:
		t.Body = NoBody
	case realLength > 0:
		t.Body = &body{src: io.LimitReader(r, realLength), closing: t.Close}
	default:
		// realLength < 0, i.e. "Content-Length" not mentioned in header
		if t.Close {
			// Close semantics (i.e. HTTP/1.0)
			t.Body = &body{src: r, closing: t.Close}
		} else {
			// Persistent connection (i.e. HTTP/1.1)
			t.Body = NoBody
		}
	}
func (l *LimitedReader) Read(p []byte) (n int, err error) {
	if l.N <= 0 {
		return 0, EOF
	}
	if int64(len(p)) > l.N {
		p = p[0:l.N]
	}
	n, err = l.R.Read(p)
	l.N -= int64(n)
	return
}

io.LimitReader在讀取到指定的長度后就會返回EOF錯誤,表示讀取完畢。

2、w.conn.r.startBackgroundRead

if requestBodyRemains(req.Body) {
    registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
} else {
    w.conn.r.startBackgroundRead()
}

當(dāng)Body讀取完之后才會開啟startBackgroundRead。

func (cr *connReader) backgroundRead() {
	n, err := cr.conn.rwc.Read(cr.byteBuf[:])
	cr.lock()
	if n == 1 {
		cr.hasByte = true
		......
	}
	if ne, ok := err.(net.Error); ok && cr.aborted && ne.Timeout() {
		// Ignore this error. It's the expected error from
		// another goroutine calling abortPendingRead.
	} else if err != nil {
		cr.handleReadError(err)
	}
	cr.aborted = false
	cr.inRead = false
	cr.unlock()
	cr.cond.Broadcast()
}
func (cr *connReader) handleReadError(_ error) {
	cr.conn.cancelCtx()
	cr.closeNotify()
}
// may be called from multiple goroutines.
func (cr *connReader) closeNotify() {
	res, _ := cr.conn.curReq.Load().(*response)
	if res != nil && atomic.CompareAndSwapInt32(&res.didCloseNotify, 0, 1) {
		res.closeNotifyCh <- true
	}
}

其實startBackgroundRead就是為了監(jiān)控客戶端是否關(guān)閉了連接,它不能影響業(yè)務(wù)數(shù)據(jù)讀取,因此需要等Body被讀取完之后才開啟,它象征性的讀取一個字節(jié),如果客戶端關(guān)閉了,對應(yīng)的 fd 是可讀的,它會像一個通道寫入數(shù)據(jù),此協(xié)程的生命周期是當(dāng)前請求,而不是當(dāng)前連接,它的作用是為了中斷當(dāng)前請求的 Handler 處理階段,它認為客戶端已經(jīng)放棄了這個請求,服務(wù)端也沒必要做過多的業(yè)務(wù)處理,但是這個在實際業(yè)務(wù)中很難實現(xiàn),或者說是多余的,在我們看來,只要請求到達了,服務(wù)端就有義務(wù)正確的給予處理,不應(yīng)該將其中斷。

當(dāng)請求處理完畢,就會調(diào)用abortPendingRead,使得startBackgroundRead協(xié)程退出。為什么startBackgroundRead協(xié)程的生命周期不是跟著連接呢,因為 Keep-Alive 的連接會持續(xù)一段時間,即便沒有請求到來,這會導(dǎo)致startBackgroundRead協(xié)程一直在運行。

那么服務(wù)端何時去關(guān)閉此連接呢,畢竟客戶端是不可信的,它是通過設(shè)置SetReadDeadlineReadHeaderTimeout來修改定時器時間,當(dāng)然如果沒有設(shè)置ReadHeaderTimeout,那么會使用ReadTimeout代替,超時還沒發(fā)來請求就可以認為客戶端已經(jīng)沒有重用此連接了,for 循環(huán)退出,defer 中關(guān)閉此連接。

實際上客戶端只會在一個短的時間內(nèi)要發(fā)送多個請求的情況下才會重用連接,比如在頁面初始化的時候,瀏覽器會視情況重用連接。

ReadDeadline是一個總的時間,一個截止時間,是讀取Header和讀取Body的總時間。

3、serverHandler{c.server}.ServeHTTP(w, w.req)

后面就開始調(diào)用Handler,如果需要用到Body的信息,則需要接著讀取Body內(nèi)容,可見Header和Body是分開來讀的。第二次讀取是不會阻塞的因為fd里面有內(nèi)容,當(dāng)然如果有人惡意攻擊,只發(fā)請求頭不填Body,那么也會阻塞。

到此這篇關(guān)于Golang中的http.Server源碼深入分析的文章就介紹到這了,更多相關(guān)Golang http.Server內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • golang中為什么Response.Body需要被關(guān)閉詳解

    golang中為什么Response.Body需要被關(guān)閉詳解

    這篇文章主要給大家介紹了關(guān)于golang中為什么Response.Body需要被關(guān)閉的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08
  • Golang 使用map需要注意的幾個點

    Golang 使用map需要注意的幾個點

    這篇文章主要介紹了Golang 使用map需要注意的幾個點,幫助大家更好的理解和學(xué)習(xí)golang,感興趣的朋友可以了解下
    2020-09-09
  • Golang?HTTP編程的源碼解析詳解

    Golang?HTTP編程的源碼解析詳解

    這篇文章主要為大家詳細介紹了Golang中的HTTP編程以及源碼解析,文中的示例代碼講解詳細,具有一定的借鑒價值,感興趣的可以了解一下
    2023-02-02
  • Go Slice擴容的這些坑你踩過哪些

    Go Slice擴容的這些坑你踩過哪些

    這篇文章主要為大家詳細介紹了Golang中對切片Slice的append操作時會遇到的踩坑經(jīng)驗分享,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下
    2023-03-03
  • Go語言中常見的坑以及高性能編程技巧分享

    Go語言中常見的坑以及高性能編程技巧分享

    代碼的穩(wěn)健性、高性能、可讀性是我們每一位coder必須去追求的目標(biāo),本文結(jié)合Go語言的特性做了相關(guān)總結(jié),感興趣的小伙伴可以了解一下
    2023-06-06
  • go語言接口用法實例分析

    go語言接口用法實例分析

    這篇文章主要介紹了go語言接口用法,實例分析了Go語言接口的定義及使用技巧,需要的朋友可以參考下
    2015-03-03
  • 高效封禁:利用Go封裝功能,提升封禁操作效率

    高效封禁:利用Go封裝功能,提升封禁操作效率

    在網(wǎng)絡(luò)安全領(lǐng)域,封禁操作是一項重要的任務(wù),用于阻止惡意行為和保護系統(tǒng)安全,而利用Go語言封裝功能可以提升封禁操作的效率,Go語言具有高效的并發(fā)性能和簡潔的語法,使得開發(fā)者可以快速構(gòu)建高性能的封禁系統(tǒng),
    2023-10-10
  • Go語言實現(xiàn)服務(wù)端消息接收和發(fā)送

    Go語言實現(xiàn)服務(wù)端消息接收和發(fā)送

    這篇文章主要為大家詳細介紹了Go語言實現(xiàn)服務(wù)端消息接收和發(fā)送功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • 細細探究Go 泛型generic設(shè)計

    細細探究Go 泛型generic設(shè)計

    這篇文章主要帶大家細細探究了Go 泛型generic設(shè)計及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • Golang空接口與類型斷言的實現(xiàn)

    Golang空接口與類型斷言的實現(xiàn)

    本文主要介紹了Golang空接口與類型斷言的實現(xiàn),文中根據(jù)實例編碼詳細介紹的十分詳盡,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03

最新評論