前言:

本文作为解决如何通过 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 其中包含一个

    和一个 路由规则

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

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

这个时候我们可以回过头来看我们之前自己写的 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 方法

路由器里存储好了相应的路由规则(Response / Request)之后,那么具体的请求又是怎么分发的呢?

路由器接收到请求之后调用 mux.handler(r).ServeHTTP(w,r)

也就是调用对应路由的 handler 的 ServerHTTP 接口,让我们来看看

mux.handler(r)是怎么处理的↓

我们可以看到它是根据用户请求的 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)
}

实现效果:

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

  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

Goalng:基础复习一遍过

漫谈计算机网络:网络层 ------ 重点:IP协议与互联网路由选择协议

以上

看完记得留下一个

Go For Web:Golang http 包详解(源码剖析)的更多相关文章

  1. Golang Context 包详解

    Golang Context 包详解 0. 引言 在 Go 语言编写的服务器程序中,服务器通常要为每个 HTTP 请求创建一个 goroutine 以并发地处理业务.同时,这个 goroutine 也 ...

  2. Java开源生鲜电商平台-盈利模式详解(源码可下载)

    Java开源生鲜电商平台-盈利模式详解(源码可下载) 该平台提供一个联合买家与卖家的一个平台.(类似淘宝购物,这里指的是食材的购买.) 平台有以下的盈利模式:(类似的平台有美菜网,食材网等) 1. 订 ...

  3. ArrayList详解-源码分析

    ArrayList详解-源码分析 1. 概述 在平时的开发中,用到最多的集合应该就是ArrayList了,本篇文章将结合源代码来学习ArrayList. ArrayList是基于数组实现的集合列表 支 ...

  4. LinkedList详解-源码分析

    LinkedList详解-源码分析 LinkedList是List接口的第二个具体的实现类,第一个是ArrayList,前面一篇文章已经总结过了,下面我们来结合源码,学习LinkedList. 基于双 ...

  5. Shiro的Filter机制详解---源码分析

    Shiro的Filter机制详解 首先从spring-shiro.xml的filter配置说起,先回答两个问题: 1, 为什么相同url规则,后面定义的会覆盖前面定义的(执行的时候只执行最后一个). ...

  6. Shiro的Filter机制详解---源码分析(转)

    Shiro的Filter机制详解 首先从spring-shiro.xml的filter配置说起,先回答两个问题: 1, 为什么相同url规则,后面定义的会覆盖前面定义的(执行的时候只执行最后一个). ...

  7. udhcp详解源码(序)

    最近负责接入模块,包括dhcp.ipoe和pppoe等等.所以需要对dhcp和ppp这几个app的源代码进行一些分析.网上有比较好的文章,参考并补充自己的分析. 这篇udhcp详解是基于busybox ...

  8. Python-Flask框架之——图书管理系统 , 附详解源码和效果图 !

    该图书管理系统要实现的功能: 1. 可以通过添加窗口添加书籍或作者, 如果要添加的作者和书籍已存在于书架上, 则给出相应的提示. 2. 如果要添加的作者存在, 而要添加的书籍书架上没有, 则将该书籍添 ...

  9. RecyclerView实现瀑布流效果(图文详解+源码奉送)

    最近有时间研究了一下RecyclerView,果然功能强大啊,能实现的效果还是比较多的,那么今天给大家介绍一个用RecyclerView实现的瀑布流效果. 先来一张效果图: 看看怎么实现吧: 整体工程 ...

  10. Android4.3 屏蔽HOME按键返回桌面详解(源码环境下)

    点击打开链接 首先声明我是做系统开发的(高通平台),所以下面介绍的方法并不适合应用开发者. 最经有个需求要屏蔽HOME按键返回桌面并且实现自己的功能,发现以前的方式报错用不了,上网搜索了一下,发现都是 ...

随机推荐

  1. 任意的形如 z = F(x,y)的曲面生成与显示---基于OpenGL Core Profile

    运行结果:   (圆锥面) (抛物面) (马鞍面) 其中的做法是:从顶部看上去就是一个平面网格.每个点的 z.x的位置都是程序细分出来的(指定起始.结束.步长).比较固定.但高度 y 的计算使用 用户 ...

  2. 针对FILES和PATH的操作

    在修改漏洞的时候发现,根据建议都使用NIO包的FILES和PATH来进行文件操作,来保证安全性. import java.nio.file.Files;import java.nio.file.Pat ...

  3. create_generated_clock 的用法

    本文转载自:create_generated_clock_亓磊的博客-CSDN博客_create_generated_clock 文章目录 参数source和master_clock区别 create ...

  4. redis为什么是单核单线程

    1)以前一直有个误区,以为:高性能服务器 一定是多线程来实现的 原因很简单因为误区二导致的:多线程 一定比 单线程 效率高,其实不然! 在说这个事前希望大家都能对 CPU . 内存 . 硬盘的速度都有 ...

  5. 二、pycharm的安装

    1.python安装教程在上一篇已描述,详情查看: 2.安装pycharm 首先从网站下载pycharm:链接为:http://www.jetbrains.com/pycharm/download/# ...

  6. java8 stream按某个字段分组,允许分组字段是null

    Map<String, List<Dto>> deviceMap = deviceList.stream().collect(Collectors.groupingBy(Dto ...

  7. swiper常见问题、动态加载数据问题

    swiper加载静态文件是没有问题的 swiper加载动态文件需要在请求后再加载这个函数 参考链接:  https://blog.csdn.net/webzrh/article/details/781 ...

  8. Centos6、7修改主机名

    centos6 1.临时修改 hostname node1 2.永久生效 , 修改/etc/sysconfig/network 文件 HOSTNAME=node1 3.修改 /etc/hosts文件 ...

  9. 生产环境Java应用服务内存泄漏分析与解决

    有个生产环境CRM业务应用服务,情况有些奇怪,监控数据显示内存异常.内存使用率99.%多.通过生产监控看板发现,CRM内存超配或内存泄漏的现象,下面分析一下这个问题过程记录. 服务器配置情况: 生产服 ...

  10. 三天吃透RabbitMQ面试八股文

    本文已经收录到Github仓库,该仓库包含计算机基础.Java基础.多线程.JVM.数据库.Redis.Spring.Mybatis.SpringMVC.SpringBoot.分布式.微服务.设计模式 ...