golang的http源碼跟蹤筆記
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