Go Web Programming Notes. To Be Updated…

1. 快速开始
HTTP 请求的 URL 格式:
2. HttpRouter
相关参考:
- 08.3. REST - Go Web 编程 | Learnku
- httprouter - julienschmidt | Github
- build-web-application-with-golang - astaxie | Github
3. http 包建立 Web 服务器
参见 3.2 Go 搭建一个 Web 服务器 - build-web-application-with-golang | Github
GET
请求:
响应:
服务端输出:
4. Go 如何使得 Web 工作
4.1 基本概念
先理清几个基本概念:
- Request:用户请求的信息,用来解析用户的请求信息,包括
POST
、GET
、Cookie
、URL
等信息 - Response:服务器需要反馈给客户端的信息
- Conn:用户每次的请求连接
- Handler:处理请求和生成返回信息的处理逻辑
下图是 Go 实现 Web 服务的工作模式流程图:

4.2 HTTP 包的执行流程
HTTP 包的执行流程:
- 创建
Listen Socket
,监听指定端口,等待客户端请求的到来 Listen Socket
接收客户端的请求,得到Client Socket
,接下来通过Client Socket
与客户端通信- 处理客户端的请求,首先从
Client Socket
读取 HTTP 请求的协议头,如果是POST
方法,还可能要读取客户端提交的数据,然后交给相应的handler
处理请求。处理完毕后,handler
会准备好客户端需要的数据,通过Client Socket
写给客户端
对于上述过程,要想了解 Go 是如何让 Web 运行起来的,需要搞清楚以下三点:
- 如何监听端口?
- 如何接收客户端请求?
- 如何分配 handler?
4.3 如何监听端口?
Go Version:
1.12.6
在之前的代码中可以看到,监听端口的实现是在http.ListenAndServe()
函数中:
该函数首先会初始化一个Server
对象,之后调用该对象的同名方法:
Server
结构体的ListenAndServe()
方法又调用了net.Listen("tcp", addr)
,也就是底层用 TCP 协议搭建了一个服务,开始监听指定的端口:
4.4 如何接收客户端请求?
监听端口之后,上述代码最后又调用了srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
作为返回值,该函数的作用就是接收并处理客户端的请求信息。该函数的具体实现如下:
省略部分代码,重点关注其中的for{}
循环:
l.Accept()
:接收请求,并处理可能出现的错误srv.newConn(rw)
:创建一个新的连接Conn
go c.serve(ctx)
:为新连接单独开一个goroutine
,把请求的数据当作参数扔给这个Conn
去服务
4.5 如何分配 handler?
那么如何具体分配到相应的函数来处理请求呢?可以看到,在上面的代码中,最后实际调用go c.serve(ctx)
处理请求,该函数的实现代码较长,仅截取重要语句如下:
c.readRequest(ctx)
:解析请求,获取对应的ResponseWriter
及Request
serverHandler.ServeHTTP(w, w.req)
:进一步处理请求
结构体serverHandler
的ServeHTTP()
方法具体实现如下:
首先通过handler := sh.srv.Handler
获取对应的Handler
,也就是最开始调用ListenAndServe()
时传入的第二个参数。实际上Handler
是一个接口类型,只定义了一个方法ServeHTTP()
:
例如之前传入的参数是nil
,就会使用默认的DefaultServeMux
。该变量是一个路由器(或者说,HTTP 请求多路复用器),用来匹配 URL 并跳转到其相应的 handle 函数,它在 Go 源码的server.go
中定义:
最开始在main()
函数中调用http.HandleFunc("/", sayHelloName)
时,就注册了请求/
的路由规则:
这样一来当请求的 URI 为/
时,路由就会跳转到/
对应的Handler
,也就是sayHelloName()
本身,最后把结果写入 Response 并反馈给客户端。
4.6 HTTP 连接的处理流程
一个 HTTP 连接的处理流程示意图如下:

5. Go 的 http 包详解
参见 3.4 Go 的 http 包详解 - build-web-application-with-golang | Github
Go 的http
包有两个核心功能:Conn、ServeMux。
5.1 Conn 的 goroutine
与其他一些语言编写的 HTTP 服务器不同,Go 为了实现高并发和高性能,使用了goroutine
来处理Conn
的读写事件,这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件,这是 Go 高效的保证。
Go 在等待客户端请求的Serve()
函数里是这样写的:
可以看到客户端的每次请求都会创建一个Conn
,函数newConn()
在server.go
中的实现如下:
可以看到Conn
实际上在net
包中定义,是一个接口类型,在net.go
中的定义如下:
客户端的每次请求都会创建一个
Conn
,这个Conn
里面保存了该次请求的信息,然后再传递到对应的handler
,该handler
中便可以读取到相应的 Header 信息,这样就保证了每个请求的独立性
5.2 ServeMux 的自定义
之前调用http.ListenAndServe(":8080", nil)
时,实际上内部时调用了http
包默认的路由器DefaultServeMux
,通过路由器把本次请求的信息传递到了后端的处理函数,它是一个ServeMux
类型的变量:
结构体ServeMux
就是 Go 中的路由器,它在server.go
中的定义如下:
Handler
是一个接口,但是之前示例代码中的sayHelloName()
函数并没有实现ServeHTTP()
这个方法,为什么能作为 Handler 添加到路由器中呢?
这是因为在http
包中还定义了一个类型HandlerFunc
,回顾一下之前设置访问路由的语句:
这里我们调用了HandleFunc()
将sayHelloName()
设置为"/"
路由对应的Handler
,而HandleFunc()
实际进行的操作如下:
可以看到这里将handler
转换为了HandlerFunc
,而它默认实现了ServeHTTP()
方法,即我们调用了HandlerFunc(f)
,将f
强制类型转换为HandlerFunc
类型,这样f
就拥有了ServeHTTP()
方法:
这也是适配器模式在 Go 中的应用
路由器里存储好了相应的路由规则,那么具体的请求又是怎样分发的呢?实际上,默认的路由器ServeMux
实现了ServeHTTP()
方法:
如上所示,路由器接收到请求之后,如果是*
则关闭连接,否则会调用mux.Handler(r)
返回对应设置路由的处理 handler,然后执行h.ServeHTTP(w, r)
,也就是调用对应路由的 handler 的ServeHTTP
接口。
继续来看mux.Handler(r)
是如何处理的:
可以看到在mux.handler()
中是调用mux.match()
进行匹配的,函数定义如下:
这样一来就清楚了,在match()
方法中,会根据mux.m[path]
获取请求路径对应的muxEntry
,返回muxEntry
中保存的Handler
以及pattern
字符串,最后调用Handler
的ServeHTTP()
方法就可以执行相应的函数了。
5.3 外部实现自定义路由
通过上面的介绍,我们大致了解了 Go 的整个路由过程。除了默认路由器DefaultServeMux
,Go 同时也支持外部实现的路由器。
http.ListenAndServe()
方法的第二个参数就是用来配置外部路由器的,它是一个Handler
接口,即外部路由器只要实现了Handler
接口的ServeHTTP()
方法,就可以在自己实现的路由器的ServeHTTP()
中实现自定义路由功能。
如下所示,实现一个简单的外部路由器MyMux
:
请求报文:
响应报文:
5.4 Go 代码的执行流程
Go Version:
1.12.6
分析完http
包后,现在梳理一下代码的执行过程。例如下面这段代码:
首先调用http.HandleFunc()
:
- 调用
DefaultServeMux.HandleFunc()
- 调用
DefaultServeMux.Handle()
,注册请求路径所对应的 handler - 在
DefaultServeMux
的map[string]muxEntry
中增加对应的 handler 和路由规则
之后调用http.ListenAndServe(":8080", nil)
:
- 实例化
Server
- 调用
server.ListenAndServe()
- 调用
net.Listen("tcp", addr)
监听端口,即Listen
- 调用
srv.Serve()
处理请求,即Serve
- 在
Serve()
中启动一个 for 循环,在循环中调用Accept()
接收请求 - 为每个请求实例化一个
Conn
,开启一个 goroutine 并调用go c.serve(ctx)
,为这个请求进行服务 - 调用
c.readRequest(ctx)
读取每个请求内容 - 判断 handler 是否为空,如果为
nil
则设为DefaultServeMux
- 调用
handler.ServeHTTP(rw, req)
,上面的例子中就进入到DefaultServeMux.ServeHTTP(rw, req)
- 根据 Request 选择 handler,并进入到这个 handler 的
ServeHTTP()
中
选择 handler:
- 循环遍历
ServeMux
的muxEntry
,判断是否有路由能满足这个 Request - 如果有路由满足,则调用这个路由 handler 的
ServeHTTP()
- 如果没有路由满足,则调用
NotFoundHandler
的ServeHTTP()
6. 表单
6.1 处理表单的输入
例如下面的表单login.gtpl
:
处理表单:
request.Form
是一个url.Values
类型,里面存储了key=value
的信息:
7. 访问数据库
8. Session 和数据存储
9. 文本文件处理
9.1 XML 处理
参考资料
文章教程
- Go Web Programming - sausheong | Github
- 08.3. REST - Go Web 编程 | Learnku
- httprouter - julienschmidt | Github
- build-web-application-with-golang - astaxie | Github
- Golang: Building a Basic Web Server in Go | Ruan Bekker’s Blog
- project-layout - Standard Go Project Layout | Github
- Go Developer Roadmap - Go 开发者路线图 | Github
- 明白了,原来 Go Web 框架中的中间件都是这样实现的 | 鸟窝
- Go 语言的修饰器编程 | 酷壳 CoolShell
- 教程:使用 go 的 gin 和 gorm 框架来构建 RESTful API 微服务 | LearnKu
- Build RESTful API service in golang using gin-gonic framework | Medium
RESTful
- RESTful API 设计指南 | 阮一峰
- RESTful API 最佳实践 | 阮一峰
- RESTful API 规范 | RyuGou
- 如何给老婆解释什么是Restful | Java3y
- 对比 RESTful 与 SOAP,深入理解 RESTful | 紫川秀的博客
- RESTful API 设计规范 | 紫川秀的博客
- 如何使用 swagger 设计出漂亮的 RESTful API | 紫川秀的博客
- Go 学习笔记 (六) - 使用 swaggo 自动生成 Restful API 文档 | Razeen’s Blog
Mock API
RPC
数据加密
- 密码学简介与 Golang 的加密库 Crypto 的使用 | 紫川秀的博客
- 加密和解密数据 - Go Web 编程 | LearnKu
- 常见的加密算法 | Go 语言中文网
- Golang 常用加密解密算法总结 (AES、DES、RSA、Sha1MD5) | CSDN