手把手带你写一个 Web 框架
叶剑峰
腾讯高级工程师,前滴滴技术专家
22731 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 42 讲
特别放送 (1讲)
手把手带你写一个 Web 框架
15
15
1.0x
00:00/00:00
登录|注册

01|net/http:使用标准库搭建Server并不是那么简单

思考题
小结
创建框架的Server结构
net/http 标准库怎么学
一定要用标准库吗
Web Server 的本质
net/http 标准库搭建Server

该思维导图由 AI 生成,仅供参考

你好,我是轩脉刃。欢迎加入我的课程,和我一起从 0 开始构建 Web 框架。
之前我简单介绍了整个课程的设计思路,也是搭建 Web 框架的学习路径,我们会先基于标准库搭建起 Server,然后一步一步增加控制器、路由、中间件,最后完善封装和重启,在整章学完后,你就能建立起一套自己的 Web 框架了。
其实你熟悉 Golang 的话就会知道,用官方提供的 net/http 标准库搭建一个 Web Server,是一件非常简单的事。我在面试的时候也发现,不少同学,在怎么搭怎么用的问题上,回答的非常溜,但是再追问一句为什么这个 Server 这么设计,涉及的 net/http 实现原理是什么? 一概不知。
这其实是非常危险的。实际工作中,我们会因为不了解底层原理,想当然的认为它的使用方式,直接导致在代码编写、应用调优的时候出现各种问题。
所以今天,我想带着你从最底层的 HTTP 协议开始,搞清楚 Web Server 本质,通过 net/http 代码库梳理 HTTP 服务的主流程脉络,先知其所以然,再搭建框架的 Server 结构。
之后,我们会基于今天分析的整个 HTTP 服务主流程原理继续开发。所以这节课你掌握的程度,对于后续内容的理解至关重要。

Web Server 的本质

确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了使用Golang中net/http标准库搭建Web Server的复杂性和重要性。作者首先解释了Web Server的本质,并深入讲解了HTTP协议的特点和工作原理。文章讨论了是否一定要使用标准库net/http,并指出了自行封装HTTP库的可能性和一些大厂的做法。强调了学习net/http标准库的重要性,并提出了快速掌握代码库的技巧。通过对库方法和结构体的分析,读者能够快速了解整个库的结构和功能。文章总结了构建HTTP客户端和服务端的功能,以及HTTP协议的各个部分的具体数据结构负责。这篇文章对于深入理解net/http标准库和避免底层原理导致问题的危险性具有重要意义。文章内容涉及技术性较强,适合对Golang Web开发感兴趣的读者阅读学习。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《手把手带你写一个 Web 框架》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(29)

  • 最新
  • 精选
  • 程旭
    1. http.FileServer 创建 FileHandler 数据结构 2. FileHandler 结构体中包含 FileSystem 接口,FileSystem 接口包含Open 方法 3. http.Dir 的 Open 方法 实现 FileSystem 接口 的 Open 方法 4. http.Dir 的 Open 方法对表示字符串的文件路径进行判断: (1)先判断 分隔符是否为 "/"且该字符串中是否包含分隔符,若不满足 返回 nil 和 error信息 "http: invalid character in file path" (2)将 http.Dir 从 Dir 类型转换为 string 类型,判断该是否为空,若为空,将 dir 赋值为 "." (3)使用 path.Clean ,filepath.FromSlash 和 filepath.Join 方法获得路径全名 (4)使用 os.Open 方法打开文件,如果打开失败,返回错误信息,如果成功以读模式打开文件

    作者回复: 你好,你的逻辑是正确的,不过可能过多关注分支细节。在使用思维导图的时候,如果对于比较复杂的逻辑,我们需要分析哪些是关键节点,哪些是非关键节点。 比如FileServer, 其关键点有两个: 1 fileHandler 我们能和ListenAndServe 连接起来,它提供了ServeHTTP的方法, 这个是请求处理的入口函数 2 FileServer 最本质的函数是封装了io.CopyN,基本逻辑是: 如果是读取文件夹,则遍历文件夹内所有文件,将文件名直接输出返回值。 如果是读取文件,则设置文件的阅读指针(如果需要多次读取文件,创建goroutine,且为每个goroutine创建阅读指针),使用io.CopyN读取文件内容输出返回值。

    2021-09-18
    3
    22
  • return
    很赞, 读源码的 思路和 思维导图 很值得学习

    作者回复: 恩,我平时阅读源码就是这样阅读的。经验之谈。感谢支持。

    2021-09-14
    16
  • 好家庭
    老师,请问 go c.serve(connCtx) 里面为什么还有一个循环?c值得是一个connection,我理解不是每个连接处理一次就好了吗,为啥还有一个for循环呢? ··· / 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()) defer func() { if err := recover(); err != nil && err != ErrAbortHandler { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf) } if !c.hijacked() { c.close() c.setState(c.rwc, StateClosed) } }() ... for { w, err := c.readRequest(ctx) if c.r.remain != c.server.initialReadLimitSize() { // If we read any bytes off the wire, we're active. c.setState(c.rwc, StateActive) } if err != nil { const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n" ... } ... ```

    作者回复: http服务在启动的时候,会默认开启keep-alive机制。keep-alive机制就是一个连接在一个请求结束之后,并不关闭当前连接,在下个请求的时候也能使用这个连接。这个for循环就是为keep-alive机制服务的。在服务一个连接的时候,处理完一个请求,并不关闭这个连接,而是循环等待下个请求。 那要关闭keep-alive怎么办呢?你也可以在for循环中的w.conn.server.doKeepAlives 看到,它判断如果服务端的 disableKeepAlives 不是0,则设置了关闭keep-alive,则就不进行for循环了。

    2021-09-16
    9
  • Middleware
    目录有点不清晰,从零开始,那么是不是应该给出建立合适的文件目录结构,命名。我们也能跟着上手敲一遍。比如这个 framework .目录是如何命名。希望老师真的能手把手

    作者回复: 你好,在后面有一章会专门介绍目录的章节,叫《如何系统设计框架的整体目录》。每一个章节,我都有存放源码在github项目的对应分支,如果希望跟着上手敲一遍,可以跟着文章敲完代码,跟着对应分支对一遍。https://github.com/gohade/coredemo/tree/geekbang/01 谢谢支持,另外为你的ID middleware 点个赞。

    2021-09-14
    3
    9
  • Groot
    一篇文章值回票价,感觉后续的文章都是在做慈善 😂 受益匪浅,感谢分享 👍

    作者回复: 你好,非常感谢支持。非常高兴本篇能让你有一些收获。后续文章是介绍写框架的过程的。感谢。

    2021-09-18
    3
    4
  • ghostwritten
    打卡第二天: https://github.com/gohade/coredemo/blob/geekbang/01/go.mod https://datatracker.ietf.org/doc/html/rfc2616 https://github.com/valyala/fasthttp https://pkg.go.dev/net/http@go1.15.5 Web Server 第一个go架构:net/http 熟悉库技巧:库函数 > 结构定义 > 结构函数 查看库命令: go doc net/http | grep "^func go doc net/http | grep "^type"|grep struct 结构函数如下: // 创建一个Foo路由和处理函数 http.Handle("/foo", fooHandler) // 创建一个bar路由和处理函数 http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) }) // 监听8080端口 log.Fatal(http.ListenAndServe(":8080", nil)) 画源代码分析图,学会脑图构思是关键(略) 流程: - 第一层,标准库创建 HTTP 服务是通过创建一个 Server 数据结构完成的; - 第二层,Server 数据结构在 for 循环中不断监听每一个连接; - 第三层,每个连接默认开启一个 Goroutine 为其服务; - 第四、五层,serverHandler 结构代表请求对应的处理逻辑,并且通过这个结构进行具体业务逻辑处理; - 第六层,Server 数据结构如果没有设置处理函数 Handler,默认使用 `DefaultServerMux` 处理请求; - 第七层,`DefaultServerMux` 是使用 map 结构来存储和查找路由规则。 创建框架的 Server 结构 1.创建一个 coredemo/framework/core.go,实现具体业务逻辑 // 框架核心结构 type Core struct { } // 初始化框架核心结构 func NewCore() *Core { return &Core{} } // 框架核心结构实现Handler接口 func (c *Core) ServeHTTP(response http.ResponseWriter, request *http.Request) { // TODO } 2.创建一个 coredemo/main.go,创建服务的方法 `ListenAndServe` 先定义了监听信息 `net.Listen`,然后调用 `Serve` 函数,实现对外提供服务

    作者回复: 👍坚持一起仗剑走天涯

    2021-09-22
    3
  • 布丁老厮
    老师,HTTP 库 Server 的代码流程是不是应该为:创建服务 -> 监听请求 -> 创建连接 -> 处理请求 要更准确一点?因为net.Listen是在srv.newConn之前进行的。

    作者回复: 是的,确实是顺序错误,已经联系编辑进行修改了。感谢提醒。

    2021-09-17
    3
  • 逗逼章鱼
    FileServer 的主要流程前面五层应该都一样,第六层开始不一样,FileServer --> fileHandler --> fileHandler里实现的 ServeHTTP --> serveFile 。

    作者回复: 是的,主要在于第六层的ServeHTTP,不同的handler实现的ServeHTTP是不一样的。

    2021-09-15
    2
  • Ppppppp
    前两天再看这一块儿,画了好几个”蜘蛛图“,人都看蒙了。感谢感谢!!

    作者回复: 感谢

    2023-02-14归属地:江苏
    1
  • Geek_8ed998
    老师为啥你代码里mani.go里面import 是"coredemo/framework" ,而我这里按你一样的目录结构要写成"./framework"才能找到包

    作者回复: 这个是gomod的机制,如果你的项目在gomod中命名为coredemo, 那么coredemo/framework就会去你的项目下的framework目录查询,当然你这里也能写成./framework。但是别人用到你的库的时候就会出现错误

    2021-12-09
收起评论
显示
设置
留言
29
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部