diff --git a/cmd/root.go b/cmd/root.go index cd429a7f..eec2ca8c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -95,6 +95,7 @@ func init() { dumpCmd, restoreCmd, socketCmd, + websocketCmd, ) // rootCmd.SetHelpCommand(helpCmd) rootCmd.PersistentFlags().StringVarP(&appPath, "app", "a", "", L("Application directory")) diff --git a/cmd/socket.go b/cmd/socket.go index c4ffd400..fd5a1948 100644 --- a/cmd/socket.go +++ b/cmd/socket.go @@ -16,8 +16,8 @@ import ( var socketCmd = &cobra.Command{ Use: "socket", - Short: L("Open socket"), - Long: L("Open socket"), + Short: L("Open a socket connection"), + Long: L("Open a socket connection"), Run: func(cmd *cobra.Command, args []string) { defer share.SessionStop() defer gou.KillPlugins() diff --git a/cmd/start.go b/cmd/start.go index 117e07ac..33a73f1e 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -47,7 +47,7 @@ var startCmd = &cobra.Command{ host = "127.0.0.1" } - if mode == "development" { + if mode == "development" || mode == "production" { fmt.Println(color.WhiteString("\n---------------------------------")) fmt.Println(color.WhiteString(L("API List"))) fmt.Println(color.WhiteString("---------------------------------")) diff --git a/cmd/websocket.go b/cmd/websocket.go new file mode 100644 index 00000000..69cc66d8 --- /dev/null +++ b/cmd/websocket.go @@ -0,0 +1,71 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/engine" + "github.com/yaoapp/yao/share" +) + +var websocketCmd = &cobra.Command{ + Use: "websocket", + Short: L("Open a websocket connection"), + Long: L("Open a websocket connection"), + Run: func(cmd *cobra.Command, args []string) { + defer share.SessionStop() + defer gou.KillPlugins() + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + Boot() + cfg := config.Conf + cfg.Session.IsCLI = true + engine.Load(cfg) + if len(args) < 1 { + fmt.Println(color.RedString(L("Not enough arguments"))) + fmt.Println(color.WhiteString(share.BUILDNAME + " help")) + return + } + + name := args[0] + websocket, has := gou.WebSockets[name] + if !has { + fmt.Println(color.RedString(L("%s not exists!"), name)) + return + } + + url := websocket.URL + protocols := websocket.Protocols + argsLen := len(args) + if argsLen > 1 { + url = args[1] + } + + if argsLen > 2 { + protocols = args[2:] + } + + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(websocket.Name)) + fmt.Println(color.WhiteString("---------------------------------")) + fmt.Println(color.GreenString(" URL: %s", url)) + fmt.Println(color.GreenString("Protocols: %s", strings.Join(protocols, ","))) + fmt.Println(color.WhiteString("--------------------------------------")) + pargs := append([]string{url}, protocols...) + err := websocket.Open(pargs...) + if err != nil { + fmt.Println(color.RedString(L("%s"), err.Error())) + return + } + }, +} diff --git a/engine/load.go b/engine/load.go index 4f11518b..00e9b871 100644 --- a/engine/load.go +++ b/engine/load.go @@ -24,6 +24,7 @@ import ( "github.com/yaoapp/yao/socket" "github.com/yaoapp/yao/store" "github.com/yaoapp/yao/table" + "github.com/yaoapp/yao/websocket" ) // Load 根据配置加载 API, FLow, Model, Plugin @@ -115,6 +116,11 @@ func Load(cfg config.Config) (err error) { log.Debug(err.Error()) } + err = websocket.Load(cfg) // Load websockets (client) + if err != nil { + log.Debug(err.Error()) + } + return nil } diff --git a/tests/scripts/websocket.js b/tests/scripts/websocket.js index 557929e3..dd5182ba 100644 --- a/tests/scripts/websocket.js +++ b/tests/scripts/websocket.js @@ -3,3 +3,89 @@ function Hello() { var response = ws.push("Hello World"); return response; } + +const host = "127.0.0.1:5099"; + +/** + * WebSocket Client EventMode(daemon) + */ +function Event() { + var url = `ws://${host}/websocket/chat`; + var ws = new WebSocket(url, "yao-chat-01"); + + // Connection opened + ws.on("open", function (event) { + ws.send("Hello Server!", event.data); + }); + + // Listen for messages + ws.on("message", function (event) { + console.log("Message from server ", event.data); + ws.close(200, "Bye"); + }); + + // Listen for error + ws.on("error", (event) => { + console.log("Message from server ", event); + }); + + // Listen for close + ws.on("close", (event) => { + console.log("The connection has been closed successfully"); + }); +} + +/** + * WebSocket Client PushMode + */ +function Push() { + var url = `ws://${host}/websocket/chat`; + var ws = new WebSocket(url, "yao-chat-01"); + var message = ws.push("Hello Server!"); + console.log(message); +} + +/** + * WebSocket Client JWT Auth + */ +function Token() { + var token = "xxx"; + var url = `ws://${host}/websocket/chat?token=${token}`; + var ws = new WebSocket(url, "yao-chat-01"); + var message = ws.push("Hello Server!"); + console.log(message); +} + +/** + * WebSocket Client Basic Auth + */ +function Basic() { + // Basic Auth username:password + var user = "test"; + var password = "WsTest123**"; + var url = `ws://${user}:${password}@${host}/websocket/chat?token=${token}`; + var ws = new WebSocket(url, "yao-chat-01"); + var message = ws.push("Hello Server!"); + console.log(message); +} + +function onData(data, recvLen) { + console.log(`Data: ${data} ${recvLen}`); + log.Trace("onData: %v %v", data, recvLen); + if (data[0] == "1") { + Process("websocket.Close", "message"); + } +} + +function onError(err) { + console.log(`Error: ${err} `); +} + +function onClosed(data, err) { + console.log(`Closed: ${data} ${err} `); +} + +function onConnected(option) { + console.log("onConnected", option); + Process("websocket.Write", "message", "1|Hello World"); +} diff --git a/tests/websockets/message.ws.json b/tests/websockets/message.ws.json new file mode 100644 index 00000000..0ef04577 --- /dev/null +++ b/tests/websockets/message.ws.json @@ -0,0 +1,18 @@ +{ + "name": "A WebSocket client", + "description": "A Chat WebSocket client", + "version": "0.10.0", + "url": "ws://127.0.0.1:5011/websocket/message", + "protocols": ["yao-message-01"], + "buffer": { "read": 1024, "write": 1024 }, + "limit": { "read-wait": 10, "pong-wait": 20, "max-message": 1024 }, + "timeout": 5, + "attempt_after": 200, + "attempts": 9, + "event": { + "data": "scripts.websocket.onData", + "closed": "scripts.websocket.onClosed", + "connected": "scripts.websocket.onConnected", + "error": "scripts.websocket.onError" + } +} diff --git a/websocket/websocket.go b/websocket/websocket.go new file mode 100644 index 00000000..860a0c6d --- /dev/null +++ b/websocket/websocket.go @@ -0,0 +1,36 @@ +package websocket + +import ( + "fmt" + "path/filepath" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +// Load 加载API +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "websockets") + return LoadFrom(root, "") +} + +// LoadFrom 从特定目录加载 +func LoadFrom(dir string, prefix string) error { + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + err := share.Walk(dir, ".ws.json", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + _, err := gou.LoadWebSocket(string(content), name) + if err != nil { + log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + } + }) + + return err +} diff --git a/websocket/websocket_test.go b/websocket/websocket_test.go new file mode 100644 index 00000000..2c5c48a7 --- /dev/null +++ b/websocket/websocket_test.go @@ -0,0 +1,69 @@ +package websocket + +import ( + "fmt" + "net" + "net/http" + "testing" + "time" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" + "github.com/yaoapp/gou/websocket" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/script" +) + +func TestLoad(t *testing.T) { + Load(config.Conf) + LoadFrom("not a path", "404.") + check(t) +} + +func TestWebSocketOpen(t *testing.T) { + Load(config.Conf) + script.Load(config.Conf) + srv, url := serve(t) + defer srv.Stop() + + ws := gou.SelectWebSocket("message") + err := ws.Open(url, "messageV2", "chatV3") + if err != nil { + t.Fatal(err) + } +} + +func serve(t *testing.T) (*websocket.Upgrader, string) { + + ws, err := websocket.NewUpgrader("test") + if err != nil { + t.Fatalf("%s", err) + } + + gin.SetMode(gin.ReleaseMode) + router := gin.Default() + ws.SetHandler(func(message []byte) ([]byte, error) { return message, nil }) + ws.SetRouter(router) + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + + go ws.Start() + go func() { + http.Serve(listener, router) + }() + time.Sleep(200 * time.Millisecond) + + return ws, fmt.Sprintf("ws://127.0.0.1:%d/websocket/test", listener.Addr().(*net.TCPAddr).Port) +} + +func check(t *testing.T) { + keys := []string{} + for key := range gou.WebSockets { + keys = append(keys, key) + } + assert.Equal(t, 1, len(keys)) +}