Beego源码解析之App(HTTP请求处理)

Beego源码解析之App


http server处理请求和返回repsonse的简单流程

在这里插入图片描述

  1. server端:http server启动后,根据配置的IP建立Listener,监听请求;
  2. client端:发送请求;
  3. 监听到客户端的请求,先尝试建立连线connection;
  4. 建立完成之后,读取request的头部内容并解析;
  5. 根据第四步解析出来的request,beego封装的ServerHttp,处理request所请求的路径,方法;
  6. 处理完后,判断连接是否为长连接,如果是要等待,否则直接return;
  7. 等待时间超时,return;
  8. 返回repsonse,在ServerHttp已经把repsonse data写入response结构体,等着返回给client。

Run()
if BConfig.Listen.EnableHTTP {
		go func() {
			app.Server.Addr = addr
			logs.Info("http server Running on http://%s", app.Server.Addr)
			if BConfig.Listen.ListenTCP4 {
				ln, err := net.Listen("tcp4", app.Server.Addr)
				if err != nil {
					logs.Critical("ListenAndServe: ", err)
					time.Sleep(100 * time.Microsecond)
					endRunning <- true
					return
				}
				if err = app.Server.Serve(ln); err != nil {
					logs.Critical("ListenAndServe: ", err)
					time.Sleep(100 * time.Microsecond)
					endRunning <- true
					return
				}
			} else {
				if err := app.Server.ListenAndServe(); err != nil {
					logs.Critical("ListenAndServe: ", err)
					time.Sleep(100 * time.Microsecond)
					endRunning <- true
				}
			}
		}()
	}

上方代码块第六行代码根据network类型和服务IP,返回一个监听,用来监听请求这个IP的request。到初始化监听结构体的函数里可以看到,如果是tcp请求,下方代码块第15行则会回传一个tcp的listener。

func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) {
   addrs, err := DefaultResolver.resolveAddrList(ctx, "listen", network, address, nil)
   if err != nil {
      return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
   }
   sl := &sysListener{
      ListenConfig: *lc,
      network:      network,
      address:      address,
   }
   var l Listener
   la := addrs.first(isIPv4)
   switch la := la.(type) {
   case *TCPAddr:
      l, err = sl.listenTCP(ctx, la)
   case *UnixAddr:
      l, err = sl.listenUnix(ctx, la)
   default:
      return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
   }
   if err != nil {
      return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: err} // l is non-nil interface containing nil pointer
   }
   return l, nil
}

当有请求过来之后,生成的Listener就会尝试建立连线,进而处理连线的请求,第一个代码块的第13行就是在做这件事,代码如下。参数l就是上方代码回传的Listener,下方第13行,会先追踪穿进来的Listener是否正常的,如果是关闭的,会回传服务器关闭的错误(第14行)。

func (srv *Server) Serve(l net.Listener) error {
	if fn := testHookServerServe; fn != nil {
		fn(srv, l) // call hook with unwrapped listener
	}

	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)

	var tempDelay time.Duration     // how long to sleep on accept failure
	baseCtx := context.Background() // base is always background, per Issue 16220
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		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
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(ctx)
	}
}

之后,才是重头戏,上方第21行-第48行。这段代码通过一个for循环,一直在监听是否有请求,如果有监听到请求则尝试建立connection,见上方第22行。详细代码见下方代码块。上方的l.Accept是一个接口方法,因为传进来的是TcpListener,而TcpListener又实现了/net/net.go里面的Listener接口。所以上方的l.Accept调的就是下方的Accept函数,返回一个类型为net.Conn的连接。如果Accept没有报错,上方第45行,则会根据这个返回的net.Conn新初始化一个/net/http/server.go的Conn结构体。这个/net/http/server.go的Conn结构体代表的是HTTP连接的服务器端。因为连线是很多的,而服务器要一直监听,所以开协程处理新建立的请求,上方第47行,详见下下方代码块。

func (l *TCPListener) Accept() (Conn, error) {
   if !l.ok() {
      return nil, syscall.EINVAL
   }
   c, err := l.accept()
   if err != nil {
      return nil, &OpError{Op: "accept", Net: l.fd.net, Source: nil, Addr: l.fd.laddr, Err: err}
   }
   return c, nil
}

下方代码块:

第7行-第9行是对有加密的请求做处理。

第18行-第19行初始化Conn的buffer read和buffer write,定义了一个读的缓存流和一个写的缓存流。

第21行,开一个for接受这个connect的请求,开for循环的原因是因为该connection可能会被复用,并且是一个长连接。

第22行,获取请求的request内容,代码详见下下方代码块。

第28行-第32行,读取body剩下内容。

第34行,ServeHTTP封装了http server实现的ServeHttp接口,在读取完request body后,匹配URI请求的方法并执行(详见Beego_code_Router中的ServeHttp方法解析)。

最后,判断该请求是否是一个长连接,如果是长连接则设置空闲超时时间及读请求的最后时间,否则结束该次请求。一个for的结束就代表着这个request处理完毕。

func (c *conn) serve(ctx context.Context) {
   c.remoteAddr = c.rwc.RemoteAddr().String()
   ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
   
    ...

   if tlsConn, ok := c.rwc.(*tls.Conn); ok {
      ...
   }

   // 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)
      
      ...

      c.curReq.Store(w)

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

      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() {
         // 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{})
   }
}

下方代码块为/net/http/server.go conn结构体的readRequest方法,主要用于读取connection的请求,初始化response结构体。真正读取request的内容的方法在下方代码块的第30行。

第10行-第22行在设定读写超时时间。

第30行,读取request body的源码见下下方代码块。

func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
   if c.hijacked() {
      return nil, ErrHijacked
   }

   var (
      wholeReqDeadline time.Time // or zero if none
      hdrDeadline      time.Time // or zero if none
   )
   t0 := time.Now()
   if d := c.server.readHeaderTimeout(); d != 0 {
      hdrDeadline = t0.Add(d)
   }
   if d := c.server.ReadTimeout; d != 0 {
      wholeReqDeadline = t0.Add(d)
   }
   c.rwc.SetReadDeadline(hdrDeadline)
   if d := c.server.WriteTimeout; d != 0 {
      defer func() {
         c.rwc.SetWriteDeadline(time.Now().Add(d))
      }()
   }

   c.r.setReadLimit(c.server.initialReadLimitSize())
   if c.lastMethod == "POST" {
      // RFC 7230 section 3 tolerance for old buggy clients.
      peek, _ := c.bufr.Peek(4) // ReadRequest will get err below
      c.bufr.Discard(numLeadingCRorLF(peek))
   }
   req, err := readRequest(c.bufr, keepHostHeader)
   if err != nil {
      if c.r.hitReadLimit() {
         return nil, errTooLarge
      }
      return nil, err
   }

   if !http1ServerSupportsRequest(req) {
      return nil, badRequestError("unsupported protocol version")
   }

   c.lastMethod = req.Method
   c.r.setInfiniteReadLimit()

   ...

   ctx, cancelCtx := context.WithCancel(ctx)
   req.ctx = ctx
   req.RemoteAddr = c.remoteAddr
   req.TLS = c.tlsState
   if body, ok := req.Body.(*body); ok {
      body.doEarlyClose = true
   }

   // Adjust the read deadline if necessary.
   if !hdrDeadline.Equal(wholeReqDeadline) {
      c.rwc.SetReadDeadline(wholeReqDeadline)
   }

   w = &response{
      conn:          c,
      cancelCtx:     cancelCtx,
      req:           req,
      reqBody:       req.Body,
      handlerHeader: make(Header),
      contentLength: -1,
      closeNotifyCh: make(chan bool, 1),

      // We populate these ahead of time so we're not
      // reading from req.Header after their Handler starts
      // and maybe mutates it (Issue 14940)
      wants10KeepAlive: req.wantsHttp10KeepAlive(),
      wantsClose:       req.wantsClose(),
   }
   if isH2Upgrade {
      w.closeAfterReply = true
   }
   w.cw.res = w
   w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize)
   return w, nil
}

下方代码块为/net/http/request.go中的readRequest方法,解析request请求,并返回Request对象。

该请求的request是http请求,所以在第7行读第一行的时候,是根据http的header的第一行读取,可见下图:

在这里插入图片描述

将读到的第一行数据拿去做解析,就可以得到method,requestURI,protocol。

第22行判断http的method是否为有效的method(OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT)。

第29行-第33行读取request header剩下的字段及字段值,以(key,value) pair的形式。

func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error) {
   tp := newTextprotoReader(b)
   req = new(Request)

   // First line: GET /index.html HTTP/1.0
   var s string
   if s, err = tp.ReadLine(); err != nil {
      return nil, err
   }
   defer func() {
      putTextprotoReader(tp)
      if err == io.EOF {
         err = io.ErrUnexpectedEOF
      }
   }()

   var ok bool
   req.Method, req.RequestURI, req.Proto, ok = parseRequestLine(s)
   if !ok {
      return nil, &badStringError{"malformed HTTP request", s}
   }
   if !validMethod(req.Method) {
      return nil, &badStringError{"invalid method", req.Method}
   }
   
   ...

   // Subsequent lines: Key: value.
   mimeHeader, err := tp.ReadMIMEHeader()
   if err != nil {
      return nil, err
   }
   req.Header = Header(mimeHeader)

   ...
    
   return req, nil
}
&badStringError{"malformed HTTP request", s}
   }
   if !validMethod(req.Method) {
      return nil, &badStringError{"invalid method", req.Method}
   }
   
   ...

   // Subsequent lines: Key: value.
   mimeHeader, err := tp.ReadMIMEHeader()
   if err != nil {
      return nil, err
   }
   req.Header = Header(mimeHeader)

   ...
    
   return req, nil
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值