美文网首页
Go语言 Web开发(2)HTTP基础流程分析(上)

Go语言 Web开发(2)HTTP基础流程分析(上)

作者: 小杰的快乐时光 | 来源:发表于2018-08-23 05:20 被阅读0次

Go语言搭建Web服务器
Go语言中提供了完善的net/http包,通过这些我们可以自己搭建一个本地web服务,同时还可以通过net/http包对cookie,静态文件的能够进行设置与操作。代码如下所示

import (
   "net/http"
   "fmt"
   "strings"
   "log"
)

func main() {
   //设置访问的路由
   http.HandleFunc("/",myGoWebService)
   //设置监听端口
   err := http.ListenAndServe(":9090",nil)
   if err != nil {
      log.Fatal("ListenAndServe: ",err)
   }
}

func myGoWebService(rw http.ResponseWriter,request *http.Request)  {
   //解析传过来的参数,默认不会解析
   request.ParseForm()
   //打印信息
   fmt.Println(request.Form)
   //打印URL路径
   fmt.Printf("path: %v \n",request.URL.Path)
   //打印URL协议
   fmt.Printf("scheme: %v \n",request.URL.Scheme)
   for k,v := range request.Form {
      fmt.Printf("key: %v\n",k)
      fmt.Printf("value: %v \n",strings.Join(v,"")) //将相同k值的v进行合并
   }
   //输出信息到浏览器
   fmt.Fprintf(rw,"Hello GoLang")
}

我们在浏览器输入地址:

[http://localhost:9090/?url_username=111&url_username=333&url_password=222](http://localhost:9090/?url_username=111&url_username=333&url_password=222)

浏览器上显示如图:

Http流程分析.png

使用Go编写Web服务器只要调用http包中的 http.HandleFunc 函数与 http.ListenAndServe 函数就可以了。

我们先来了解以下关于服务端的几个概念
①Request:用户请求的信息,用来解析用户的请求,包括URL,cookie,post,get等。
②Response:服务器发送给客户端的信息。
③Conn:用户每次的请求连接
④Handler:服务器处理用户请求并生成返回信息的处理逻辑。

分析http包的运行机制
Go实现Web服务的工作模式的流程图

Web服务的工作模式的流程图.png

流程分析
① 创建Listen Socket, 监听指定的端口, 等待客户端请求到来。

② Listen Socket接受客户端的请求connect, 得到Client Socket(accept), 接下来通过Client Socket与客户端通信(request与response)。

③处理客户端的请求, 首先从Client Socket读取HTTP请求的协议头, 如果是POST方法, 还可能要读取客户端提交的数据, 然后交给相应的handler处理请求, handler处理完毕准备好客户端需要的数据, 通过ClientSocket写给客户端。

这个流程中,我们主要关注三个问题:
①如何监听端口?
②如何接收客户端请求(connect)?
③如何根据HTTP请求分配对应的handler?

再回到我们之前用Go语言写的本地web服务代码中,我们通过使用函数 http.ListenAndServe 来监听指定的端口号,在底层中是这样实现的:

// addr 指定端口号,handler 指定处理逻辑,可以为nil
func ListenAndServe(addr string, handler Handler) error {
    //初始化一个server对象
   server := &Server{Addr: addr, Handler: handler} 
   return server.ListenAndServe()
}

func (srv *Server) ListenAndServe() error {
   addr := srv.Addr
   if addr == "" {
      addr = ":http"
   }
 //底层用TCP协议搭建了一个服务,然后监控我们设置的端口。
   ln, err := net.Listen("tcp", addr)
   if err != nil {
      return err
   }
    //处理接收客户端的请求信息(connect)
   return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})

整个http处理过程源码:

//Serve接受Listener l上的传入连接,为每个连接创建一个新的服务goroutine。 服务goroutines读取请求,然后调用srv.Handler来回复它们。

//对于HTTP / 2支持,应在调用Serve之前将srv.TLSConfig初始化为提供的侦听器的TLS配置。 如果srv.TLSConfig为非零并且在Config.NextProtos中不包含字符串“h2”,则不启用HTTP / 2支持。

//服务始终返回非零错误。 关闭或关闭后,返回的错误是ErrServerClosed。

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)
//在for循环中处理请求
   for {
    //在这里通过Listener接收请求
      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
    //创建一个Conn 连接
      c := srv.newConn(rw)
      c.setState(c.rwc, StateNew) // before Serve can return
    //单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务
      go c.serve(ctx)
   }
}

在上面代码中我们可以看到用户的每一次请求都是在一个新的goroutine去服务,相互不影响,这就是Go的高并发体现。

那么如何具体分配handler到对应的函数中去呢?在上面代码中的 c.serve(ctx) 调用的是 func (c *conn) serve(ctx context.Context) 函数,其中在该函数中有下面这段代码(部分)

//解析request请求
w, err := c.readRequest(ctx)
//取出并分析resp,req
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
}
//映射url与handlefunc()
serverHandler{c.server}.ServeHTTP(w, w.req)

ServeHTTP的源码如下所示:

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
   handler := sh.srv.Handler //获取相应的handler
   if handler == nil {
//若ListenAndServe函数中的handler为nil,则handler为默认值DefaultServeMux
//该变量是用来匹配url跳转到相应的handle函数
//比如之前设置的 http.HandleFunc("/",myGoWebService),当请求的url为“/”时,就会跳转到 myGoWebService 函数
      handler = DefaultServeMux  
   }
   if req.RequestURI == "*" && req.Method == "OPTIONS" {
      handler = globalOptionsHandler{} // 全局选项处理程序响应“OPTIONS *”请求。
   }
   handler.ServeHTTP(rw, req) //调用 myGoWebService 函数,最后通过写入response的信息反馈到客户端。
}

上述代码可以用以下流程图来演示:一个http连接处理流程


一个http连接处理流程.png

相关文章

网友评论

      本文标题:Go语言 Web开发(2)HTTP基础流程分析(上)

      本文链接:https://www.haomeiwen.com/subject/ljouiftx.html