Community
Development of a Simple Command Line Websocket Client
While working on the Websocket Shootout, I frequently needed to connect to a websocket server and interact directly. None of the existing tools I was aware of had exactly the features I was wanted. That led to the creation of ws
.
The goals for ws
were as follows:
- Telnet style interaction with remote websocket servers
- Persistent, readline style history
- Cross-platform
- No or minimal dependencies
Go was an obvious choice for meeting the cross-platform and dependency requirements. Thanks to excellent library support the rest of the features were easy to build.
The github.com/spf13/cobra is used for command line argument parsing. The Go standard library flag
package would have been sufficient, but I prefer GNU style flags. cobra
makes it easy to define arguments.
rootCmd := &cobra.Command{
Use: "ws URL",
Short: "websocket tool",
Run: root,
}
rootCmd.Flags().StringVarP(&options.origin, "origin", "o", "", "websocket origin")
rootCmd.Flags().BoolVarP(&options.printVersion, "version", "v", false, "print version")
The websocket protocol is handled by the github.com/gorilla/websocket
package. With one Dial
function call, we get a connection that let's us work at the websocket message level.
headers := make(http.Header)
headers.Add("Origin", origin)
ws, _, err := websocket.DefaultDialer.Dial(url, headers)
if err != nil {
return err
}
github.com/chzyer/readline provided GNU readline style functionality in pure Go. One command initializes the readline system.
rlConf := &readline.Config{
Prompt: "> ",
HistoryFile: historyFile,
}
// ...
rl, err := readline.NewEx(rlConf)
if err != nil {
return err
}
Reading a line is similarly easy.
line, err := s.rl.Readline()
if err != nil {
s.errChan <- err
return
}
I encountered one bug/issue, but the author pushed a fix in less than a day of my post.
The github.com/fatih/color library made it easy to output color text in a cross platform way. It lets you get a Sprintf
style function that wraps the text in the appropriate escape characters for a given color.
rxSprintf := color.New(color.FgGreen).SprintfFunc()
Goroutines made handling keyboard input and websocket IO simple by treating them independently.
// ...
go sess.readConsole()
go sess.readWebsocket()
// ...
func (s *session) readConsole() {
for {
line, err := s.rl.Readline()
if err != nil {
s.errChan <- err
return
}
_, err = io.WriteString(s.ws, line)
if err != nil {
s.errChan <- err
return
}
}
}
func (s *session) readWebsocket() {
rxSprintf := color.New(color.FgGreen).SprintfFunc()
for {
_, buf, err := s.ws.ReadMessage()
if err != nil {
s.errChan <- err
return
}
fmt.Fprint(s.rl.Stdout(), rxSprintf("< %s\n", string(buf)))
}
}
The entire program is just over 130 lines. You can check it out at github.com/hashrocket/ws.
If you have a working Go installation, you can build ws
with a single command:
go get -u github.com/hashrocket/ws