10 | 基于 Gin 实现 HTTP 服务器
提示:
- 所有体系课见专栏:Go 项目开发极速入门实战课;
- 欢迎加入 云原生 AI 实战 星球,12+ 高质量体系课、20+ 高质量实战项目助你在 AI 时代建立技术竞争力(聚焦于 Go、云原生、AI Infra);
- 本节课最终源码位于 fastgo 项目的 feature/s07 分支;
- 更详细的课程版本见:Go 项目开发中级实战课:20 | Web 服务器实现:如何基于 Gin 实现一个 HTTP 服务器?
在 Go 项目开发中,开发场景最多的是开发一个 Web 服务器,Web 服务器种类有很多,例如:HTTP 服务器、RPC 服务器、WebSocket 服务器等。
其中,HTTP 服务器是最常需要开发的服务器类型。HTTP 服务器,其实就是一个对外提供 API 接口的 Web 服务器。API 接口其实是有规范的,当前用的最多的 REST 规范。
所以,本节课,就来看下如何快速开发一个 REST API 服务器。
REST Web 框架选择
要编写一个 RESTful 风格的 API 服务器,你可以自己调用 net/http
包手动实现一个,但这样耗时而且效果也不好。所以企业开发中,通常会使用 Web 框架来开发一个 REST 服务器。
Web 框架有很多,例如:Gin、Hertz、Echo、Eiber 等。当前使用最多的是 Gin 框架。Gin 框架很轻量,并且具有以下优点:高性能、扩展性强、稳定性强、相对而言比较简洁(查看 性能对比)。关于 Gin 的更多介绍可以参考 Gin 官网。
开发一个简单的 REST 服务器
使用 Gin 框架开发一个 REST 服务器分为以下几步:
- 配置 REST 服务器(配置监听端口);
- 创建 Gin 引擎;
- 设置 Gin 路由;
- 启动 Gin 服务器。
步骤 1:配置 REST 服务器
修改 cmd/fg-apiserver/app/options/options.go 文件,给 ServerOptions
结构体添加 Addr 配置项。代码变更如下:
package optionsimport ("fmt""net""strconv"...
)type ServerOptions struct {...Addr string `json:"addr" mapstructure:"addr"`
}// NewServerOptions 创建带有默认值的 ServerOptions 实例.
func NewServerOptions() *ServerOptions {return &ServerOptions{...Addr: "0.0.0.0:6666",}
}// Validate 校验 ServerOptions 中的选项是否合法.
// 提示:Validate 方法中的具体校验逻辑可以由 Claude、DeepSeek、GPT 等 LLM 自动生成。
func (o *ServerOptions) Validate() error {...// 验证服务器地址if o.Addr == "" {return fmt.Errorf("server address cannot be empty")}// 检查地址格式是否为host:port_, portStr, err := net.SplitHostPort(o.Addr)if err != nil {return fmt.Errorf("invalid server address format '%s': %w", o.Addr, err)}// 验证端口是否为数字且在有效范围内port, err := strconv.Atoi(portStr)if err != nil || port < 1 || port > 65535 {return fmt.Errorf("invalid server port: %s", portStr)}return nil
}// Config 基于 ServerOptions 构建 apiserver.Config.
func (o *ServerOptions) Config() (*apiserver.Config, error) {return &apiserver.Config{...Addr: o.Addr,}, nil
}
上面的代码给 ServerOptions
结构体添加了 Addr
字段,用来保存 Web 服务器的监听地址,默认地址设置为:0.0.0.0:6666
。
在 Validate方法中,添加了对 Addr
字段的校验,检查了 Addr
字段的格式是否合法,端口的范围是否正确。在实际开发中,你可以根据实际需要添加更多的校验。
在 Config 方法中,需要将应用的 Addr
配置字段赋值给运行时配置。所以还要在运行时配置中添加 Addr
字段。修改 internal/apiserver/server.go 文件中的 Config 结构体添加 Addr
字段:
type Config struct {...Addr string
}
步骤 2:创建 Gin 引擎
修改 internal/apiserver/server.go 文件,在 Server 结构体中新增 *http.Server
类型的字段 srv
,并在 NewServer方法中实例化 srv
,变更代码如代码清单 10-1 所示:
代码清单 10-1
package apiserverimport (..."net/http"..."github.com/gin-gonic/gin"...
)
...
// Server 定义一个服务器结构体类型.
type Server struct {...srv *http.Server
}// NewServer 根据配置创建服务器.
func (cfg *Config) NewServer() (*Server, error) {// 创建 Gin 引擎engine := gin.New()// 注册 404 Handler.engine.NoRoute(func(c *gin.Context) {c.JSON(http.StatusNotFound, gin.H{"code": "PageNotFound", "message": "Page not found."})})// 注册 /healthz handler.engine.GET("/healthz", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"status": "ok"})})// 创建 HTTP Server 实例httpsrv := &http.Server{Addr: cfg.Addr, Handler: engine}return &Server{cfg: cfg, srv: httpsrv}, nil
}
在 NewServer
方法中,通过 gin.New()
创建了一个 Gin 引擎实例 engine
。并通过 engine
中的方法给 engine
设置了 REST 路由和中间件。
代码中的 engine.NoRoute
方法调用,设置了当 Gin 找不到匹配的请求路径时返回的信息:
{"code":"PageNotFound","message":"Page not found."}
NewServer
方法的最后部分,创建了标准库的 http.Server
实例,并将配置好的 Gin 引擎设置为其Handler,随后将配置对象和 HTTP 服务器实例注入到新创建的 Server
结构体中并返回。
步骤 3:设置 Gin 路由
有时候服务器进程起来不代表服务器可以正常对外提供 API,我就曾经就遇到过这种问题:服务器进程存在,但是访问 API 确实失败的。
因此在启动服务器时,最好加一个健康检查接口。可以在健康检查接口中,检查任何我们觉得会影响服务器健康状态的项目。
在代码清单 10-1 中,通过以下方法调用给 engine
实例添加了一条健康检查 HTTP 路由:
// 注册 /healthz handler.engine.GET("/healthz", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"status": "ok"})})
engine
实例提供了 GET
、POST
、DELETE
、PATCH
、PUT
、OPTIONS
、HEAD
、Any
方法,来给 engine
实例添加对应的 HTTP 路由。
上述代码添加了一条 HTTP 路由:
- 请求方法:
GET
; - 请求路径:
/healthz
; - 请求返回:
{"status":"ok"}
。
步骤 4:启动 Gin 服务器
在 NewServer
方法中创建了 Server
类型的实例,接下来就可以在 Server
实例的 Run 方法中启动 HTTP 服务器。代码如下:
// Run 运行应用.
func (s *Server) Run() error {// 运行 HTTP 服务器// 打印一条日志,用来提示 HTTP 服务已经起来,方便排障slog.Info("Start to listening the incoming requests on http address", "addr", s.cfg.Addr)if err := s.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {return err}return nil
}
上述代码,打印了一条日志,日志中输出了 HTTP 服务器的请求端口。将请求端口打印出来,可以用来帮助开发者了解 HTTP 的启动配置。
s.srv.ListenAndServe()
方法用来启动 HTTP 服务器。当该方法返回错误时,报错并退出。这里要注意,上述代码处理了一个特殊的错误情况:http.ErrServerClosed
。当我们主动调用 http.Server
的 Shutdown()
或 Close()
方法时,ListenAndServe()
会返回这个特定的错误(http.ErrServerClosed
),这表示服务器是被预期地、主动地关闭,而非意外崩溃。因此,这种情况不应被视作异常或错误,不需要额外处理或返回。
编译并运行
执行以下命令编译并运行:
$ ./build.sh
$ _output/fg-apiserver -c configs/fg-apiserver.yaml
打开一个新的 Linux 终端,执行以下命令测试功能是否正常:
$ curl http://127.0.0.1:6666/nonexist # 请求路径不存在时,返回预期错误
{"code":"PageNotFound","message":"Page not found."}
$ curl http://127.0.0.1:6666/healthz # 请求健康检查接口,返回服务器 ok 信息
{"status":"ok"}
附录: cURL 工具介绍
本节课采用 cURL 工具来测试 RESTful API,标准的 Linux 发行版都安装了 cURL 工具。cURL 可以很方便地完成对 REST API 的调用场景,比如:设置 Header,指定 HTTP 请求方法,指定 HTTP 消息体,指定权限认证信息等。
通过 -v
选项也能输出 REST 请求的所有返回信息。cURL 功能很强大,有很多参数,这里列出 REST 测试常用的参数:
-X/--request [GET|POST|PUT|DELETE|…] 指定请求的 HTTP 方法
-H/--header 指定请求的 HTTP Header
-d/--data 指定请求的 HTTP 消息体(Body)
-v/--verbose 输出详细的返回信息
-u/--user 指定账号、密码
-b/--cookie 读取 cookie