优化cli命令行的问题

This commit is contained in:
fancl 2024-01-18 17:11:44 +08:00
parent 83afa05fa3
commit 972eb004d2
10 changed files with 91 additions and 19 deletions

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"embed" "embed"
"flag" "flag"
"git.nspix.com/golang/kos/entry/cli"
"git.nspix.com/golang/kos/entry/http" "git.nspix.com/golang/kos/entry/http"
httpkg "net/http" httpkg "net/http"
@ -16,11 +17,29 @@ var webDir embed.FS
type subServer struct { type subServer struct {
} }
type users struct {
Name string `json:"name"`
Age int `json:"age"`
Tags []string `json:"tags"`
}
func (s *subServer) Start(ctx context.Context) (err error) { func (s *subServer) Start(ctx context.Context) (err error) {
kos.Http().Root("/web", httpkg.FS(webDir)) kos.Http().Root("/web", httpkg.FS(webDir))
kos.Http().Handle(httpkg.MethodGet, "/hello", func(ctx *http.Context) (err error) { kos.Http().Handle(httpkg.MethodGet, "/hello", func(ctx *http.Context) (err error) {
return ctx.Success("Hello World") return ctx.Success("Hello World")
}) })
kos.Command().Handle("/test", "test command", func(ctx *cli.Context) (err error) {
return ctx.Success([][]string{
[]string{"NAME", "AGE"},
[]string{"SSS", "aaa"},
})
})
kos.Command().Handle("/users", "test command", func(ctx *cli.Context) (err error) {
return ctx.Success([]*users{
{Name: "Zhan", Age: 10, Tags: []string{"a", "b"}},
{Name: "Lisi", Age: 15, Tags: []string{"c", "d"}},
})
})
return return
} }
@ -33,7 +52,6 @@ func main() {
svr := kos.Init( svr := kos.Init(
kos.WithName("git.nspix.com/golang/test", "0.0.1"), kos.WithName("git.nspix.com/golang/test", "0.0.1"),
kos.WithServer(&subServer{}), kos.WithServer(&subServer{}),
kos.WithDirectHttp(),
) )
svr.Run() svr.Run()
} }

View File

@ -141,7 +141,7 @@ func (client *Client) completer(str string) (ss []string) {
) )
ss = make([]string, 0) ss = make([]string, 0)
seq = client.getSequence() seq = client.getSequence()
if err = writeFrame(client.conn, newFrame(PacketTypeCompleter, FlagComplete, seq, []byte(str))); err != nil { if err = writeFrame(client.conn, newFrame(PacketTypeCompleter, FlagComplete, seq, client.Timeout, []byte(str))); err != nil {
return return
} }
select { select {
@ -166,10 +166,10 @@ func (client *Client) Execute(s string) (err error) {
}() }()
go client.ioLoop(client.conn) go client.ioLoop(client.conn)
seq = client.getSequence() seq = client.getSequence()
if err = writeFrame(client.conn, newFrame(PacketTypeCommand, FlagComplete, seq, []byte(s))); err != nil { if err = writeFrame(client.conn, newFrame(PacketTypeCommand, FlagComplete, seq, client.Timeout, []byte(s))); err != nil {
return err return err
} }
client.waitResponse(seq, time.Second*30) client.waitResponse(seq, client.Timeout)
return return
} }
@ -185,7 +185,7 @@ func (client *Client) Shell() (err error) {
defer func() { defer func() {
_ = client.Close() _ = client.Close()
}() }()
if err = writeFrame(client.conn, newFrame(PacketTypeHandshake, FlagComplete, client.getSequence(), nil)); err != nil { if err = writeFrame(client.conn, newFrame(PacketTypeHandshake, FlagComplete, client.getSequence(), client.Timeout, nil)); err != nil {
return return
} }
go client.ioLoop(client.conn) go client.ioLoop(client.conn)
@ -216,7 +216,7 @@ func (client *Client) Shell() (err error) {
continue continue
} }
seq = client.getSequence() seq = client.getSequence()
if err = writeFrame(client.conn, newFrame(PacketTypeCommand, FlagComplete, seq, []byte(line))); err != nil { if err = writeFrame(client.conn, newFrame(PacketTypeCommand, FlagComplete, seq, client.Timeout, []byte(line))); err != nil {
break break
} }
client.liner.AppendHistory(line) client.liner.AppendHistory(line)

View File

@ -1,6 +1,7 @@
package cli package cli
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"math" "math"
@ -9,6 +10,7 @@ import (
type Context struct { type Context struct {
Id int64 Id int64
seq uint16 seq uint16
ctx context.Context
wc io.WriteCloser wc io.WriteCloser
params map[string]string params map[string]string
args []string args []string
@ -18,6 +20,7 @@ func (ctx *Context) reset(id int64, wc io.WriteCloser) {
ctx.Id = id ctx.Id = id
ctx.wc = wc ctx.wc = wc
ctx.seq = 0 ctx.seq = 0
ctx.ctx = context.Background()
ctx.args = make([]string, 0) ctx.args = make([]string, 0)
ctx.params = make(map[string]string) ctx.params = make(map[string]string)
} }
@ -34,6 +37,14 @@ func (ctx *Context) Bind(v any) (err error) {
return return
} }
func (ctx *Context) setContext(c context.Context) {
ctx.ctx = c
}
func (ctx *Context) Context() context.Context {
return ctx.Context()
}
func (ctx *Context) Argument(index int) string { func (ctx *Context) Argument(index int) string {
if index >= len(ctx.args) || index < 0 { if index >= len(ctx.args) || index < 0 {
return "" return ""
@ -93,12 +104,12 @@ __END:
chunkSize := math.MaxInt16 - 1 chunkSize := math.MaxInt16 - 1
n := len(buf) / chunkSize n := len(buf) / chunkSize
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
if err = writeFrame(ctx.wc, newFrame(res.Type, FlagPortion, ctx.seq, buf[offset:chunkSize+offset])); err != nil { if err = writeFrame(ctx.wc, newFrame(res.Type, FlagPortion, ctx.seq, 0, buf[offset:chunkSize+offset])); err != nil {
return return
} }
offset += chunkSize offset += chunkSize
} }
err = writeFrame(ctx.wc, newFrame(res.Type, FlagComplete, ctx.seq, buf[offset:])) err = writeFrame(ctx.wc, newFrame(res.Type, FlagComplete, ctx.seq, 0, buf[offset:]))
return return
} }

View File

@ -27,6 +27,7 @@ type (
Seq uint16 `json:"seq"` Seq uint16 `json:"seq"`
Data []byte `json:"data"` Data []byte `json:"data"`
Error string `json:"error"` Error string `json:"error"`
Timeout int64 `json:"timeout"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
} }
) )
@ -55,6 +56,9 @@ func readFrame(r io.Reader) (frame *Frame, err error) {
if err = binary.Read(r, binary.LittleEndian, &frame.Seq); err != nil { if err = binary.Read(r, binary.LittleEndian, &frame.Seq); err != nil {
return return
} }
if err = binary.Read(r, binary.LittleEndian, &frame.Timeout); err != nil {
return
}
if err = binary.Read(r, binary.LittleEndian, &frame.Timestamp); err != nil { if err = binary.Read(r, binary.LittleEndian, &frame.Timestamp); err != nil {
return return
} }
@ -116,6 +120,9 @@ func writeFrame(w io.Writer, frame *Frame) (err error) {
if err = binary.Write(w, binary.LittleEndian, frame.Seq); err != nil { if err = binary.Write(w, binary.LittleEndian, frame.Seq); err != nil {
return return
} }
if err = binary.Write(w, binary.LittleEndian, frame.Timeout); err != nil {
return
}
if err = binary.Write(w, binary.LittleEndian, frame.Timestamp); err != nil { if err = binary.Write(w, binary.LittleEndian, frame.Timestamp); err != nil {
return return
} }
@ -142,13 +149,14 @@ func writeFrame(w io.Writer, frame *Frame) (err error) {
return return
} }
func newFrame(t, f byte, seq uint16, data []byte) *Frame { func newFrame(t, f byte, seq uint16, timeout time.Duration, data []byte) *Frame {
return &Frame{ return &Frame{
Feature: Feature, Feature: Feature,
Type: t, Type: t,
Flag: f, Flag: f,
Seq: seq, Seq: seq,
Data: data, Data: data,
Timeout: int64(timeout),
Timestamp: time.Now().Unix(), Timestamp: time.Now().Unix(),
} }
} }

View File

@ -97,6 +97,9 @@ func (r *Router) Handle(path string, command Command) {
name = path name = path
path = "" path = ""
} }
if name == "-" {
name = "app"
}
children := r.getChildren(name) children := r.getChildren(name)
if children == nil { if children == nil {
children = newRouter(name) children = newRouter(name)

View File

@ -170,7 +170,7 @@ func serializeArray(val []any) (buf []byte, err error) {
if rv.Kind() == reflect.Array || rv.Kind() == reflect.Slice { if rv.Kind() == reflect.Array || rv.Kind() == reflect.Slice {
row := make([]any, 0, rv.Len()) row := make([]any, 0, rv.Len())
for i := 0; i < rv.Len(); i++ { for i := 0; i < rv.Len(); i++ {
if isNormalKind(rv.Index(i).Elem().Kind()) || rv.Index(i).Interface() == nil { if isNormalKind(rv.Index(i).Kind()) || rv.Index(i).Interface() == nil {
row = append(row, rv.Index(i).Interface()) row = append(row, rv.Index(i).Interface())
} else { } else {
goto __END goto __END

View File

@ -44,9 +44,8 @@ func (svr *Server) releaseContext(ctx *Context) {
ctxPool.Put(ctx) ctxPool.Put(ctx)
} }
func (svr *Server) handle(ctx *Context, frame *Frame) { func (svr *Server) handle(ctx *Context, frame *Frame) (err error) {
var ( var (
err error
params map[string]string params map[string]string
tokens []string tokens []string
args []string args []string
@ -54,6 +53,15 @@ func (svr *Server) handle(ctx *Context, frame *Frame) {
) )
cmd := string(frame.Data) cmd := string(frame.Data)
tokens = strings.Fields(cmd) tokens = strings.Fields(cmd)
if frame.Timeout > 0 {
childCtx, cancelFunc := context.WithTimeout(svr.ctx, time.Duration(frame.Timeout))
ctx.setContext(childCtx)
defer func() {
cancelFunc()
}()
} else {
ctx.setContext(svr.ctx)
}
if r, args, err = svr.router.Lookup(tokens); err != nil { if r, args, err = svr.router.Lookup(tokens); err != nil {
if errors.Is(err, ErrNotFound) { if errors.Is(err, ErrNotFound) {
err = ctx.Error(errNotFound, fmt.Sprintf("Command %s not found", cmd)) err = ctx.Error(errNotFound, fmt.Sprintf("Command %s not found", cmd))
@ -75,6 +83,7 @@ func (svr *Server) handle(ctx *Context, frame *Frame) {
ctx.setParam(params) ctx.setParam(params)
err = r.command.Handle(ctx) err = r.command.Handle(ctx)
} }
return
} }
func (svr *Server) nextSequence() int64 { func (svr *Server) nextSequence() int64 {
@ -130,7 +139,9 @@ func (svr *Server) process(conn net.Conn) {
break break
} }
case PacketTypeCommand: case PacketTypeCommand:
svr.handle(ctx, frame) if err = svr.handle(ctx, frame); err != nil {
break
}
default: default:
break break
} }

View File

@ -31,6 +31,12 @@ type (
} }
Option func(o *Options) Option func(o *Options)
HandleOptions struct {
description string
}
HandleOption func(o *HandleOptions)
) )
func (o *Options) ShortName() string { func (o *Options) ShortName() string {
@ -45,6 +51,12 @@ func (o *Options) ShortName() string {
return o.shortName return o.shortName
} }
func WithHandleDescription(s string) HandleOption {
return func(o *HandleOptions) {
o.description = s
}
}
func WithName(name string, version string) Option { func WithName(name string, version string) Option {
return func(o *Options) { return func(o *Options) {
o.Name = name o.Name = name
@ -100,3 +112,11 @@ func NewOptions(cbs ...Option) *Options {
} }
return opts return opts
} }
func newHandleOptions(cbs ...HandleOption) *HandleOptions {
opts := &HandleOptions{}
for _, cb := range cbs {
cb(opts)
}
return opts
}

View File

@ -76,14 +76,15 @@ func (app *application) Command() *cli.Server {
return app.command return app.command
} }
func (app *application) Handle(path string, cb HandleFunc) { func (app *application) Handle(path string, cb HandleFunc, cbs ...HandleOption) {
opts := newHandleOptions(cbs...)
if app.http != nil { if app.http != nil {
app.http.Handle(http.MethodPost, path, func(ctx *http.Context) (err error) { app.http.Handle(http.MethodPost, path, func(ctx *http.Context) (err error) {
return cb(ctx) return cb(ctx)
}) })
} }
if app.command != nil { if app.command != nil {
app.command.Handle(path, "", func(ctx *cli.Context) (err error) { app.command.Handle(path, opts.description, func(ctx *cli.Context) (err error) {
return cb(ctx) return cb(ctx)
}) })
} }
@ -252,10 +253,10 @@ func (app *application) preStart() (err error) {
Uptime: time.Now().Sub(app.uptime).String(), Uptime: time.Now().Sub(app.uptime).String(),
Gateway: app.gateway.State(), Gateway: app.gateway.State(),
}) })
}) }, WithHandleDescription("Display application state"))
app.Handle("/-/healthy", func(ctx Context) (err error) { app.Handle("/-/healthy", func(ctx Context) (err error) {
return ctx.Success(app.Healthy()) return ctx.Success(app.Healthy())
}) }, WithHandleDescription("Display application healthy"))
} }
app.plugins.Range(func(key, value any) bool { app.plugins.Range(func(key, value any) bool {
if plugin, ok := value.(Plugin); ok { if plugin, ok := value.(Plugin); ok {

View File

@ -22,9 +22,9 @@ type (
Info() *Info Info() *Info
Http() *http.Server Http() *http.Server
Command() *cli.Server Command() *cli.Server
Handle(method string, cb HandleFunc) Handle(method string, cb HandleFunc, opts ...HandleOption)
} }
// Info application information // Info application information
Info struct { Info struct {
ID string `json:"id"` ID string `json:"id"`