首页 > 编程语言 >Go For Web:Golang http 包详解(源码剖析)

Go For Web:Golang http 包详解(源码剖析)

时间:2023-04-14 22:26:08浏览次数:86  
标签:ServeHTTP Web 调用 http handler Go 源码

前言:

本文作为解决如何通过 Golang 来编写 Web 应用这个问题的前瞻,对 Golang 中的 Web 基础部分进行一个简单的介绍。目前 Go 拥有成熟的 Http 处理包,所以我们去编写一个做任何事情的动态 Web 程序应该是很轻松的,接下来我们就去学习了解一些关于 Web 的相关基础,了解一些概念,以及 Golang 是如何运行一个 Web 程序的。
文章预计分为四个部分逐步更新
2023-04-13 星期四 一更 全文共计约 3800 字 阅读大约花费 5 分钟
2023-04-14 星期五 二更(两篇) 全文共计约 2000 字 阅读大概花费 4 分钟
2023-04-14 星期五 三更 全文共计约 2000 字 阅读大概花费 5 分钟


文章目录:

  1. Web 的工作方式
  2. 用 Go 搭建一个最简单的 Web 服务
  3. 了解 Golang 运行 web 的原理
  4. Golang http 包详解(源码剖析)

正文:

Golang http 包详解(源码剖析)

前面小节我们认识了 Web 的工作方式,也成功用 Go 搭建了一个最简单的 Web 服务了解了 Golang 运行 Web 的原理。现在我们详细地去解剖以下 http 包,看看它如何实现整个过程的

Go 的 http 包中有两个核心功能:Conn 、ServeMux

Conn 的 goroutine

与我们使用其他语言编写 http 服务器不同, Go为了实现高并发和高性能,使用了 goroutines 来处理 Conn 的读写事件。这样让每个请求都能保持独立,相互不会阻塞,可以高效地响应网络事件,这是 Go 高效的保证。

根据上一节,我们知道 Go 在等待客户端请求里面是这样写的:

点击查看代码
c, err := srv.newConn(rw)
if ree != nil {
	continue
}
go c.serve()

这段代码中,客户端的每一次请求都会创建一个 Conn,这个 Conn 里面保存了这次请求的信息,然后再传递到对应的 handler,该handler中便可以读取到相应的 header 信息,这样保证了每个请求的独立性。

ServeMux 的自定义

在之前我们 使用 conn.server 的时候,其实内部是调用了 http 包默认的路由器也就是DefaultServeMux,通过这个路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢?

结构如下:

  • 首先是一个 自定义类型结构体 ServeMux 其中包含一个
    和一个 路由规则
    image

  • 路由规则中一个 string 对应一个 mux 实体,我们来看看 muxEntry 它也是一个自定义类型结构体,包含一个 布尔值,一个Handler 处理函数
    image

  • 最后再来看看 Handler 的定义,它其实是一个接口,实现了 ServeHTTP 这个函数
    image

这个时候我们可以回过头来看我们之前自己写的 Web 服务器

点击查看代码
// Handler处理函数
func sayhelloName(w http.ResponseWriter, r *http.Request) {
	r.ParseForm() // 解析参数,默认不会解析
	fmt.Println(r.Form)// 以下这些信息是输出到服务端的打印信息:请求表单form、路径path、格式scheme
	fmt.Println("path", r.URL.Path)
	fmt.Println("scheme", r.URL.Scheme)
	fmt.Println(r.Form["url_long"])
	for k, v := range r.Form {
		fmt.Println("key:", k)
		fmt.Println("val:", strings.Join(v, ""))
	}
	fmt.Fprintln(w, "Hello astaxie!") // 输出到客户端
}
调用: `http.HandleFunc("/", sayhelloName) // 设置访问的路由`

我们会发现,我们自己写的 sayhelloName 函数并没有实现 ServeHTTP 这个函数,也就是说按照常理我们并没有实现 Handler 这个接口,那我们是怎么添加的?

原来, http 包里面还定义了一个自定义函数类型 HandlerFunc,而我们定义的函数 sayhelloName 就是这个 HandlerFunc 调用之后的结果,这个自定义函数类型默认会实现 ServeHTTP 这个方法,即我们调用了 HandlerFunc(f)强制类型转换 f 成为了 HandlerFunc 类型,这样 f 就拥有了ServeHTTP 方法
image

路由器里存储好了相应的路由规则(Response / Request)之后,那么具体的请求又是怎么分发的呢?
路由器接收到请求之后调用 mux.handler(r).ServeHTTP(w,r)
也就是调用对应路由的 handler 的 ServerHTTP 接口,让我们来看看
mux.handler(r)是怎么处理的↓
image

我们可以看到它是根据用户请求的 URL 和路由器里面存储的 map 去匹配的,当匹配到之后返回存储的 handler,调用这个 handler 的 ServeHTTP 接口就可以执行相应的函数了

通过上面的介绍,我们大致了解了整个构建路由的过程,Go其实支持外部实现的路由器 而 ListenAndServe 的第二个参数就是用来配置外部路由器的,它是一个 Handler 接口,所以我们的外部路由只要实现了 Handler 接口就可以发挥作用,因此我们可以在自己实现的路由器的 ServeHTTP 里面实现自定义的路由功能

贴个代码↓

点击查看代码
package main

import (
	"fmt"
	"net/http"
)

type MyMux struct {
}

func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		sayhelloName2(w, r)
		return
	} else {
		http.NotFound(w, r)
		return
	}
}

func sayhelloName2(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello myroute!")
}

func main() {
	mux := &MyMux{}
	http.ListenAndServe(":9090", mux)
}

实现效果:
image

Go 代码的执行流程

最后我们来梳理一下整个代码的执行过程

  • 首先调用 Http.HandleFunc
    按照顺序做了这几件事:
  1. 调用了 DefaultServeMux 的 HandleFunc
  2. 调用了 DefaultServeMux 的 Handler
  3. 往 DefaultServeMux 的 map[string]muxEntry 中增加对应的handler 和 路由规则
  • 其次调用 http.ListenAndServe(":9090",nil)
    按顺序做了这几件事:
  1. 实例化 Server

  2. 调用 Server 的 ListenAndServer()

  3. 调用 net.Listen("tcp", addr)监听端口

  4. 启动一个 for 循环,在循环题中 Accept 请求

  5. 对每一个请求实例化一个 Conn,并且开启一个 goroutine 为这个请求开一个 go.c.serve()

  6. 读取每个请求的内容 w, err := c.readRequest()

  7. 判断 handler 是否为空,如果没有就设置 handler(默认设置)

  8. 调用 handler 的ServeHTTP

  9. 进入到 DefaultServerMux.ServeHTTP

  10. 根据 request 选择 handler, 并且进去到这个 handler 的 ServerHTTP
    image

  11. 选择 handler
    A 判断是否有路由能满足这个 request (循环遍历 ServerMux 的 muxEntry)
    B 如果满足,则调用这个路由 handler 的 ServeHTTP
    C 如果不满足,则调用 NotFoundHandler 的 ServeHTTP

总结

到这里为止我们从第一章介绍了 HTTP 协议,DNS 解析过程,了解了 Web 的工作方式,第二章分别用 Go 搭建一个最简单的 Web 服务,并且了解 Golang 运行 web 的原理,在最后一章,我们还深入到 net/http 包中的源码里为大家揭开了更底层的原理

既然对 Go 开发 Web 有了初步的了解,接下来我们就可以有十足的信心去学习更多 Go For Web 的后续内容了!

关于 Golang 基础部分 以及 计算机网络部分读者可以参阅我的往期 blog

标签:ServeHTTP,Web,调用,http,handler,Go,源码
From: https://www.cnblogs.com/slowlydance2me/p/17318092.html

相关文章

  • vue2源码-五、将模板编译解析成AST语法树1
    将模板编译成ast语法树complileToFunction方法vue数据渲染:template模板->ast语法树->render函数,模板编译的最终结果结果就是render函数。在complileToFunction方法中,生成render函数,需要以下两个核心步骤:通过parserHTML方法:将模板(template或html)内容编译成ast语法树通过co......
  • web空指针报500错误
     循环遍历数据遇到空指针,调试,发现转发没错,取出现问题。  后想起,需要先将数据查询,使得先进行转发再取,之前是直接取,所以为空。 将web配置后可行。 ......
  • web端audiocontext进行音频采集和播放
    1学习网站https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/getUserMediahttps://developer.mozilla.org/zh-CN/docs/Web/API/AudioContext2准备工作下载小而美H5编辑器,Hbulider.用于代码编辑和运行测试;下载瑞昱高清晰音频管理器,用于保证麦克风扬声器可调式可用......
  • 未来源码|什么是数据集成?超全的SeaTunnel 集成工具介绍
    以下文章来源于大数据与云原生技术分享,作者liugp推荐语:随着互联网流量爆发式增长,越来越多的公司业务需要支撑海量数据存储,对高并发、高可用、高可扩展性等特性提出了更高的要求。这也促使各种类型的数据库快速发展,至今常见数据库已经达到200多个。与之相伴的便是,各种数据库之间......
  • Spring很常用的@Conditional注解的使用场景和源码解析
    你好,我是刘牌!介绍今天要分享的是Spring的注解@Conditional,@Conditional是一个条件注解,它的作用是判断Bean是否满足条件,如果满足条件,则将Bean注册进IOC中,如果不满足条件,则不进行注册,这个注解在SpringBoot中衍生出很多注解,比如@ConditionalOnProperty,@ConditionalOnBean,@Conditi......
  • javaweb实验二
    实验项目名称:实验二  服务器端简单程序设计 一、实验目的通过一个小型网站的开发,加深对session,request,response,cookie等对象的理解,掌握其使用方法,进一步深入掌握HTML、CSS和JavaScript等知识。二、实验内容和基本要求1)编写index.jsp文件,展示某一类物品或知识的介绍,可以......
  • centos 升级内核版本(源码)
    查看内核版本号:username-r 或者username-a  1、安装依赖yuminstall-ygccmakegitctagsncurses-developenssl-develyuminstall-ybisonflexelfutils-libelf-develbc如果担心依赖问题不够新,可以在执行:yum-yupgrade,更新操作系统上的所有依赖 2......
  • Web前端资源
    招聘要求前端工程师:1.熟练掌握HTML5、CSS3、JavaScript,Ajax,跨域等基础知识;2.熟练掌握VUE开发框架,熟悉ElementUI、Vant等UI框架的使用;3.掌握项目版本控制工具Git的使用;4.有APICloud开发经验更佳;Web前端画图招聘要求:1.熟练掌握ECharts画图功能,包括曲线图、热力图以......
  • WebAssembly初尝试
    前言之前老是听别人提到WebAssembly这个词,一直对其比较模糊,不能理解是个啥东西,后来自己实践了一下,发现其实就是一种提高代码性能的手段。简介WebAssembly是一种运行在现代网络浏览器中的新型代码,并且提供新的性能特性和效果。它设计的目的不是为了手写代码而是为诸如C、C+......
  • c# 计算器2.0源码
      主要时间浪费在 //执行计算objectresult=newDataTable().Compute(s,"");这条语句上。如果不加处理,特别大的整数相乘,会提示值太大。只好将整数加个0变为小数;usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;......