golang的http源碼與twirp源碼筆記

http

啓動httpHandle

使用http包的ListenAndServe方法,需要提供一個Handler對象

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
func (srv *Server) ListenAndServe() error {
	if srv.shuttingDown() {
		return ErrServerClosed
	}
	addr := srv.Addr
	if addr == "" {
		addr = ":http"
	}
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return srv.Serve(ln)
}

Handler對象中提供處理請求的方法,ServerHttp

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

Server循環accept請求

在ListenAndServe方法中,使用Handler構建一個Server對象,最終調用其Server方法

func (srv *Server) Serve(l net.Listener) error {
	.....
	//包裝listener
	origListener := l
	l = &onceCloseListener{Listener: l}
	defer l.Close()
	//啓動失敗直接結束
	if err := srv.setupHTTP2_Serve(); err != nil {
		return err
	}

	if !srv.trackListener(&l, true) {
		return ErrServerClosed
	}
	defer srv.trackListener(&l, false)

	//綁定上下文等信息
	baseCtx := context.Background()
	if srv.BaseContext != nil {
		baseCtx = srv.BaseContext(origListener)
		if baseCtx == nil {
			panic("BaseContext returned a nil context")
		}
	}
	.....

	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		//循環accept請求
		rw, err := l.Accept()
		if err != nil {
			select {
			//如果服務器已經關閉,直接結束
			case <-srv.getDoneChan():
				return ErrServerClosed
			default:
			}
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
			//如果accept失敗,等待一個時間週期後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) // before Serve can return
		//開啓一個新的goroutine去執行請求
		go c.serve(connCtx)
	}
}

處理connection

func (c *conn) serve(ctx context.Context) {
	//包裝緩存區,上下文信息等
	.....
	for {
		//w是解析request數據得到的一個包裝對象,包含請求的詳細信息
		w, err := c.readRequest(ctx)
		if c.r.remain != c.server.initialReadLimitSize() {
			//如果這個連接已經被讀取過(長連接),更新狀態
			c.setState(c.rwc, StateActive)
		}
		if err != nil {
			//如果報文解析出錯
			const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"

			switch {
			//根據錯誤類型,返回不同http status
			case .....
			default:
				publicErr := "400 Bad Request"
				if v, ok := err.(badRequestError); ok {
					publicErr = publicErr + ": " + string(v)
				}
				fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
				return
			}
		}

		//進一步解析內容
		req := w.req
		if req.expectsContinue() {
			if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
				req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
			}
		} else if req.Header.get("Expect") != "" {
			w.sendExpectationFailed()
			return
		}
		c.curReq.Store(w)
		//如果body部分的內容還沒讀取完畢,等待本次io讀取完畢後
		//修改管道的io讀取爲後臺讀取方式(開啓另一個gorountine區完成數據讀取)
		if requestBodyRemains(req.Body) {
			registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
		} else {
			w.conn.r.startBackgroundRead()
		}
		//調用ServeHTTP方法處理請求
		serverHandler{c.server}.ServeHTTP(w, w.req)
		w.cancelCtx()
		if c.hijacked() {
			return
		}
		//完成請求
		w.finishRequest()
		if !w.shouldReuseConnection() {
			//如果連接不允許重複使用,結束
			if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
				c.closeWriteAndWait()
			}
			return
		}
		c.setState(c.rwc, StateIdle)
		c.curReq.Store((*response)(nil))
		//如果不是長連接,就結束了
		if !w.conn.server.doKeepAlives() {
			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{})
	}
}

twirp

twirp是一個rpc框架,具體可以看看我的其他博客——go使用twirp開發rpc

TwirpServer

我們利用http啓動twirp服務時傳遞的是TwirpServer

type TwirpServer interface {
	http.Handler
	ServiceDescriptor() ([]byte, int)
	ProtocGenTwirpVersion() string
	PathPrefix() string
}

proto文件

這是之前的proto文件,利用他我們編譯生成了twirp的go代碼

syntax = "proto3";
package main;

message A{}
message B {
	string result = 1;
}
service Test{
	rpc hello(A) returns(B);
}

Twirp的ServeHttp實現

可以查看twirp編譯後生成代碼提供的server實現方法ServeHttp

const TestPathPrefix = "/twirp/main.Test/"

func (s *testServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	ctx := req.Context()
	ctx = ctxsetters.WithPackageName(ctx, "main")
	ctx = ctxsetters.WithServiceName(ctx, "Test")
	ctx = ctxsetters.WithResponseWriter(ctx, resp)

	var err error
	ctx, err = callRequestReceived(ctx, s.hooks)
	if err != nil {
		s.writeError(ctx, resp, err)
		return
	}

	if req.Method != "POST" {
		msg := fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method)
		err = badRouteError(msg, req.Method, req.URL.Path)
		s.writeError(ctx, resp, err)
		return
	}

	switch req.URL.Path {
	case "/twirp/main.Test/Hello":
		s.serveHello(ctx, resp, req)
		return
	default:
		msg := fmt.Sprintf("no handler for path %q", req.URL.Path)
		err = badRouteError(msg, req.Method, req.URL.Path)
		s.writeError(ctx, resp, err)
		return
	}
}

可以看到只有post請求被允許,並且請求的url格式爲

/twirp/包名.服務名/方法名

serve請求

最終再進去serveXxx的邏輯中去處理請求,根據是json格式數據還是protobuf格式數據,執行不同方法

func (s *testServer) serveHello(ctx context.Context, resp http.ResponseWriter, req *http.Request) {
	header := req.Header.Get("Content-Type")
	i := strings.Index(header, ";")
	if i == -1 {
		i = len(header)
	}
	switch strings.TrimSpace(strings.ToLower(header[:i])) {
	case "application/json":
		s.serveHelloJSON(ctx, resp, req)
	case "application/protobuf":
		s.serveHelloProtobuf(ctx, resp, req)
	default:
		msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type"))
		twerr := badRouteError(msg, req.Method, req.URL.Path)
		s.writeError(ctx, resp, twerr)
	}
}

比如進入protobuf

func (s *testServer) serveHelloProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) {
	var err error
	ctx = ctxsetters.WithMethodName(ctx, "Hello")
	//如果在hook裏面編寫了路由,此處執行
	ctx, err = callRequestRouted(ctx, s.hooks)
	if err != nil {
		s.writeError(ctx, resp, err)
		return
	}
	//讀取請求內容
	buf, err := ioutil.ReadAll(req.Body)
	if err != nil {
		s.writeError(ctx, resp, wrapInternal(err, "failed to read request body"))
		return
	}
	reqContent := new(A)
	//Unmarshal請求內容
	if err = proto.Unmarshal(buf, reqContent); err != nil {
		s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded"))
		return
	}

	// Call service method
	//執行對應的服務方法
	var respContent *B
	func() {
		defer ensurePanicResponses(ctx, resp, s.hooks)
		//Test對象就是嵌套在server對象中的真正的服務對象
		respContent, err = s.Test.Hello(ctx, reqContent)
	}()

	if err != nil {
		s.writeError(ctx, resp, err)
		return
	}
	if respContent == nil {
		s.writeError(ctx, resp, twirp.InternalError("received a nil *B and nil error while calling Hello. nil responses are not supported"))
		return
	}
	//執行完服務,執行返回前類型的hooks
	ctx = callResponsePrepared(ctx, s.hooks)
	//將要返回的數據Marshal爲字節
	respBytes, err := proto.Marshal(respContent)
	if err != nil {
		s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response"))
		return
	}
	//狀態碼處理等
	ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK)
	resp.Header().Set("Content-Type", "application/protobuf")
	resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes)))
	resp.WriteHeader(http.StatusOK)
	//將數據寫回請求客戶端
	if n, err := resp.Write(respBytes); err != nil {
		msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error())
		twerr := twirp.NewError(twirp.Unknown, msg)
		callError(ctx, s.hooks, twerr)
	}
	//執行返回後類型的hooks
	callResponseSent(ctx, s.hooks)
}

更多文章,請搜索公衆號歪歪梯Club
更多資料,請搜索公衆號編程寶可夢

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