refactor: use nginx -s reload instead of systemd
- doc: add comment
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
// Package daemonserver accepts local CLI requests over the frozen Unix Socket.
|
||||
// Package daemonserver 通过固定的本地 Unix Socket 接受命令行客户端请求,并将其分派给事务编排器执行。
|
||||
// 服务端负责监听并校验请求、把请求映射到后端更新或重启动作、将执行进度流式写回客户端,
|
||||
// 最终返回携带事务终态与错误信息的结果。
|
||||
package daemonserver
|
||||
|
||||
import (
|
||||
@@ -20,22 +22,36 @@ import (
|
||||
"yms-daemon/internal/transaction"
|
||||
)
|
||||
|
||||
// maximumRequestBytes 单次请求体的最大字节数,超过该上限的请求会被拒绝,以防内存被异常输入耗尽。
|
||||
const maximumRequestBytes = 1 << 20
|
||||
|
||||
// backendUpdater 抽象了后端更新与重启的具体实现,是 Server 与事务编排器之间的解耦接口。
|
||||
// 它由 backendupdate 包在运行时注入,便于在测试中用轻量替身替换。
|
||||
type backendUpdater interface {
|
||||
// UpdateRepack 使用重新打包的 ZIP 制品执行后端更新,file 为制品绝对路径。
|
||||
UpdateRepack(context.Context, string, backendupdate.ProgressReporter) (transaction.Transaction, error)
|
||||
// UpdateNativeJAR 使用原生后端 JAR 文件执行后端更新,file 为制品绝对路径。
|
||||
UpdateNativeJAR(context.Context, string, backendupdate.ProgressReporter) (transaction.Transaction, error)
|
||||
// UpdateContainerImage 使用容器镜像执行后端更新,imageReference 为镜像引用,startLog 控制是否输出启动日志。
|
||||
UpdateContainerImage(context.Context, string, bool, backendupdate.ProgressReporter) (transaction.Transaction, error)
|
||||
// Restart 重启后端服务而不更换制品。
|
||||
Restart(context.Context, backendupdate.ProgressReporter) (transaction.Transaction, error)
|
||||
}
|
||||
|
||||
// Server owns the local Unix Socket and dispatches requests to the transaction orchestrator.
|
||||
// Server 持有本地 Unix Socket 并把收到的请求分派给事务编排器。
|
||||
// 它负责连接的生命周期管理、请求解码与校验、执行进度回写以及最终结果的返回。
|
||||
type Server struct {
|
||||
// socketPath 守护进程监听的 Unix Socket 绝对路径。
|
||||
socketPath string
|
||||
updater backendUpdater
|
||||
logger *slog.Logger
|
||||
// updater 后端更新与重启动作的实际执行者。
|
||||
updater backendUpdater
|
||||
// logger 用于记录服务运行与请求处理过程中的日志。
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// New 构造一个 Server 实例并校验入参。
|
||||
// socketPath 必须是规范化的绝对路径,updater 不能为空,logger 为空时回退为默认日志器。
|
||||
// 当路径非法或 updater 为空时返回 error。
|
||||
func New(socketPath string, updater backendUpdater, logger *slog.Logger) (*Server, error) {
|
||||
if !filepath.IsAbs(socketPath) || filepath.Clean(socketPath) != socketPath {
|
||||
return nil, errors.New("daemon Unix Socket path must be a clean absolute path")
|
||||
@@ -49,7 +65,10 @@ func New(socketPath string, updater backendUpdater, logger *slog.Logger) (*Serve
|
||||
return &Server{socketPath: socketPath, updater: updater, logger: logger}, nil
|
||||
}
|
||||
|
||||
// Serve listens until ctx is canceled. Each accepted request owns its response connection.
|
||||
// Serve 监听 Unix Socket 直到 ctx 被取消,返回前会等待所有连接处理完成并清理 Socket 文件。
|
||||
// 它先准备 Socket 目录并移除残留的旧 Socket,然后以 0o600 权限监听;
|
||||
// 每个被接受的连接都由独立的 goroutine 处理,连接在响应写完后由 handle 关闭。
|
||||
// 当监听或准备 Socket 路径失败时返回 error,ctx 取消后正常返回 nil。
|
||||
func (s *Server) Serve(ctx context.Context) error {
|
||||
if err := prepareSocketPath(s.socketPath); err != nil {
|
||||
return err
|
||||
@@ -90,6 +109,11 @@ func (s *Server) Serve(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// handle 处理单个已接受的连接:读取并校验请求、执行对应操作、把进度与结果写回客户端。
|
||||
// 它限制请求读取时限为 10 秒,仅接受 service 为 backend 的请求;
|
||||
// 对于 update 操作会按 inputType 分派到不同更新实现,对 restart 操作校验其不携带多余输入。
|
||||
// 执行期间产生的进度通过 report 回调实时写回,一旦写回失败便停止后续进度发送,
|
||||
// 最终无论成功失败都会写入一条 ResponseResult 响应并记录日志。
|
||||
func (s *Server) handle(ctx context.Context, connection net.Conn) {
|
||||
defer connection.Close()
|
||||
_ = connection.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
@@ -168,10 +192,14 @@ func (s *Server) handle(ctx context.Context, connection net.Conn) {
|
||||
}
|
||||
}
|
||||
|
||||
// writeResponse 把一条响应以换行分隔的 JSON 编码写入连接,失败时返回底层写入错误。
|
||||
func (s *Server) writeResponse(connection net.Conn, response daemonapi.Response) error {
|
||||
return json.NewEncoder(connection).Encode(response)
|
||||
}
|
||||
|
||||
// decodeRequest 从 reader 中解码并校验一条客户端请求。
|
||||
// 它限制请求体大小不超过 maximumRequestBytes,禁止未知字段,并要求请求只能包含一个 JSON 值,
|
||||
// 且所有字符串字段不得带有前后空白字符;任一条不满足时返回 error。
|
||||
func decodeRequest(reader io.Reader) (daemonapi.Request, error) {
|
||||
limited := io.LimitReader(reader, maximumRequestBytes+1)
|
||||
decoder := json.NewDecoder(limited)
|
||||
@@ -193,6 +221,8 @@ func decodeRequest(reader io.Reader) (daemonapi.Request, error) {
|
||||
return request, nil
|
||||
}
|
||||
|
||||
// prepareSocketPath 在监听前准备 Socket 路径:创建父目录,若目标位置已被占用则校验其是否为 Socket,
|
||||
// 若是残留 Socket 则移除,若被非 Socket 文件占用则返回 error。
|
||||
func prepareSocketPath(socketPath string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(socketPath), 0o755); err != nil {
|
||||
return fmt.Errorf("create daemon Unix Socket directory: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user