Golang Http Server源码阅读

2023-11-11

这篇文章出现的理由是业务上需要创建一个Web Server。创建web是所有语言出现必须实现的功能之一了。在nginx+fastcgi+php广为使用的今天,这里我们不妨使用Go来进行web服务器的搭建。

前言

使用Go搭建Web服务器的包有很多,大致有下面几种方法,直接使用net包,使用net.http包,使用第三方包(比如gorilla)。使用net包就需要从tcp层开始封装,耗费人力物力极大,果断舍弃。直接使用封装好的net.http和第三方包才是上策。这里我们就选择了使用官方提供的net.http包来搭建web服务。另外附带一句,gorilla的第三方包现在使用还是非常广的,文档也是比较全的,有兴趣的同学可以考虑使用一下。

 

建议看这篇文章前先看一下net/http文档 http://golang.org/pkg/net/http/

 

net.http包里面有很多文件,都是和http协议相关的,比如设置cookie,header等。其中最重要的一个文件就是server.go了,这里我们阅读的就是这个文件。

几个重要概念

ResponseWriter: 生成Response的接口

Handler: 处理请求和生成返回的接口

ServeMux: 路由,后面会说到ServeMux也是一种Handler

Conn : 网络连接

 

具体分析

(具体的说明直接以注释形式放在代码中)

几个接口:

Handler

1
2
3
type Handler  interface  {
     ServeHTTP(ResponseWriter, *Request)   // 具体的逻辑函数
}

实现了handler接口的对象就意味着往server端添加了处理请求的逻辑。

下面是三个接口(ResponseWriter, Flusher, Hijacker):

ResponseWriter, Flusher, Hijacker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ResponseWriter的作用是被Handler调用来组装返回的Response的
type ResponseWriter  interface  {
     // 这个方法返回Response返回的Header供读写
     Header() Header
 
     // 这个方法写Response的Body
     Write([] byte ) ( int , error)
     
     // 这个方法根据HTTP State Code来写Response的Header
     WriteHeader( int )
}
 
// Flusher的作用是被Handler调用来将写缓存中的数据推给客户端
type Flusher  interface  {
     // 这个方法将写缓存中数据推送给客户端
     Flush()
}
 
// Hijacker的作用是被Handler调用来关闭连接的
type Hijacker  interface  {
     // 这个方法让调用者主动管理连接
     Hijack() (net.Conn, *bufio.ReadWriter, error)
 
}
1
  

response

实现这三个接口的结构是response(这个结构是http包私有的,在文档中并没有显示,需要去看源码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// response包含了所有server端的http返回信息
type response  struct  {
     conn          *conn          // 保存此次HTTP连接的信息
     req           *Request  // 对应请求信息
     chunking       bool      // 是否使用chunk
     wroteHeader    bool      // header是否已经执行过写操作
     wroteContinue  bool      // 100 Continue response was written
     header        Header    // 返回的http的Header
     written       int64     // Body的字节数
     contentLength int64     // Content长度
     status         int       // HTTP状态
     needSniff      bool      // 是否需要使用sniff。(当没有设置Content-Type的时候,开启sniff能根据HTTP body来确定Content-Type)
     
     closeAfterReply  bool      //是否保持长链接。如果客户端发送的请求中connection有keep-alive,这个字段就设置为false。
 
     requestBodyLimitHit  bool  //是否requestBody太大了(当requestBody太大的时候,response是会返回411状态的,并把连接关闭)
 
}

 

在response中是可以看到

1
2
3
4
5
func (w *response) Header() Header
func (w *response) WriteHeader(code  int )
func (w *response) Write(data [] byte ) (n  int , err error)
func (w *response) Flush()
func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error)

这么几个方法。所以说response实现了ResponseWriter,Flusher,Hijacker这三个接口

 

HandlerFunc

handlerFunc是经常使用到的一个type

1
2
3
4
5
6
7
// 这里将HandlerFunc定义为一个函数类型,因此以后当调用a = HandlerFunc(f)之后, 调用a的ServeHttp实际上就是调用f的对应方法
type HandlerFunc func(ResponseWriter, *Request)
 
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
     f(w, r)
}

 

这里需要多回味一下了,这个HandlerFunc定义和ServeHTTP合起来是说明了什么?说明HandlerFunc的所有实例是实现了ServeHttp方法的。另,实现了ServeHttp方法就是什么?实现了接口Handler!

 

所以你以后会看到很多这样的句子:

1
2
3
4
5
func AdminHandler(w ResponseWriter, r *Request) {
     ...
}
handler := HandlerFunc(AdminHandler)
handler.ServeHttp(w,r)

 

请不要讶异,你明明没有写ServeHttp,怎么能调用呢? 实际上调用ServeHttp就是调用AdminHandler。

好吧,理解这个也花了我较长时间,附带上一个play.google写的一个小例子

http://play.golang.org/p/nSt_wcjc2u

有兴趣继续研究的同学可以继续试验下去

 

如果你理解了HandlerFunc,你对下面两个句子一定不会讶异了

1
2
3
func NotFound(w ResponseWriter, r *Request) { Error(w,  "404 page not found" , StatusNotFound) }
 
func NotFoundHandler() Handler {  return  HandlerFunc(NotFound) }

 

下面接着看Server.go

ServerMux结构

它就是http包中的路由规则器。你可以在ServerMux中注册你的路由规则,当有请求到来的时候,根据这些路由规则来判断将请求分发到哪个处理器(Handler)。

它的结构如下:

1
2
3
4
type ServeMux  struct  {
     mu sync.RWMutex    //锁,由于请求设计到并发处理,因此这里需要一个锁机制
     m  map[ string ]muxEntry   // 路由规则,一个string对应一个mux实体,这里的string就是我注册的路由表达式
}
1
  

下面看一下muxEntry

1
2
3
4
type muxEntry  struct  {
     explicit  bool    // 是否精确匹配
     h        Handler  // 这个路由表达式对应哪个handler
}
1
  

看到这两个结构就应该对请求是如何路由的有思路了:

当一个请求request进来的时候,server会依次根据ServeMux.m中的string(路由表达式)来一个一个匹配,如果找到了可以匹配的muxEntry,就取出muxEntry.h,这是个handler,调用handler中的ServeHTTP(ResponseWriter, *Request)来组装Response,并返回。

 

ServeMux定义的方法有:

1
2
3
4
5
6
func (mux *ServeMux) match(path  string ) Handler    //根据path获取Handler
func (mux *ServeMux) handler(r *Request) Handler   //根据Request获取Handler,内部实现调用match
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)  //!!这个说明,ServeHttp也实现了Handler接口,它实际上也是一个Handler!内部实现调用handler
func (mux *ServeMux) Handle(pattern  string , handler Handler)  //注册handler方法
 
func (mux *ServeMux) HandleFunc(pattern  string , handler func(ResponseWriter, *Request))   //注册handler方法(直接使用func注册)

 

在godoc文档中经常见到的DefaultServeMux是http默认使用的ServeMux

var DefaultServeMux = NewServeMux()

如果我们没有自定义ServeMux,系统默认使用这个ServeMux。

 

换句话说,http包外层(非ServeMux)中提供的几个方法:

1
2
3
4
func Handle(pattern  string , handler Handler) { DefaultServeMux.Handle(pattern, handler) }
func HandleFunc(pattern  string , handler func(ResponseWriter, *Request)) {
     DefaultServeMux.HandleFunc(pattern, handler)
}

实际上就是调用ServeMux结构内部对应的方法。

 

Server

下面还剩下一个Server结构

1
2
3
4
5
6
7
8
type Server  struct  {
     Addr            string         // 监听的地址和端口
     Handler        Handler        // 所有请求需要调用的Handler(实际上这里说是ServeMux更确切)如果为空则设置为DefaultServeMux
     ReadTimeout    time.Duration  // 读的最大Timeout时间
     WriteTimeout   time.Duration  // 写的最大Timeout时间
     MaxHeaderBytes  int            // 请求头的最大长度
     TLSConfig      *tls.Config    // 配置TLS
}

Server提供的方法有:

1
2
3
func (srv *Server) Serve(l net.Listener) error    //对某个端口进行监听,里面就是调用for进行accept的处理了
func (srv *Server) ListenAndServe() error   //开启http server服务,内部调用Serve
func (srv *Server) ListenAndServeTLS(certFile, keyFile  string ) error  //开启https server服务,内部调用Serve

 

当然Http包也直接提供了方法供外部使用,实际上内部就是实例化一个Server,然后调用ListenAndServe方法

1
2
func ListenAndServe(addr  string , handler Handler) error    //开启Http服务
func ListenAndServeTLS(addr  string , certFile  string , keyFile  string , handler Handler) error  //开启HTTPs服务

 

具体例子分析

下面根据上面的分析,我们对一个例子我们进行阅读。这个例子搭建了一个最简易的Server服务。当调用http://XXXX:12345/hello的时候页面会返回“hello world”

1
2
3
4
5
6
7
8
9
10
11
12
func HelloServer(w http.ResponseWriter, req *http.Request) {
     io.WriteString(w,  "hello, world!\n" )
}
 
func main() {
     http.HandleFunc( "/hello" , HelloServer)
     err := http.ListenAndServe( ":12345" , nil)
     if  err != nil {
         log.Fatal( "ListenAndServe: " , err)
     }
 
}

 

首先调用Http.HandleFunc

按顺序做了几件事:

1 调用了DefaultServerMux的HandleFunc

2 调用了DefaultServerMux的Handle

3 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则

 

其次调用http.ListenAndServe(":12345", nil)

按顺序做了几件事情:

1 实例化Server

2 调用Server的ListenAndServe()

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

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

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

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

7 判断header是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux

8 调用handler的ServeHttp

9 在这个例子中,下面就进入到DefaultServerMux.ServeHttp

10 根据request选择handler,并且进入到这个handler的ServeHTTP

       mux.handler(r).ServeHTTP(w, r)

11 选择handler:

    A 判断是否有路由能满足这个request(循环遍历ServerMux的muxEntry)

    B 如果有路由满足,调用这个路由handler的ServeHttp

    C 如果没有路由满足,调用NotFoundHandler的ServeHttp

后记

对于net.http包中server的理解是非常重要的。理清serverMux, responseWriter, Handler, HandlerFunc等常用结构和函数是使用go web的重要一步。个人感觉由于go中文档较少,像这样有点复杂的包,看godoc的效果就远不如直接看代码来的快和清晰了。实际上在理解了http包后,才会对godoc中出现的句子有所理解。后续还会写一些文章关于使用net.http构建web server的。请期待之。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Golang Http Server源码阅读 的相关文章

  • 开源Go语言数值算法库 An open numerical library purely based on Go programming language

    目录 关于 goNum https github com chfenger goNum 安装环境 安装方法 算法 许可证书 致谢 关于goNum goNum是一款完全以Go语言为基础的开源数值算法库 它可以使你像调用其它go函数一样使用其进
  • TiDB数据库权限管理

    TiDB数据库权限管理 TiDB 的权限管理系统按照 MySQL 的权限管理进行实现 TiDB 支持大部分的 MySQL 的语法和权限类型 本文主要介绍 TiDB 权限相关操作 各项操作需要的权限以及权限系统的实现 权限相关操作 授予权限
  • Go语言中的rune数据类型

    写在前面 最近开始学习Go语言 因为自己是从Java逐步转Go原因 在感慨Go语言简便的同时 也因为其封装的数据类型和包较多 所以还得慢慢学习 今天来谈谈Go语言中的rune数据类型 名词解释 Go语言中的整数类型也有有符号数和无符号数之别
  • 香橙派4和树莓派4B构建K8S集群实践之八: TiDB

    目录 1 说明 2 准备工作 3 安装 3 1 参考Tidb官方 v1 5安装说明 3 2 准备存储类 3 3 创建crd 3 4 执行operator 3 5 创建cluster dashboard monitor容器组 3 6 设置访问
  • go-libp2p中文文档

    GO LIBP2P入门 这是有关使用libp2p的Go实现go libp2p的一系列教程中的第一篇 我们将介绍安装Go 设置新的Go模块 启动libp2p节点以及在它们之间发送ping消息 安装Go go libp2p建议使用包含模块功能的
  • go : GoLand安装及环境配置

    前因后果 新学期新气象 开学的第一节课是zw老师的区块链技术与应用领域 congratulations 涉及编程实验 使用Go语言 需要安装GoLand软件进行下一步 Go语言下载地址 golang下载链接 进去之后选择对应的版本下载 这里
  • error An unexpected error occurred: “https://registry.yarnpkg.com/axios: con 解决方案

    error An unexpected error occurred https registry yarnpkg com axios con 今天用在跑一个项目的时候发现了这个错误 看着像是网络连接不上 发现这里是用的Dokcerfile
  • go语言标准库

    在Go语言的安装文件里包含了一些可以直接使用的包 即标准库 Go语言的标准库 通常被称为语言自带的电池 提供了清晰的构建模块和公共接口 包含 I O 操作 文本处理 图像 密码学 网络和分布式应用程序等 并支持许多标准化的文件格式和编解码协
  • 42-Golang中的单元测试

    Golang中的单元测试 需求 传统方法 基本介绍 单元测试快速入门总结 综合案例 需求 在工作中 我们会遇到这样的情况 就是去确认一个函数 或者一个模块的结果是否正确 传统方法 在main函数中 调用addUpper函数 看看实际输出的记
  • TiDB介绍

    目录 TiDB 简介 一 四大核心应用场景 二 TiDB 整体架构 三 TiDB 数据库的存储 Key Value Pairs 键值对 本地存储 RocksDB Raft 协议 Region MVCC 分布式 ACID 事务 参考 TiDB
  • 共识算法 --- PBFT、Raft和Paxos

    目录 一 Raft共识算法 1 什么是Raft 2 Raft的工作流程 3 Raft的相关应用 4 Raft的缺陷 5 Raft中三个子问题 5 1 Leader选举 Election 5 1 1 节点的三种角色 5 1 2 选举过程 5
  • 【Go语言教程】(一) 下载、安装、配置

    1 下载 官网安装包下载地址为 https golang org dl 如果打不开可以使用这个地址 https golang google cn dl 找到适合你系统的版本下载 我下载的是windows版本 2 安装 msi文件点击完成安装
  • Go基础(复杂类型):结构体

    结构体 一个结构体 struct 就是一个字段的集合 而 type 的含义跟其字面意思相符 下面来写一个简单的例子 package main import fmt 一个结构体就是一个字段的集合 type Vertex struct X in
  • 解决GO语言编译程序在openwrt(mipsle架构)上运行提示Illegal instruction问题

    RT 最近在研究openwrt mipsle架构 上运行go语言编译出来的程序 一运行就报 Illegal instruction 这样的错误 百度和Google搜索了一遍 得出两种解决方案 PS 更新一遍 当时写这个文档的时候没有发现Go
  • 使用Go Hijack和jQuery轻松实现异步推送服务

    使用Go Hijack和jQuery轻松实现异步推送服务 首先要说明的是 这里实现的异步推送服务采用的是Long Polling方式 并不是Comet 如果想用Comet来实现的话 可以参考这个开源项目 http cometd org 不过
  • go语言连接mysql数据库,并验证连通性

    go语言连接mysql数据库 并验证连通性 package main import database sql sql Open加载包 github com go sql driver mysql 没用到包里的内容但是需要加载一下这个包 lo
  • 猿创征文|国产数据库之TiDB详解和安装使用

    文章目录 前言 1 TiDB简介 2 TiDB架构 3 TiDB的安装使用 3 1 部署本地测试集群 3 2 在单机上模拟部署生产环境集群 4 在生产环境部署TiDB 4 1 软硬件环境需求及前置检查 4 2 环境与系统配置检查 4 3 在
  • go添加国内镜像加速

    添加国内镜像加速 七牛云 七牛云镜像 全球CDN加速 全球CDN加速 打开你的命令终端输入Go 1 13 及以上 推荐 go env w GO111MODULE on go env w GOPROXY https goproxy cn di
  • Go语言学习4-数组类型

    数组类型 引言 1 数组 1 1 类型表示法 1 2 值表示法 1 3 属性和基本操作 总结 引言 上篇我们了解 Go语言的基本数据类型 现在开始介绍数组类型 主要如下 1 数组 在Go语言中 数组被称为Array 就是一个由若干相同类型的
  • Go Web编程实战(6)----反射

    目录 反射 反射的3大原则 接口类型变量 转换为 反射类型对象 反射类型对象 转换为 接口类型变量 反射类型对象 修改 值必 可写的 反射 与其他语言一样 Go语言的反射同样是指 计算机程序在运行时 可以访问 检测和修改它本身状态或行为的一

随机推荐

  • k8s学习-DaemonSet(模板、创建、更新、回滚、删除等)

    目录 概念 模板 实战 创建 更新 回滚 回滚到上一版本 回滚到指定版本 删除 参考 概念 DaemonSet 守护进程集 在kubectl中缩写为ds 在所有节点或者是匹配的节点上都部署一个Pod 当有节点加入集群时 也会为他们新增一个
  • Unity Cinemachine之第三人称摄像机CinemachineFreeLook属性详解

    演示 CinemachineFreeLook是一个第三人称视角相机插件 功能非常强大 内置了很多属性可以供开发者根据需求进行调整 创建 首先是Cinemachine插件的导入 如果是2018以后的Unity版本 应该 可以直接从Unity顶
  • C++之:struct和class的区别

    一 数据封装和成员访问权限 1 结构体 struct 在struct中 默认的成员访问权限是public 这意味着 在结构体外部 我们可以直接访问其成员变量和成员函数 下面是一个简单的例子 struct Point int x y Poin
  • ggplot2入门

    ggplot2入门 二 3 5 几何对象 geom 和统计变换 stat 几何对象执行着图层的实际渲染 控制着生成的图像类型 各种类型图如下 示例代码如下 head mtcars library ggplot2 p lt ggplot mt
  • 自学Java做的第一个入门项目:ATM银行系统

    import java util ArrayList import java util Random import java util Scanner public class ATMSystem public static void ma
  • Unity中协程和线程区别

    协程和线程的效果类似 都可以实现与主线程异步的效果 但是本质不一样 协程在主线程内 通过软件手段实现函数与主线程的异步效果 Startcoroutine IEnumerator Func IEnumerator是迭代器关键字 修饰一个函数
  • 标准单元库---NLDM/CCS library model

    Timing Model 数字芯片设计 除了全定制设计外 绝大部分都是基于std cell的半定制设计 那么std cell的模型就极为重要 尤其半定制 需要把一个std cell看成block box 只考虑其input output p
  • python发送邮件的乱码问题解决方案

    公司项目中需要通过后台发送邮件 邮件内容包括图片附件 如果通过PHPmailer发送 由于邮件服务器可能存在延迟现象 通过PHPmailer发送邮件 需要等待邮件发送成功后才能返回结果 这在实践中证明 有时发送邮件无法即时返回结果 影响用户
  • Python内容聚合

    Python3 教程 Python 标准库概览 pip 设置代理 设置代理 PyCharm支持的4种Python Interpreter和配置方法 Python中import的as语法 Python安装自己的代码到site packages
  • python 停止线程

    import threading import time import inspect import ctypes def async raise tid exctype raises the exception performs clea
  • MySQL索引命中与失效

    目录 创建表 MySQL执行优化器 索引的命中与失效情况 总结 拓展 讨论MySQL索引命中与失效 我们得先来创建表 创建表 SET NAMES utf8mb4 SET FOREIGN KEY CHECKS 0 Table structur
  • 动态规划记录 [动态更新]

    2021 江西省赛A 题目链接 https ac nowcoder com acm contest 21592 A 题意 给出一个布尔矩阵 每个位置的值非零即一 然后问给定p和q 问从 1 1 n m 的所有路径中至少通过p次0 q次1的路
  • Java技术栈(跳槽,面试必备)

    Java技术栈 来到北京后 感觉氛围有点浮躁 人员流动性很大 很少有人能沉下心学习 所以最近打算把整理过的知识点拿出来给大家分享下 基础扎实了 无论是工作还是跳槽都很有用 跳槽前将知识点整理成word打印出来 然后背其中的知识点 保证你能面
  • 【渗透测试】常见的数据库

    一 网站暴库漏洞 暴库 就是通过一些技术手段或者程序漏洞得到数据库的地址 并将数据非法下载到本地 黑客非常乐意于这种工作 为什么呢 因为黑客在得到网站数据库后 就能得到网站管理账号 对网站进行破坏与管理 黑客也能通过数据库得到网站用户的隐私
  • GitHub 组织是什么?您应该使用一个吗?

    GitHub 作为一个平台 被个人程序员和大型组织所使用 无论您与多少人一起工作 GitHub Organizations 都为管理多个项目的人员提供了一些不错的工具 GitHub 组织是什么 GitHub Organizations 是
  • 解决linux mysql命令 bash: mysql: command not found 的方法

    解决linux mysql命令 bash mysql command not found 的方法 腾讯云开发者社区 腾讯云 tencent com
  • 服务器的安装与维护技巧——数据湾

    机房服务器的安装与维护是机房管理中的主要任务 安装维护好服务器才能保证服务器正常 安全稳定地运行 才能确保数据的安全 服务器生产的厂家 类型 系列不同其具体的安装维护方法 步骤是有差异的 本文主要以戴尔 DELL 系列服务器为例介绍服务器的
  • 数据结构经典算法集锦

    数据结构经典算法集锦 第2章 线性表 KMP算法 获得next数组 void GetNext char t int next MAX int i 1 j 0 next 1 0 设t 0 中为字符串长度 字符保存在t 1 之后 while i
  • 谈一谈c/c++程序的内存布局

    文章目录 内存布局 文本段 初始化数据段 未初始化数据段 bss 堆区 栈区 如何查看一个程序的内存布局 内存布局 一个C 程序的典型内存布局由以下几部分组成 文本段 初始化数据段 未初始化的数据段 bss 堆区 栈区 文本段 文本段也叫代
  • Golang Http Server源码阅读

    这篇文章出现的理由是业务上需要创建一个Web Server 创建web是所有语言出现必须实现的功能之一了 在nginx fastcgi php广为使用的今天 这里我们不妨使用Go来进行web服务器的搭建 前言 使用Go搭建Web服务器的包有