hello大家好呀,我是小楼,这是系列文《Go底层原理剖析》的第二篇,依旧是分析 Http 模块,话不多说,开始。

从一个 Demo 入手

俗话说万事开头难,但用 Go 实现一个 Http Server 真不难,简单到什么程度?起一个 Server,并且能响应请求,算上包名、导入的依赖,甚至空行,也就只要 15 行代码:

package main

import (
"io"
"net/http"
) func main() {
http.HandleFunc("/hello", hello)
http.ListenAndServe(":81", nil)
} func hello(response http.ResponseWriter, request *http.Request) {
io.WriteString(response, "hello world")
}

这么简单,能与之一战的恐怕只有 Python 了吧,而且 Go 还能编译成可执行的二进制文件,你说牛啤不牛啤?

Http Server 如何处理连接?

我们从这一行代码看起

http.ListenAndServe(":81", nil)

从命名来看,这个方法干了两件事,监听并且服务,从方法的单一职责上来说,我觉得不ok,一个方法怎么能干两件事?但这是大佬写的代码,就很合理。

第一个参数Addr是要监听的地址和端口,第二个参数Handler一般是nil,它是真正的逻辑处理,但我们通常用第一行代码那样来注册处理器,这代码一看就感觉是把 path 映射到业务逻辑上,我们先大概了解,待会再来看它

http.HandleFunc("/hello", hello)

如果了解过一点网络编程基础,就会知道操作系统提供了bindlistenaccept这样的系统调用,我们只要按顺序发起调用,就能组合出一个 Server。

Go 也是利用这些系统调用,把他们都封装在了ListenAndServe中。

Listen 往下追究就是系统调用,所以我们重点看 Serve

把分支代码收起来,只看主干,发现是一个 for 循环里面在不停地 Accept,而这个 Accept 在没有连接时是阻塞的,当有连接时,起一个新的协程来处理。

Http Server 如何处理请求的?

一些前置工作

处理请求的一行代码是,可以看出是每个连接单开了一个协程处理:

go c.serve(connCtx)

这里的 connCtx 代入了当前的 Server 对象:

ctx := context.WithValue(baseCtx, ServerContextKey, srv)
...
connCtx := ctx

而且还提供了修改它的 hook 方法 srv.ConnContext,可以在每次 Accept 时修改原始的 context

if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}

它的定义是:

// ConnContext optionally specifies a function that modifies
// the context used for a new connection c. The provided ctx
// is derived from the base context and has a ServerContextKey
// value.
ConnContext func(ctx context.Context, c net.Conn) context.Context

但是如果按照我开头给的代码,你是没法修改 srv.ConnContext 的,可以改成这样来自定义:

func main() {
http.HandleFunc("/hello", hello)
server := http.Server{
Addr: ":81",
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
return context.WithValue(ctx, "hello", "roshi")
},
}
server.ListenAndServe()
}

同样的 c.setState 也提供了 hook,可采取如上的方法设置,在每次连接状态改变时执行 hook 方法:

c.setState(c.rwc, StateNew, runHooks) // before Serve can return
// ConnState specifies an optional callback function that is
// called when a client connection changes state. See the
// ConnState type and associated constants for details.
ConnState func(net.Conn, ConnState)

开始真正干活

为了能看清楚 Accept 后,serve 方法到底干了什么,我们再简化一下:

func (c *conn) serve(ctx context.Context) {
...
for {
w, err := c.readRequest(ctx)
...
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
}

serve 也是一个大循环,循环里面主要是读取一个请求,然后将请求交给 Handler 处理。

为什么是一个大循环呢?因为每个 serve 处理的是一个连接,一个连接可以有多次请求。

读请求就显得比较枯燥乏味,按照Http协议,读出URL,header,body等信息。

这里有个细节是在每次读取了一个请求后,还开了一个协程去读下一个请求,也算是做了优化吧。

for {
w, err := c.readRequest(ctx)
... if requestBodyRemains(req.Body) {
registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
} else {
w.conn.r.startBackgroundRead()
}
...
}

请求如何路由?

当读取到一个请求后,便进入这一行代码:

serverHandler{c.server}.ServeHTTP(w, w.req)

ServeHTTP 找到我们注册的 Handler 去处理,如果请求的URI 是 *或请求 Method 是 OPTIONS,则使用globalOptionsHandler,也就是说这类请求不需要我们手动处理,直接就返回了。

对于我们注册的 Handler 也需要去寻找路由,这个路由的规则还是比较简单,主要由如下三条:

  • 如果注册了带 host 的路由,则按 host + path 去寻找,如果没注册带 host 的路由,则按 path 寻找
  • 路由规则匹配以完全匹配优先,如果注册的路由规则最后一个字符是/,则除了完全匹配外,还会以前缀查找

举几个例子来理解一下:

  • 带 host 的匹配规则

注册路由为

http.HandleFunc("/hello", hello)
http.HandleFunc("127.0.0.1/hello", hello2)

此时如果执行

curl 'http://127.0.0.1:81/hello'

则会匹配到 hello2,但如果执行

curl 'http://localhost:81/hello'

就匹配的是 hello

  • 前缀匹配

如果注册路由为

http.HandleFunc("/hello", hello)
http.HandleFunc("127.0.0.1/hello/", hello2)

注意第二个最后还有个/,此时如果执行

curl 'http://127.0.0.1:81/hello/roshi'

也能匹配到 hello2,怎么样,是不是理解了?

找到路由之后就直接调用我们开头注册的方法,如果我们往 Response 中写入数据,就能返回给客户端,这样一个请求就处理完成了。

总结

最后我们回忆下 Go Http Server 的要点:

  • 用 Go 起一个 Http Server 非常简单
  • Go Http Server 本质是一个大循环,每当有一个新连接时,会起一个新的协程来处理
  • 每个连接的处理也是一个大循环,这个循环里做了读取请求、寻找路由、执行逻辑三件大事

感谢能抽空看到这里,如果你能点赞在看分享,我会更加感激不尽~


搜索关注微信公众号"捉虫大师",后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践

一文读懂Go Http Server原理的更多相关文章

  1. 一文读懂Https的安全性原理、数字证书、单项认证、双项认证等

    本文引用了作者Smily(博客:blog.csdn.net/qq_20521573)的文章内容,感谢无私分享. 1.前言 目前苹果公司已经强制iOS应用必须使用HTTPS协议开发(详见<苹果即将 ...

  2. 一篇读懂HTTPS:加密原理、安全逻辑、数字证书等

    1.引言 HTTPS(全称: Hypertext Transfer Protocol Secure,超文本传输安全协议),是以安全为目标的HTTP通道,简单讲是HTTP的安全版.本文,就来深入介绍下其 ...

  3. 一文读懂HTTP/2及HTTP/3特性

    摘要: 学习 HTTP/2 与 HTTP/3. 前言 HTTP/2 相比于 HTTP/1,可以说是大幅度提高了网页的性能,只需要升级到该协议就可以减少很多之前需要做的性能优化工作,当然兼容问题以及如何 ...

  4. 一文读懂高性能网络编程中的I/O模型

    1.前言 随着互联网的发展,面对海量用户高并发业务,传统的阻塞式的服务端架构模式已经无能为力.本文(和下篇<高性能网络编程(六):一文读懂高性能网络编程中的线程模型>)旨在为大家提供有用的 ...

  5. 从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路

    本文原作者阮一峰,作者博客:ruanyifeng.com. 1.引言 HTTP 协议是最重要的互联网基础协议之一,它从最初的仅为浏览网页的目的进化到现在,已经是短连接通信的事实工业标准,最新版本 HT ...

  6. [转帖]从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路

    从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路   http://www.52im.net/thread-1709-1-2.html     本文原作者阮一峰,作者博客:r ...

  7. 即时通讯新手入门:一文读懂什么是Nginx?它能否实现IM的负载均衡?

    本文引用了“蔷薇Nina”的“Nginx 相关介绍(Nginx是什么?能干嘛?)”一文部分内容,感谢作者的无私分享. 1.引言   Nginx(及其衍生产品)是目前被大量使用的服务端反向代理和负载均衡 ...

  8. 一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现

    一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现 导读:近日,马云.马化腾.李彦宏等互联网大佬纷纷亮相2018世界人工智能大会,并登台演讲.关于人工智能的现状与未来,他们提出了各自的观点,也引 ...

  9. kubernetes基础——一文读懂k8s

    容器 容器与虚拟机对比图(左边为容器.右边为虚拟机)   容器技术是虚拟化技术的一种,以Docker为例,Docker利用Linux的LXC(LinuX Containers)技术.CGroup(Co ...

  10. 一文读懂对抗生成学习(Generative Adversarial Nets)[GAN]

    一文读懂对抗生成学习(Generative Adversarial Nets)[GAN] 0x00 推荐论文 https://arxiv.org/pdf/1406.2661.pdf 0x01什么是ga ...

随机推荐

  1. breakout靶机

    breakout:https://www.vulnhub.com/entry/empire-breakout,751/ 开机显示ip也可以不用扫描 首先使用nmap扫描 去访问网页 使用dirb扫描这 ...

  2. java程序员在交接别人的工作时如何保证顺利交接?

    序言 各位好啊,我是会编程的蜗牛,作为java开发者,尤其是在职场混迹了多年的老手,肯定会遇到同事离职的情况,或者自己跳槽的情况,这些都免不了需要做好交接工作,不管是别人交接给我们,还是我们交接给别人 ...

  3. Vitepress搭建组件库文档(上)—— 基本配置

    在 vite 出现以前,vuepress 是搭建组件库文档不错的工具,支持以 Markdown 方式编写文档.伴随着 vite 的发展,vitepress 已经到了 1.0.0-alpha.22 版本 ...

  4. JS中的键盘Keycode

    常用的keycode 组合键 if ((window.event.ctrlKey)&&(window.event.keyCode==49)) alert("您按下了CTRL+ ...

  5. curl 下载地址中有特殊字符解决方案

    curl 下载地址中有特殊字符解决方案 情况 使用 curl 下载 地址中带有 特殊字符的时候 比如下面这个地址.实际访问地址不正确,参数丢失问题 curl -o kspf.jpeg https:// ...

  6. Spring Boot 中使用 tkMapper

    说明:基于 MyBatis 有很多第三方功能插件,这些插件可以完成数据操作方法的封装.数据库逆向工程的生成等. tkMapper 和 MyBatis-plus 都是基于 MyBatis 提供的第三方插 ...

  7. [排序算法] 快速排序 (C++) (含三种写法)

    快速排序解释 快速排序 Quick Sort 与归并排序一样,也是典型的分治法的应用. (如果有对 归并排序还不了解的童鞋,可以看看这里哟~ 归并排序) 快速排序的分治模式 1.选取基准值,获取划分位 ...

  8. 聊聊Go里面的闭包

    以前写 Java 的时候,听到前端同学谈论闭包,觉得甚是新奇,后面自己写了一小段时间 JS,虽只学到皮毛,也大概了解到闭包的概念,现在工作常用语言是 Go,很多优雅的代码中总是有闭包的身影,看来不了解 ...

  9. 我引用中没有Spire.Pdf,但是发现无法解析的“Spire.Pdf”的不同版本之间存在冲突

    问题: 导出错误!未能加载文件或程序集"Spire.Pdf, Version=8.6.1.0, Culture=neutral, PublicKeyToken=663f351905198cb ...

  10. OpenCV之C++经典案例

    四个案例实战 1.刀片缺陷检测 2.自定义对象检测 3.实时二维码检测 4.图像分割与色彩提取 1.刀片缺陷检测 问题分析 解决思路 尝试二值图像分析 模板匹配技术 代码实现 #include < ...