7、基于Gin实现HTTP服务器
7、基于Gin实现HTTP服务器
在go项目开发中,开发场景很多的是开发一个web服务器、web服务器种类有很多,例如:Http服务器、RPC服务器、webSocket服务器等。
其中Http服务器是最常需要开发的服务器类型。HTTP就是一个对外提供API接口的Web服务器。API接口其实是有规范的,当前用的最多的REST规范。
REST Web框架选择
要编写一个RESTful
风格的API服务器,你可以自己调用 net/http 包手动实现一个,但这样耗时且效果不好,所以通常会使用web框架来开发一个REST服务器
Web框架有很多,例如:Gin、Hertz、Echo、Eiber等。用的最多的是Gin,查看性能对比
开发一个简单的REST服务器
使用Gin框架开发一个REST服务器分为以下几步:
- 配置REST服务器(配置监听端口)
- 创建Gin引擎
- 设置Gin路由
- 启动Gin服务器
步骤一:配置REST服务器
修改cmd/fg-apiserver/app/options/options.go 文件,给ServerOptions
结构体添加Addr
配置项。
package options
import (
"fmt"
"gitlab.com/onexstack/fastgo/internal/apiserver"
genericoptions "gitlab.com/onexstack/fastgo/pkg/options"
"log/slog"
"net"
"strconv"
)
type ServerOptions struct {
MySQLOptions *genericoptions.MySQLOption `json:"mysql" mapstructure:"mysql"`
Addr string `json:"addr" mapstructure:"addr"`
}
// NewServerOptions 创建带有默认值的 ServerOptions 实例.
func NewServerOptions() *ServerOptions {
return &ServerOptions{
MySQLOptions: genericoptions.NewMYSQLOption(),
Addr: "0.0.0.0:6666",
}
}
// Validate
//
// @Description: Validate校验ServierOptions参数是否合法
// @receiver s
// @return error
func (s *ServerOptions) Validate() error {
// 验证服务器地址
if s.Addr == "" {
slog.Error(fmt.Sprintf("server address cannot be empty"))
return fmt.Errorf("server address cannot be empty")
}
// 检查地址是否为host:port
_, portStr, err := net.SplitHostPort(s.Addr)
if err != nil {
slog.Error(fmt.Sprintf("invalid server address format '%s': %w", s.Addr, err))
return fmt.Errorf("invalid server address format '%s': %w", s.Addr, err)
}
// 验证端口是否为数字且在有效范围内
port, err := strconv.Atoi(portStr)
if err != nil || port < 1 || port > 65535 {
return fmt.Errorf("invalid server port: %s", portStr)
}
// 校验mysql格式
mysqlErr := s.MySQLOptions.Validate()
if mysqlErr != nil {
slog.Error(mysqlErr.Error())
return mysqlErr
}
return nil
}
// Config
//
// @Description: 基于ServerOptions构建apiserver.Config
// @receiver s
// @return *apiserver.Config
// @return error
func (s *ServerOptions) Config() (*apiserver.Config, error) {
return &apiserver.Config{
MYSQLOptions: s.MySQLOptions,
Addr: s.Addr,
}, nil
}
上面的代码给ServerOptions
结构体添加了Addr
字段,用来保存web服务器的监听地址,默认地址设置为:0.0.0.0:6666
在Validate方法中,添加了对Addr字段的校验。
在Config方法中,需要将应用的Addr
配置字段赋值给运行时陪住,所以还要在运行时配置中添加Addr
字段。修改internal/apiserver/server.go中的Config结构体添加Addr字段
type Config struct {
MYSQLOptions *genericoptions.MySQLOption
Addr string
}
步骤二:创建Gin引擎
修改internal/apiserver/server.go,在Server结构体中增加*http.Server
类型的字段srv,并在NewServer
方法中实例化srv
,
// Server
// @Description: 定义一个服务器结构体类型
type Server struct {
cfg *Config
srv *http.Server
}
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": 404,
"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":404,"message":"Page not Found"}
NewServer
方法的最后部分,创建了标准库的http.Server
实例,并将配制好的Gin引擎设置为其Handler,随后将配置对象和HTTP服务器注入到新创建的Server结构体中并返回。
步骤三:设置Gin路由
有时候服务器进程起来不代表服务器可以正常对外提供API,因此在启动服务器时最好加一个健康检查接口。
// 注册/healthz handler
engine.GET("/healthz", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "ok",
})
})
步骤四:启动Gin服务器
在NewServer
方法中创建了Server的实例,接下来就可以在Server实例的Run方法中启动HTTP服务器
func (s *Server) Run() error {
slog.Info(fmt.Sprintf("Read MYSQL host from config %s", s.cfg.MYSQLOptions.Addr))
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
}
s.srv.ListenAndServe()
方法用来启动HTTP服务器。当该方法返回错误时,报错并退出。这里要注意,上面代码处理了一个特殊的错误情况:http.ErrServerClosed
。当主动调用http.Server
的Shutdown()
或Close()
方法时,ListenAndServe()
会返回这个特定的错误http.ErrServerClosed
,标表示服务器是被预期的、主动的关闭,而非意外崩溃。所以这种情况不应该被视作异常或错误,不需要额外处理或返回
编译并运行
(base) dujie@MacBook-Pro fastgo % sh build.sh
gitlab.com/onexstack/fastgo/internal/apiserver
gitlab.com/onexstack/fastgo/cmd/fg-apiserver/app/options
command-line-arguments
^[[A% (base) dujie@MacBook-Pro fastgo % ./_output/fg-apiserver -c configs/fg-apiserver.yaml
/Users/dujie/.fastgo/fg-apiserver.yaml
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /healthz --> gitlab.com/onexstack/fastgo/internal/apiserver.(*Config).NewServer.func2 (1 handlers)
打开一个新的Linux终端测试
(base) dujie@MacBook-Pro fastgo % curl http://127.0.0.1:6666/nonexist
{"code":404,"message":"Page not Found"}% base) dujie@MacBook-Pro fastgo % curl http://127.0.0.1:6666/healthz
{"status":"ok"}%
curl工具常用参数
-X/--request [GET|POST|PUT|DELETE|…] 指定请求的 HTTP 方法
-H/--header 指定请求的 HTTP Header
-d/--data 指定请求的 HTTP 消息体(Body)
-v/--verbose 输出详细的返回信息
-u/--user 指定账号、密码
-b/--cookie 读取 cookie