将Unix socket通信改为命名管道并添加flag处理
This commit is contained in:
parent
e9474f0e3f
commit
2f7ca5cefd
|
|
@ -21,3 +21,4 @@
|
||||||
# Go workspace file
|
# Go workspace file
|
||||||
go.work
|
go.work
|
||||||
|
|
||||||
|
vendor/
|
||||||
|
|
@ -72,10 +72,10 @@ func TestRead(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCmdDaemonListen(t *testing.T) {
|
func TestCmdDaemonListen(t *testing.T) {
|
||||||
socketPath := "/tmp/test.sock"
|
pipePath := "/tmp/test.pipe"
|
||||||
daemon := &CmdDaemon{
|
daemon := &CmdDaemon{
|
||||||
SocketPath: socketPath,
|
PipePath: pipePath,
|
||||||
cmds: make(map[string]CmdHandler),
|
cmds: make(map[string]CmdHandler),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册一个简单的命令处理程序
|
// 注册一个简单的命令处理程序
|
||||||
|
|
@ -89,15 +89,18 @@ func TestCmdDaemonListen(t *testing.T) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 等待服务器启动
|
// 等待管道文件创建
|
||||||
time.Sleep(1 * time.Second)
|
waitForPipe(t, pipePath)
|
||||||
|
|
||||||
// 模拟客户端连接
|
// 打开命名管道进行写入
|
||||||
conn, err := net.Dial("unix", socketPath)
|
pipe, err := os.OpenFile(pipePath, os.O_RDWR, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Client connect failed: %v", err)
|
t.Fatalf("Failed to open pipe for writing: %v", err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer pipe.Close()
|
||||||
|
|
||||||
|
// 将文件转换为io.ReadWriter接口,以便与现有的Read/Write函数兼容
|
||||||
|
pipeConn := &pipeConnection{pipe}
|
||||||
|
|
||||||
// 发送请求
|
// 发送请求
|
||||||
req := CmdRequest{
|
req := CmdRequest{
|
||||||
|
|
@ -106,13 +109,13 @@ func TestCmdDaemonListen(t *testing.T) {
|
||||||
Args: "arg1",
|
Args: "arg1",
|
||||||
IsDebug: false,
|
IsDebug: false,
|
||||||
}
|
}
|
||||||
err = Write(conn, req)
|
err = Write(pipeConn, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Write failed: %v", err)
|
t.Errorf("Write failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取响应
|
// 读取响应
|
||||||
resp, err := Read[CmdResponse](conn)
|
resp, err := Read[CmdResponse](pipeConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Read failed: %v", err)
|
t.Fatalf("Read failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -125,10 +128,10 @@ func TestCmdDaemonListen(t *testing.T) {
|
||||||
func TestCmdDaemonRun(t *testing.T) {
|
func TestCmdDaemonRun(t *testing.T) {
|
||||||
// 这里我们假设有一个正在运行的服务器来处理请求
|
// 这里我们假设有一个正在运行的服务器来处理请求
|
||||||
// 因此我们需要首先启动一个简单的服务器来测试Run方法
|
// 因此我们需要首先启动一个简单的服务器来测试Run方法
|
||||||
socketPath := "/tmp/test_run.sock"
|
pipePath := "/tmp/test_run.pipe"
|
||||||
daemon := &CmdDaemon{
|
daemon := &CmdDaemon{
|
||||||
SocketPath: socketPath,
|
PipePath: pipePath,
|
||||||
cmds: make(map[string]CmdHandler),
|
cmds: make(map[string]CmdHandler),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册一个简单的命令处理程序
|
// 注册一个简单的命令处理程序
|
||||||
|
|
@ -141,27 +144,17 @@ func TestCmdDaemonRun(t *testing.T) {
|
||||||
go startTestDaemon(daemon, listening)
|
go startTestDaemon(daemon, listening)
|
||||||
|
|
||||||
// 等待直到监听开始
|
// 等待直到监听开始
|
||||||
// for循环中检查socket文件是否存在,如果存在退出循环,否则sleepeep 1秒
|
|
||||||
// 使用一个错误channel来传递错误
|
// 使用一个错误channel来传递错误
|
||||||
errChan := make(chan error)
|
errChan := make(chan error)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
listened := false
|
// 等待管道文件创建
|
||||||
for {
|
waitForPipe(t, pipePath)
|
||||||
|
|
||||||
if listened {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(socketPath); err == nil {
|
|
||||||
listened = true
|
|
||||||
}
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置命令行参数
|
// 设置命令行参数
|
||||||
os.Args = []string{"cmd", "--debug", "test", "arg1"}
|
os.Args = []string{"cmd", "--debug", "test", "arg1"}
|
||||||
ndaemon := &CmdDaemon{
|
ndaemon := &CmdDaemon{
|
||||||
SocketPath: socketPath,
|
PipePath: pipePath,
|
||||||
}
|
}
|
||||||
// 执行Run方法
|
// 执行Run方法
|
||||||
err := ndaemon.Run()
|
err := ndaemon.Run()
|
||||||
|
|
@ -240,10 +233,10 @@ func (h *UnknownCmdHandler) Usage() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyCommand(t *testing.T) {
|
func TestEmptyCommand(t *testing.T) {
|
||||||
socketPath := "/tmp/test_empty.sock"
|
pipePath := "/tmp/test_empty.pipe"
|
||||||
daemon := &CmdDaemon{
|
daemon := &CmdDaemon{
|
||||||
SocketPath: socketPath,
|
PipePath: pipePath,
|
||||||
cmds: make(map[string]CmdHandler),
|
cmds: make(map[string]CmdHandler),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册一个简单的命令处理程序
|
// 注册一个简单的命令处理程序
|
||||||
|
|
@ -255,28 +248,17 @@ func TestEmptyCommand(t *testing.T) {
|
||||||
// 启动守护进程
|
// 启动守护进程
|
||||||
go startTestDaemon(daemon, listening)
|
go startTestDaemon(daemon, listening)
|
||||||
|
|
||||||
// 等待直到监听开始
|
|
||||||
// for循环中检查socket文件是否存在,如果存在退出循环,否则sleepeep 1秒
|
|
||||||
// 使用一个错误channel来传递错误
|
// 使用一个错误channel来传递错误
|
||||||
errChan := make(chan error)
|
errChan := make(chan error)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
listened := false
|
// 等待管道文件创建
|
||||||
for {
|
waitForPipe(t, pipePath)
|
||||||
|
|
||||||
if listened {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(socketPath); err == nil {
|
|
||||||
listened = true
|
|
||||||
}
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置命令行参数
|
// 设置命令行参数
|
||||||
os.Args = []string{"cmd", "empty"}
|
os.Args = []string{"cmd", "empty"}
|
||||||
ndaemon := &CmdDaemon{
|
ndaemon := &CmdDaemon{
|
||||||
SocketPath: socketPath,
|
PipePath: pipePath,
|
||||||
}
|
}
|
||||||
// 执行Run方法
|
// 执行Run方法
|
||||||
err := ndaemon.Run()
|
err := ndaemon.Run()
|
||||||
|
|
@ -295,10 +277,10 @@ func TestEmptyCommand(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnknownCommand(t *testing.T) {
|
func TestUnknownCommand(t *testing.T) {
|
||||||
socketPath := "/tmp/test_unknown.sock"
|
pipePath := "/tmp/test_unknown.pipe"
|
||||||
daemon := &CmdDaemon{
|
daemon := &CmdDaemon{
|
||||||
SocketPath: socketPath,
|
PipePath: pipePath,
|
||||||
cmds: make(map[string]CmdHandler),
|
cmds: make(map[string]CmdHandler),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册一个简单的命令处理程序
|
// 注册一个简单的命令处理程序
|
||||||
|
|
@ -312,13 +294,13 @@ func TestUnknownCommand(t *testing.T) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 等待 socket 文件创建
|
// 等待管道文件创建
|
||||||
waitForSocket(t, socketPath)
|
waitForPipe(t, pipePath)
|
||||||
|
|
||||||
// 设置命令行参数
|
// 设置命令行参数
|
||||||
os.Args = []string{"cmd", "unknown"}
|
os.Args = []string{"cmd", "unknown"}
|
||||||
ndaemon := &CmdDaemon{
|
ndaemon := &CmdDaemon{
|
||||||
SocketPath: socketPath,
|
PipePath: pipePath,
|
||||||
}
|
}
|
||||||
// 执行Run方法
|
// 执行Run方法
|
||||||
err := ndaemon.Run()
|
err := ndaemon.Run()
|
||||||
|
|
@ -327,15 +309,15 @@ func TestUnknownCommand(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitForSocket 等待直到指定路径的 socket 文件被创建
|
// waitForPipe 等待直到指定路径的命名管道文件被创建
|
||||||
func waitForSocket(t *testing.T, path string) {
|
func waitForPipe(t *testing.T, path string) {
|
||||||
timeout := time.After(5 * time.Second)
|
timeout := time.After(5 * time.Second)
|
||||||
ticker := time.Tick(500 * time.Millisecond)
|
ticker := time.Tick(500 * time.Millisecond)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
t.Fatal("Timed out waiting for socket")
|
t.Fatal("Timed out waiting for pipe")
|
||||||
case <-ticker:
|
case <-ticker:
|
||||||
if _, err := os.Stat(path); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
package gocmdDaemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MFlag struct {
|
||||||
|
flags map[string]interface{}
|
||||||
|
RemainArgs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MFlag) Int(short string, long string, value int, usage string) {
|
||||||
|
var v int
|
||||||
|
m.flags[long] = &v
|
||||||
|
flag.IntVar(&v, short, value, usage)
|
||||||
|
flag.IntVar(&v, long, value, usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MFlag) String(short string, long string, value string, usage string) {
|
||||||
|
var v string
|
||||||
|
m.flags[long] = &v
|
||||||
|
flag.StringVar(&v, short, value, usage)
|
||||||
|
flag.StringVar(&v, long, value, usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MFlag) Bool(short string, long string, value bool, usage string) {
|
||||||
|
var v bool
|
||||||
|
m.flags[long] = &v
|
||||||
|
flag.BoolVar(&v, short, value, usage)
|
||||||
|
flag.BoolVar(&v, long, value, usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MFlag) Parse() {
|
||||||
|
flag.Parse()
|
||||||
|
m.RemainArgs = flag.Args()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MFlag) GetIntFlag(name string) int {
|
||||||
|
v, ok := m.flags[name]
|
||||||
|
if !ok {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
r := *(v.(*int))
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MFlag) GetStringFlag(name string) string {
|
||||||
|
v, ok := m.flags[name]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *(v.(*string))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MFlag) GetBoolFlag(name string) bool {
|
||||||
|
v, ok := m.flags[name]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *(v.(*bool))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MFlag) MarshalJSON() ([]byte, error) {
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
for k, v := range m.flags {
|
||||||
|
// 判断值类型
|
||||||
|
name := k
|
||||||
|
switch v.(type) {
|
||||||
|
case *int:
|
||||||
|
result[k] = m.GetIntFlag(name)
|
||||||
|
case *string:
|
||||||
|
result[k] = m.GetStringFlag(name)
|
||||||
|
case *bool:
|
||||||
|
result[k] = m.GetBoolFlag(name)
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// result[k] = *(v.(*interface{}))
|
||||||
|
}
|
||||||
|
result["remainArgs"] = m.RemainArgs
|
||||||
|
return json.Marshal(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MFlag) UnmarshalJSON(data []byte) error {
|
||||||
|
var result map[string]interface{}
|
||||||
|
err := json.Unmarshal(data, &result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for k, v := range result {
|
||||||
|
if k == "remainArgs" {
|
||||||
|
m.RemainArgs = convert2StringSlice(v.([]interface{}))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch v.(type) {
|
||||||
|
case float64:
|
||||||
|
p := int(v.(float64))
|
||||||
|
m.flags[k] = &p
|
||||||
|
case string:
|
||||||
|
p := v.(string)
|
||||||
|
m.flags[k] = &p
|
||||||
|
case bool:
|
||||||
|
p := v.(bool)
|
||||||
|
m.flags[k] = &p
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertSlice[T any, U any](input []T, convert func(T) U) []U {
|
||||||
|
result := make([]U, len(input))
|
||||||
|
for i, v := range input {
|
||||||
|
result[i] = convert(v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func convert2StringSlice[T any](input []T) []string {
|
||||||
|
return convertSlice(input, func(v T) string {
|
||||||
|
return fmt.Sprint(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,226 @@
|
||||||
|
package gocmdDaemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMFlag_Int(t *testing.T) {
|
||||||
|
// 保存原始参数,以便测试后恢复
|
||||||
|
oldArgs := os.Args
|
||||||
|
defer func() { os.Args = oldArgs }()
|
||||||
|
|
||||||
|
// 重置flag包状态
|
||||||
|
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
||||||
|
|
||||||
|
// 设置测试参数
|
||||||
|
os.Args = []string{"cmd", "-i", "123", "--int-flag", "456"}
|
||||||
|
|
||||||
|
m := &MFlag{
|
||||||
|
flags: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册标志
|
||||||
|
m.Int("i", "int-flag", 0, "测试整数标志")
|
||||||
|
m.Parse()
|
||||||
|
|
||||||
|
// 测试短标志
|
||||||
|
if got := m.GetIntFlag("int-flag"); got != 456 {
|
||||||
|
t.Errorf("GetIntFlag() = %v, want %v", got, 456)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMFlag_String(t *testing.T) {
|
||||||
|
// 保存原始参数,以便测试后恢复
|
||||||
|
oldArgs := os.Args
|
||||||
|
defer func() { os.Args = oldArgs }()
|
||||||
|
|
||||||
|
// 重置flag包状态
|
||||||
|
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
||||||
|
|
||||||
|
// 设置测试参数
|
||||||
|
os.Args = []string{"cmd", "-s", "hello", "--str-flag", "world"}
|
||||||
|
|
||||||
|
m := &MFlag{
|
||||||
|
flags: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册标志
|
||||||
|
m.String("s", "str-flag", "", "测试字符串标志")
|
||||||
|
m.Parse()
|
||||||
|
|
||||||
|
// 测试长标志
|
||||||
|
if got := m.GetStringFlag("str-flag"); got != "world" {
|
||||||
|
t.Errorf("GetStringFlag() = %v, want %v", got, "world")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMFlag_Bool(t *testing.T) {
|
||||||
|
// 保存原始参数,以便测试后恢复
|
||||||
|
oldArgs := os.Args
|
||||||
|
defer func() { os.Args = oldArgs }()
|
||||||
|
|
||||||
|
// 重置flag包状态
|
||||||
|
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
||||||
|
|
||||||
|
// 设置测试参数
|
||||||
|
os.Args = []string{"cmd", "-b", "--bool-flag"}
|
||||||
|
|
||||||
|
m := &MFlag{
|
||||||
|
flags: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册标志
|
||||||
|
m.Bool("b", "bool-flag", false, "测试布尔标志")
|
||||||
|
m.Parse()
|
||||||
|
|
||||||
|
// 测试布尔标志
|
||||||
|
if got := m.GetBoolFlag("bool-flag"); !got {
|
||||||
|
t.Errorf("GetBoolFlag() = %v, want %v", got, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMFlag_GetNonExistentFlags(t *testing.T) {
|
||||||
|
m := &MFlag{
|
||||||
|
flags: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试获取不存在的标志
|
||||||
|
if got := m.GetIntFlag("non-existent"); got != -1 {
|
||||||
|
t.Errorf("GetIntFlag() for non-existent flag = %v, want %v", got, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := m.GetStringFlag("non-existent"); got != "" {
|
||||||
|
t.Errorf("GetStringFlag() for non-existent flag = %v, want %v", got, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := m.GetBoolFlag("non-existent"); got != false {
|
||||||
|
t.Errorf("GetBoolFlag() for non-existent flag = %v, want %v", got, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMFlag_RemainArgs(t *testing.T) {
|
||||||
|
// 保存原始参数,以便测试后恢复
|
||||||
|
oldArgs := os.Args
|
||||||
|
defer func() { os.Args = oldArgs }()
|
||||||
|
|
||||||
|
// 重置flag包状态
|
||||||
|
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
||||||
|
|
||||||
|
// 设置测试参数,包括剩余参数
|
||||||
|
os.Args = []string{"cmd", "-i", "123", "arg1", "arg2", "arg3", "-d"}
|
||||||
|
|
||||||
|
m := &MFlag{
|
||||||
|
flags: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册标志
|
||||||
|
m.Int("i", "int-flag", 0, "测试整数标志")
|
||||||
|
m.Parse()
|
||||||
|
|
||||||
|
// 测试剩余参数
|
||||||
|
expectedArgs := []string{"arg1", "arg2", "arg3", "-d"}
|
||||||
|
if len(m.RemainArgs) != len(expectedArgs) {
|
||||||
|
t.Errorf("RemainArgs length = %v, want %v", len(m.RemainArgs), len(expectedArgs))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, arg := range expectedArgs {
|
||||||
|
if i < len(m.RemainArgs) && m.RemainArgs[i] != arg {
|
||||||
|
t.Errorf("RemainArgs[%d] = %v, want %v", i, m.RemainArgs[i], arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMFlag_MultipleFlags(t *testing.T) {
|
||||||
|
// 保存原始参数,以便测试后恢复
|
||||||
|
oldArgs := os.Args
|
||||||
|
defer func() { os.Args = oldArgs }()
|
||||||
|
|
||||||
|
// 重置flag包状态
|
||||||
|
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
||||||
|
|
||||||
|
// 设置测试参数
|
||||||
|
os.Args = []string{"cmd", "-i", "123", "-s", "hello", "-b", "subcmd", "-s", "h1"}
|
||||||
|
|
||||||
|
m := &MFlag{
|
||||||
|
flags: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册多个标志
|
||||||
|
m.Int("i", "int-flag", 0, "测试整数标志")
|
||||||
|
m.String("s", "str-flag", "", "测试字符串标志")
|
||||||
|
m.Bool("b", "bool-flag", false, "测试布尔标志")
|
||||||
|
m.Parse()
|
||||||
|
|
||||||
|
// 测试所有标志值
|
||||||
|
if got := m.GetIntFlag("int-flag"); got != 123 {
|
||||||
|
t.Errorf("GetIntFlag() = %v, want %v", got, 123)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := m.GetStringFlag("str-flag"); got != "hello" {
|
||||||
|
t.Errorf("GetStringFlag() = %v, want %v", got, "hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := m.GetBoolFlag("bool-flag"); !got {
|
||||||
|
t.Errorf("GetBoolFlag() = %v, want %v", got, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMFlag_MarshalJSON(t *testing.T) {
|
||||||
|
oldArgs := os.Args
|
||||||
|
defer func() { os.Args = oldArgs }()
|
||||||
|
|
||||||
|
// 重置flag包状态
|
||||||
|
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
||||||
|
|
||||||
|
// 设置测试参数
|
||||||
|
os.Args = []string{"cmd", "-i", "123", "-s", "hello", "-b", "subcmd", "-s", "h1"}
|
||||||
|
|
||||||
|
m := &MFlag{
|
||||||
|
flags: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册多个标志
|
||||||
|
m.Int("i", "int-flag", 0, "测试整数标志")
|
||||||
|
m.String("s", "str-flag", "", "测试字符串标志")
|
||||||
|
m.Bool("b", "bool-flag", false, "测试布尔标志")
|
||||||
|
m.Parse()
|
||||||
|
fmt.Println(m.GetIntFlag("int-flag"))
|
||||||
|
jsonBytes, err := m.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("MarshalJSON() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
var result map[string]interface{}
|
||||||
|
err = json.Unmarshal(jsonBytes, &result)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("UnmarshalJSON() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
if int(result["int-flag"].(float64)) != 123 {
|
||||||
|
t.Errorf("MarshalJSON() int-flag = %v, want %v", result["int-flag"], 123)
|
||||||
|
}
|
||||||
|
if result["str-flag"] != "hello" {
|
||||||
|
t.Errorf("MarshalJSON() str-flag = %v, want %v", result["str-flag"], "hello")
|
||||||
|
}
|
||||||
|
if result["bool-flag"] != true {
|
||||||
|
t.Errorf("MarshalJSON() bool-flag = %v, want %v", result["bool-flag"], true)
|
||||||
|
}
|
||||||
|
m1 := &MFlag{
|
||||||
|
flags: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
err1 := json.Unmarshal(jsonBytes, m1)
|
||||||
|
if err1 != nil {
|
||||||
|
t.Errorf("UnmarshalJSON() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
if int(m1.GetIntFlag("int-flag")) != 123 {
|
||||||
|
t.Errorf("MarshalJSON() int-flag = %v, want %v", m1.GetIntFlag("int-flag"), 123)
|
||||||
|
}
|
||||||
|
if m1.GetStringFlag("str-flag") != "hello" {
|
||||||
|
t.Errorf("MarshalJSON() str-flag = %v, want %v", m1.GetStringFlag("str-flag"), "hello")
|
||||||
|
}
|
||||||
|
if m1.GetBoolFlag("bool-flag") != true {
|
||||||
|
t.Errorf("MarshalJSON() bool-flag = %v, want %v", m1.GetBoolFlag("bool-flag"), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
197
main.go
197
main.go
|
|
@ -1,19 +1,22 @@
|
||||||
package gocmdDaemon
|
package gocmdDaemon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"flag"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CmdDaemon struct {
|
type CmdDaemon struct {
|
||||||
Name string // app cli command name
|
Name string // app cli command name
|
||||||
SocketPath string // unix socket path
|
PipePath string // named pipe path
|
||||||
cmds map[string]CmdHandler
|
cmds map[string]CmdHandler
|
||||||
}
|
}
|
||||||
type CmdRequest struct {
|
type CmdRequest struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
@ -33,6 +36,26 @@ type CmdConn struct {
|
||||||
Id string
|
Id string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pipeConnection 是一个适配器,将 *os.File 转换为 net.Conn 接口
|
||||||
|
type pipeConnection struct {
|
||||||
|
*os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现 net.Conn 接口所需的方法
|
||||||
|
func (p *pipeConnection) LocalAddr() net.Addr { return &pipeAddr{p.Name()} }
|
||||||
|
func (p *pipeConnection) RemoteAddr() net.Addr { return &pipeAddr{p.Name()} }
|
||||||
|
func (p *pipeConnection) SetDeadline(t time.Time) error { return nil }
|
||||||
|
func (p *pipeConnection) SetReadDeadline(t time.Time) error { return nil }
|
||||||
|
func (p *pipeConnection) SetWriteDeadline(t time.Time) error { return nil }
|
||||||
|
|
||||||
|
// pipeAddr 实现 net.Addr 接口
|
||||||
|
type pipeAddr struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *pipeAddr) Network() string { return "pipe" }
|
||||||
|
func (a *pipeAddr) String() string { return a.name }
|
||||||
|
|
||||||
func (c *CmdConn) Write(d string) error {
|
func (c *CmdConn) Write(d string) error {
|
||||||
resp := CmdResponse{
|
resp := CmdResponse{
|
||||||
Id: c.Id,
|
Id: c.Id,
|
||||||
|
|
@ -64,7 +87,7 @@ type CmdHandler interface {
|
||||||
Usage() string
|
Usage() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen 启动守护进程并监听Unix socket上的连接
|
// Listen 启动守护进程并监听命名管道上的连接
|
||||||
// 参数:
|
// 参数:
|
||||||
//
|
//
|
||||||
// c: CmdDaemon 实例指针
|
// c: CmdDaemon 实例指针
|
||||||
|
|
@ -73,59 +96,61 @@ type CmdHandler interface {
|
||||||
//
|
//
|
||||||
// error: 监听过程中发生的错误(如果有)
|
// error: 监听过程中发生的错误(如果有)
|
||||||
func (c *CmdDaemon) Listen() error {
|
func (c *CmdDaemon) Listen() error {
|
||||||
// 删除已存在的 socket 文件(如果存在)
|
// 删除已存在的命名管道(如果存在)
|
||||||
if err := os.Remove(c.SocketPath); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(c.PipePath); err != nil && !os.IsNotExist(err) {
|
||||||
return fmt.Errorf("failed to remove existing socket file: %v", err)
|
return fmt.Errorf("failed to remove existing pipe: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听 unix socket
|
// 创建命名管道
|
||||||
listener, err := net.Listen("unix", c.SocketPath)
|
if err := syscall.Mkfifo(c.PipePath, 0666); err != nil {
|
||||||
|
return fmt.Errorf("failed to create named pipe: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置管道文件权限
|
||||||
|
if err := os.Chmod(c.PipePath, 0666); err != nil {
|
||||||
|
return fmt.Errorf("failed to set pipe permissions: %v", err)
|
||||||
|
}
|
||||||
|
pipe, err := os.OpenFile(c.PipePath, os.O_RDWR, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to listen on socket: %v", err)
|
return fmt.Errorf("failed to open pipe for reading: %v", err)
|
||||||
}
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
// 设置 socket 文件权限
|
|
||||||
if err := os.Chmod(c.SocketPath, 0777); err != nil {
|
|
||||||
return fmt.Errorf("failed to set socket file permissions: %v", err)
|
|
||||||
}
|
}
|
||||||
|
defer pipe.Close()
|
||||||
|
|
||||||
|
// 将文件转换为io.ReadWriter接口,以便与现有的Read/Write函数兼容
|
||||||
|
pipeConn := &pipeConnection{pipe}
|
||||||
for {
|
for {
|
||||||
conn, err := listener.Accept()
|
// 打开命名管道进行读取
|
||||||
|
|
||||||
|
// 处理请求
|
||||||
|
// go func(pipe *os.File) {
|
||||||
|
|
||||||
|
req, err := Read[CmdRequest](pipeConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to accept connection: %v", err)
|
_ = Write(pipeConn, CmdResponse{
|
||||||
|
Error: "failed to read request: " + err.Error(),
|
||||||
|
Continue: false,
|
||||||
|
})
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理每个连接
|
cmdHandler, ok := c.cmds[req.Cmd]
|
||||||
go func(conn net.Conn) {
|
if !ok {
|
||||||
defer conn.Close()
|
_ = Write(pipeConn, CmdResponse{
|
||||||
|
Error: "unknown command: " + req.Cmd + "\n" + c.Usage(),
|
||||||
|
Continue: false,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
req, err := Read[CmdRequest](conn)
|
// 执行命令处理程序
|
||||||
if err != nil {
|
cmdConn := &CmdConn{Conn: pipeConn, Id: req.Id}
|
||||||
_ = Write(conn, CmdResponse{
|
err = cmdHandler.Handle(cmdConn, req)
|
||||||
Error: "failed to read request: " + err.Error(),
|
if err != nil {
|
||||||
Continue: false,
|
_ = cmdConn.End(err.Error() + cmdHandler.Usage())
|
||||||
})
|
}
|
||||||
return
|
// }(pipe)
|
||||||
}
|
|
||||||
|
|
||||||
cmdHandler, ok := c.cmds[req.Cmd]
|
|
||||||
if !ok {
|
|
||||||
_ = Write(conn, CmdResponse{
|
|
||||||
Error: "unknown command: " + req.Cmd + "\n" + c.Usage(),
|
|
||||||
Continue: false,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行命令处理程序
|
|
||||||
cmdConn := &CmdConn{Conn: conn, Id: req.Id}
|
|
||||||
err = cmdHandler.Handle(cmdConn, req)
|
|
||||||
if err != nil {
|
|
||||||
_ = cmdConn.End(err.Error() + cmdHandler.Usage())
|
|
||||||
}
|
|
||||||
}(conn)
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage 生成命令使用说明
|
// Usage 生成命令使用说明
|
||||||
|
|
@ -173,58 +198,64 @@ func (c *CmdDaemon) isDebug(cmd string) bool {
|
||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// error: 执行过程中发生的错误(如果有)
|
// error: 执行过程中发生的错误(如果有)
|
||||||
func (c *CmdDaemon) Run() error {
|
func (c *CmdDaemon) Run() (*CmdResponse, error) {
|
||||||
|
// 定义flag: -debug 和 -daemon
|
||||||
|
// flag := flag.NewFlagSet(c.Name, flag.ContinueOnError)
|
||||||
|
var isDebug bool
|
||||||
|
flag.BoolVar(&isDebug, "debug", false, "Run command in debug mode")
|
||||||
|
flag.BoolVar(&isDebug, "d", false, "Run command in debug mode")
|
||||||
|
var daemon bool
|
||||||
|
flag.BoolVar(&daemon, "daemon", false, "Run command in daemon mode")
|
||||||
|
flag.BoolVar(&daemon, "D", false, "Run command in daemon mode")
|
||||||
|
flag.Parse()
|
||||||
// 从命令参数中解析出是否debug 子命令和剩余参数字符串
|
// 从命令参数中解析出是否debug 子命令和剩余参数字符串
|
||||||
args := os.Args[1:]
|
|
||||||
isDebug := c.isDebug(args[0])
|
|
||||||
var remainingArgs []string
|
var remainingArgs []string
|
||||||
cmd := ""
|
cmd := ""
|
||||||
if isDebug {
|
// if isDebug {
|
||||||
if len(args) > 1 {
|
// if len(args) > 1 {
|
||||||
cmd = args[1]
|
// cmd = args[1]
|
||||||
remainingArgs = args[2:]
|
// remainingArgs = args[2:]
|
||||||
} else {
|
// } else {
|
||||||
cmd = "help"
|
// cmd = "help"
|
||||||
isDebug = false
|
// isDebug = false
|
||||||
remainingArgs = []string{}
|
// remainingArgs = []string{}
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
if len(args) > 0 {
|
// if len(args) > 0 {
|
||||||
cmd = args[0]
|
// cmd = args[0]
|
||||||
remainingArgs = args[1:]
|
// remainingArgs = args[1:]
|
||||||
} else {
|
// } else {
|
||||||
cmd = "help"
|
// cmd = "help"
|
||||||
isDebug = false
|
// isDebug = false
|
||||||
remainingArgs = []string{}
|
// remainingArgs = []string{}
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
cmdReq := CmdRequest{
|
cmdReq := CmdRequest{
|
||||||
Args: strings.Join(remainingArgs, " "),
|
Args: strings.Join(remainingArgs, " "),
|
||||||
Cmd: cmd,
|
Cmd: cmd,
|
||||||
Id: uuid.New().String(),
|
Id: uuid.New().String(),
|
||||||
IsDebug: isDebug,
|
IsDebug: isDebug,
|
||||||
}
|
}
|
||||||
// dial unix socket
|
// 打开命名管道进行写入
|
||||||
conn, err := net.Dial("unix", c.SocketPath)
|
pipe, err := os.OpenFile(c.PipePath, os.O_RDWR, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, fmt.Errorf("failed to open pipe for writing: %v", err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer pipe.Close()
|
||||||
// send cmd request
|
|
||||||
cmdReqJson, err := json.Marshal(cmdReq)
|
// 将文件转换为io.ReadWriter接口,以便与现有的Read/Write函数兼容
|
||||||
|
pipeConn := &pipeConnection{pipe}
|
||||||
|
|
||||||
|
// 发送命令请求
|
||||||
|
err = Write(pipeConn, cmdReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, fmt.Errorf("failed to write request to pipe: %v", err)
|
||||||
}
|
|
||||||
_, err = conn.Write(cmdReqJson)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
resp, err := Read[CmdResponse](pipeConn)
|
||||||
resp, err := Read[CmdResponse](conn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, fmt.Errorf("failed to read response from pipe: %v", err)
|
||||||
}
|
}
|
||||||
if resp.Error != "" {
|
if resp.Error != "" {
|
||||||
fmt.Println(resp.Error)
|
fmt.Println(resp.Error)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue