diff --git a/README.md b/README.md index deb6c31..9d71959 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Gorilla WebSocket is a [Go](http://golang.org/) implementation of the * [API Reference](http://godoc.org/github.com/gorilla/websocket) * [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) +* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) * [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) * [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) diff --git a/examples/command/README.md b/examples/command/README.md new file mode 100644 index 0000000..786e8cd --- /dev/null +++ b/examples/command/README.md @@ -0,0 +1,19 @@ +# Command example + +This example connects a websocket connection to stdin and stdout of a command. +Received messages are written to stdin followed by a `\\n`. Each line read from +from standard out is sent as a message to the client. + + $ go get github.com/gorilla/websocket + $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/command` + $ go run main.go + # Open http://localhost:8080/ . + +Try the following commands. + + # Echo sent messages to the output area. + $ go run main.go cat + + # Run a shell.Try sending `ls` and `cat main.go`. + $ go run main.go sh + diff --git a/examples/command/home.html b/examples/command/home.html new file mode 100644 index 0000000..9ddcb6e --- /dev/null +++ b/examples/command/home.html @@ -0,0 +1,92 @@ + + + +Command Example + + + + + +
+
+ + +
+ + diff --git a/examples/command/main.go b/examples/command/main.go new file mode 100644 index 0000000..b971fe8 --- /dev/null +++ b/examples/command/main.go @@ -0,0 +1,156 @@ +// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bufio" + "flag" + "io" + "log" + "net/http" + "os" + "os/exec" + "text/template" + "time" + + "github.com/gorilla/websocket" +) + +var ( + addr = flag.String("addr", "127.0.0.1:8080", "http service address") + homeTempl = template.Must(template.ParseFiles("home.html")) +) + +const ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Maximum message size allowed from peer. + maxMessageSize = 8192 +) + +// connection is an middleman between the websocket connection and the command. +type connection struct { + ws *websocket.Conn + stdout io.ReadCloser + stdin io.WriteCloser + cmd *exec.Cmd +} + +func (c *connection) pumpStdin() { + defer c.ws.Close() + c.ws.SetReadLimit(maxMessageSize) + for { + _, message, err := c.ws.ReadMessage() + if err != nil { + break + } + message = append(message, '\n') + if _, err := c.stdin.Write(message); err != nil { + break + } + } + c.stdin.Close() + log.Println("exit stdin pump") +} + +func (c *connection) pumpStdout() { + defer c.ws.Close() + s := bufio.NewScanner(c.stdout) + for s.Scan() { + c.ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := c.ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil { + break + } + } + if s.Err() != nil { + log.Println("scan:", s.Err()) + } + c.stdout.Close() + log.Println("exit stdout pump") +} + +func internalError(ws *websocket.Conn, fmt string, err error) { + log.Println(fmt, err) + ws.WriteMessage(websocket.TextMessage, []byte("Internal server error.")) +} + +var upgrader = websocket.Upgrader{} + +func serveWs(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + c := &connection{ + cmd: exec.Command(flag.Args()[0], flag.Args()[1:]...), + ws: ws, + } + + c.stdout, err = c.cmd.StdoutPipe() + if err != nil { + internalError(ws, "stdout: %v", err) + ws.Close() + return + } + + c.stdin, err = c.cmd.StdinPipe() + if err != nil { + internalError(ws, "stdin: %v", err) + c.stdout.Close() + if closer, ok := c.cmd.Stdout.(io.Closer); ok { + closer.Close() + } + ws.Close() + return + } + + if err := c.cmd.Start(); err != nil { + internalError(ws, "start: %v", err) + c.stdout.Close() + c.stdin.Close() + ws.Close() + return + } + + go c.pumpStdout() + c.pumpStdin() + + c.cmd.Process.Signal(os.Interrupt) + if err := c.cmd.Wait(); err != nil { + log.Println("wait:", err) + } + log.Println("exit serveWs") +} + +func serveHome(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.Error(w, "Not found", 404) + return + } + if r.Method != "GET" { + http.Error(w, "Method not allowed", 405) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + homeTempl.Execute(w, r.Host) +} + +func main() { + flag.Parse() + if len(flag.Args()) < 1 { + log.Fatal("must specify at least one argument") + } + http.HandleFunc("/", serveHome) + http.HandleFunc("/ws", serveWs) + log.Fatal(http.ListenAndServe(*addr, nil)) +}