来我们探究一下net/http 的代码流程

简介: 来我们探究一下net/http 的代码流程

探究一下net/http 的代码流程

net/http 是什么?

是GO的其中一个标准库,用于Web应用的开发,使用这个库,可以让开发变得更加迅速和简便,且易于上手。

那么问题来了

使用库,确实方便,无脑调接口,拼拼凑凑能跑就行,管他效率性能,出了问题,删库跑路就行了。。。

实际真的是这个样子吗?作为一个开发,一定要想办法弄明白不清楚的事情,要弄明白用到工具的原理,更需要清晰的知道自己开发产品的运作原理,正所谓

知其然,而不知其所以然,欲摹写其情状,而心不能自喻,口不能自宣,笔不能自传。

我们对于技术要有探索精神,对代码要有敬畏之心,那今天咱们就来看看net/http的代码流程吧

使用框架/库,必要要接受其自身的一套约定和模式,我们必须要了解和熟悉这些约定和模式的用法,否则就会陷入用错了都不知道的境地。

在GOLANG中,net/http的组成部分有客户端服务端

库中的结构和函数有的只支持客户端和服务器这两者中的一个,有的同时支持客户端和服务器,用图说话:

  • 只支持客户端的

Client , response

  • 只支持服务端的

ServerMux,Server ,ResponseWriter,Handler 和 HandlerFunc

  • 客户端,服务端都支持的

Header , Request , Cookie

net/http构建服务器也很简单,大体框架如下:

客户端 请求 服务器,服务器里面使用 net/http包,包中有多路复用器,和对应多路复用器的接口,服务器中的多个处理器处理不同的请求,最终需要落盘的数据即入库

万里长城第一步,我们发车了

开始写一个简单的Request

package main
import (
   "fmt"
   "net/http"
)
func main() {
   http.HandleFunc("/Hi", func(w http.ResponseWriter, r *http.Request) {
       // <h1></h1> 是html 标签
      w.Write([]byte("<h1>Hi xiaomotong</h1>"))
   })
   // ListenAndServe 不写ip 默认服务器地址是 127.0.0.1
   if err := http.ListenAndServe(":8888", nil); err != nil {
      fmt.Println("http server error:", err)
   }
}

运行服务器代码,在浏览器中输入127.0.0.1:8888/Hi或者localhost:8888/Hi,即可看到效果

创建一个Go写的服务器就是那么简单,只要调用ListenAndServe并传入网络地址,端口,处理请求的处理器(handler)即可。

注意:

  • 如果网络地址参数为空字符串,那么服务器默认使用80端口进行网络连接
  • 如果处理器参数为nil,那么服务器将使用默认的多路复用器DefaultServeMux

可是实际上http是如何建立起来的呢?一顿操作猛如虎,一问细节二百五

HTTP的建立过程

HTTP的建立流程都是通用的,因为他是标准协议。写C/C++的时候,这些流程基本上自己都要去写一遍,但是写GO的时候,标准库里面已经封装好了,因此才会有上述一个函数就可以写一个web服务器的情况

服务端涉及的流程

  • socket建立套接字
  • bind绑定地址和端口
  • listen设置最大监听数
  • accept开始阻塞等待客户端的连接
  • read读取数据
  • write回写数据
  • close 关闭

客户端涉及的流程

  • socket建立套接字
  • connect 连接服务端
  • write写数据
  • read读取数据

那么数据在各个层级之间是如何走的呢

还是那个熟悉的7层OSI模型,不过实际应用的话,我们用TCP/IP 5层模型

上述TCP/IP五层模型,可能会用到的协议大体列一下

  • 应用层:

HTTP协议,SMTP,SNMP,FTP,Telnet,SIP,SSH,NFS,RTSP

  • 传输层

比较常见的协议是TCP,UDP,SCTP,SPX,ATP等

UDP不可靠, SCTP有自己特殊的运用场景, 所以一般情况下HTTP是由TCP协议进行传输

不过企业应用的话,会将UDP改造成可靠的传输,实际上是对标准udp上封装自定义的头,模拟TCP的可靠传输,三次握手, 四次挥手就是在这里发生的

  • 网络层

IP协议、ICMP,IGMP,IPX,BGP,OSPF,RIP,IGRP,EIGRP,ARP,RARP协议 ,等等

  • 数据链路层

Ethernet , PPP,WiFi ,802.11等等

  • 物理层

SO2110,IEEE802 等等

知道HTTP的通用流程,那么我们来具体看看net/http标准库是如何实现这整个流程的,先从建立socket看起

net/http 建立socket

还记得最上面说到的request小案例吗?我们可以从这里开始入手

package main
import (
   "fmt"
   "net/http"
)
func main() {
   http.HandleFunc("/Hi", func(w http.ResponseWriter, r *http.Request) {
      w.Write([]byte("<h1>Hi xiaomotong</h1> "))
   })
   if err := http.ListenAndServe(":8888", nil); err != nil {
      fmt.Println("http server error:", err)
   }
}

http.HandleFunc("/Hi", func(w http.ResponseWriter, r *http.Request) {

w.Write([]byte("

Hi xiaomotong

"))})

HandleFunc这一段是注册路由,这个路由的handler会默认放到到DefaultServeMux

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
   DefaultServeMux.HandleFunc(pattern, handler)
}

HandleFunc 实际上是调用了 ServeMux服务的HandleFunc

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
   if handler == nil {
      panic("http: nil handler")
   }
   mux.Handle(pattern, HandlerFunc(handler))
}

ServeMux服务的HandleFunc调用了自己服务的Handle 实现

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
   mux.mu.Lock()
   defer mux.mu.Unlock()
   if pattern == "" {
      panic("http: invalid pattern")
   }
   if handler == nil {
      panic("http: nil handler")
   }
   if _, exist := mux.m[pattern]; exist {
      panic("http: multiple registrations for " + pattern)
   }
   if mux.m == nil {
      mux.m = make(map[string]muxEntry)
   }
   e := muxEntry{h: handler, pattern: pattern}
   mux.m[pattern] = e
   if pattern[len(pattern)-1] == '/' {
      mux.es = appendSorted(mux.es, e)
   }
   if pattern[0] != '/' {
      mux.hosts = true
   }
}

看了实际的注册路由实现还是比较简单,我们先不再深入的网下看,要是感兴趣的可以接着这里往下追具体的数据结构

目前的注册路由的流程是:

  • http.HandleFunc ->
  • (mux *ServeMux) HandleFunc ->
  • (mux *ServeMux) Handle

net/http监听端口+响应请求

那我们在来看看刚才request案例里面的监听地址和端口的代码是如何走的

if err := http.ListenAndServe(":8888", nil); err != nil {

fmt.Println(“http server error:”, err)

}

经过上面的三个函数流程,已经知道注册路由是如何走的了,那么ListenAndServe这个函数的监听已经handler处理数据后的响应是如何实现的呢?来我们继续

ListenAndServe侦听TCP网络地址addr,然后调用handler来处理传入连接的请求,收的连接配置为启用TCP keep-alive,该参数通常为nil,在这种情况下使用DefaultServeMux,上面提过一次,此处再次强调

// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
   server := &Server{Addr: addr, Handler: handler}
   return server.ListenAndServe() // 调用Server服务的 ListenAndServe函数 (srv *Server) ListenAndServe
}
// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// If srv.Addr is blank, ":http" is used.
//
// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
func (srv *Server) ListenAndServe() error {
   if srv.shuttingDown() {
      return ErrServerClosed
   }
   addr := srv.Addr
   if addr == "" {
      addr = ":http"
   }
   ln, err := net.Listen("tcp", addr) //实际是通过 net.Listen 进行监听地址和端口的
   if err != nil {
      return err
   }
   return srv.Serve(ln)
}
func Listen(network, address string) (Listener, error) {
   var lc ListenConfig
   return lc.Listen(context.Background(), network, address)
}
func (srv *Server) Serve(l net.Listener) error {
   if fn := testHookServerServe; fn != nil {
      fn(srv, l) // call hook with unwrapped listener // 回调函数的调用的位置
   }
 // ...此处省略15行代码...
   var tempDelay time.Duration // how long to sleep on accept failure   // accept阻塞失败睡眠的间隔时间
   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() {
            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) // 此处开一个协程来处理具体的请求消息
   }
}

此处通过 go c.serve(connCtx) 开启一个协程专门处理具体的请求消息

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
   c.remoteAddr = c.rwc.RemoteAddr().String()
   ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
   //  ... 此处省略部分代码
    // 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.
    //HTTP不能同时有多个活动请求。[*],直到服务器响应这个请求,它不能读取另一个
    serverHandler{c.server}.ServeHTTP(w, w.req)  // ServeHTTP 是重点
    w.cancelCtx()
    if c.hijacked() {
        return
    }
    w.finishRequest()
    //  ... 此处省略部分代码
}

此处ServeHTTP相当重要

// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
   srv *Server
}
// 处理请求
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
   handler := sh.srv.Handler
   if handler == nil {
      handler = DefaultServeMux
   }
   if req.RequestURI == "*" && req.Method == "OPTIONS" {
      handler = globalOptionsHandler{}
   }
   handler.ServeHTTP(rw, req)
}
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
   if r.RequestURI == "*" {
      if r.ProtoAtLeast(1, 1) {
         w.Header().Set("Connection", "close")
      }
      w.WriteHeader(StatusBadRequest)
      return
   }
   h, _ := mux.Handler(r)
   h.ServeHTTP(w, r)
}

(sh serverHandler) ServeHTTP 调用 (mux *ServeMux) ServeHTTP , ServeHTTP将请求发送给处理程序 h, _ := mux.Handler(r)

源码看到这里,对于net/http标准库 对于注册路由,监听服务端地址和端口的流程,大致清楚了吧

整个过程,net/http基本上是提供了 HTTP流程的整套服务,可以说是非常的香了, 整个过程基本上是这个样子的

  • net.Listen 做了初始化 套接字 socket,bind 绑定ip 和端口,listen 设置最大监听数量的 操作
  • Accept 进行阻塞等待客户端的连接
  • go c.serve(ctx) 启动新的协程来处理当前的请求. 同时主协程继续等待其他客户端的连接, 进行高并发操作
  • mux.Handler获取注册的路由, 然后拿到这个路由的handler 处理器, 处理客户端的请求后,返回给客户端结果

关于底层是如何封包解包,字节是如何偏移的,ipv4,ipv6如何去处理的,有兴趣的朋友们可以顺着代码继续追,欢迎多多沟通交流

好了,本次就到这里,下一次是 gin的路由算法分享

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~

相关文章
|
3天前
|
安全 Linux 网络安全
Linux使用HTTP隧道代理代码示例模版
Linux使用HTTP隧道代理代码示例模版
20 0
|
3天前
状态码对于理解HTTP请求和响应的流程,以及调试网络问题非常重要
【5月更文挑战第15天】HTTP状态码由三位数字表示,分为1xx-5xx五类。1xx为信息响应,2xx表示成功,如200(请求成功)、201(创建成功)。3xx是重定向,如301(永久移动)、302(临时重定向)。4xx表示客户端错误,如400(坏请求)、404(未找到)。5xx是服务器错误,包括500(内部服务器错误)和503(服务不可用)。这些状态码用于理解请求响应流程和调试网络问题。
9 1
|
3天前
|
存储 网络协议 Go
7天玩转 Golang 标准库之 http/net
7天玩转 Golang 标准库之 http/net
7 2
|
3天前
|
JSON 前端开发 搜索推荐
BoostCompass( http_server 模块 | 项目前端代码 )
BoostCompass( http_server 模块 | 项目前端代码 )
26 4
|
3天前
|
SQL DataWorks Java
DataWorks操作报错合集之在阿里云 DataWorks 中,代码在开发测试阶段能够成功运行,但在提交后失败并报错“不支持https”如何解决
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
26 1
DataWorks操作报错合集之在阿里云 DataWorks 中,代码在开发测试阶段能够成功运行,但在提交后失败并报错“不支持https”如何解决
|
3天前
|
JSON 编解码 Go
Golang深入浅出之-HTTP客户端编程:使用net/http包发起请求
【4月更文挑战第25天】Go语言`net/http`包提供HTTP客户端和服务器功能,简化高性能网络应用开发。本文探讨如何发起HTTP请求,常见问题及解决策略。示例展示GET和POST请求的实现。注意响应体关闭、错误处理、内容类型设置、超时管理和并发控制。最佳实践包括重用`http.Client`,使用`context.Context`,处理JSON以及记录错误日志。通过实践这些技巧,提升HTTP编程技能。
26 1
|
3天前
|
中间件 Go API
Golang深入浅出之-Go语言标准库net/http:构建Web服务器
【4月更文挑战第25天】Go语言的`net/http`包是构建高性能Web服务器的核心,提供创建服务器和发起请求的功能。本文讨论了使用中的常见问题和解决方案,包括:使用第三方路由库改进路由设计、引入中间件处理通用逻辑、设置合适的超时和连接管理以防止资源泄露。通过基础服务器和中间件的代码示例,展示了如何有效运用`net/http`包。掌握这些最佳实践,有助于开发出高效、易维护的Web服务。
29 1
|
3天前
|
Go 开发者
Golang深入浅出之-HTTP客户端编程:使用net/http包发起请求
【4月更文挑战第24天】Go语言的`net/http`包在HTTP客户端编程中扮演重要角色,但使用时需注意几个常见问题:1) 检查HTTP状态码以确保请求成功;2) 记得关闭响应体以防止资源泄漏;3) 设置超时限制,避免长时间等待;4) 根据需求处理重定向。理解这些细节能提升HTTP客户端编程的效率和质量。
19 1
|
3天前
|
JavaScript
GET http://192.168.2.198:8080/sockjs-node/info?t=1626862752216 net::ERR_CONNECTION_TIMED_OUT
GET http://192.168.2.198:8080/sockjs-node/info?t=1626862752216 net::ERR_CONNECTION_TIMED_OUT
19 0
|
3天前
|
开发框架 搜索推荐 .NET
ASP.NET体检中心源码,实现检前、检中、检后全流程管理
健康体检系统遵循整个健康体检的实际流程,以提高工作效率、降低错检、防止漏检提高人性化服务水平为目的,在体检过程中可以高效、自动化、人性化的处理数据与提供服务。针对体检流程中工作强度在时间分配上不均匀等特点,解决了体检信息处理效率问题,在不增加体检中心人力资源投入或少投入的基础上,提升信息处理的效率,从而突破体检中心日处理体检人数的上限,为体检中心创造更大经济效益的同时,还能有效的降低体检工作者的劳动强度。
41 5