1 // Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
17 "github.com/gorilla/websocket"
21 addr = flag.String("addr", "127.0.0.1:8080", "http service address")
26 // Time allowed to write a message to the peer.
27 writeWait = 10 * time.Second
29 // Maximum message size allowed from peer.
32 // Time allowed to read the next pong message from the peer.
33 pongWait = 60 * time.Second
35 // Send pings to peer with this period. Must be less than pongWait.
36 pingPeriod = (pongWait * 9) / 10
38 // Time to wait before force close on connection.
39 closeGracePeriod = 10 * time.Second
42 func pumpStdin(ws *websocket.Conn, w io.Writer) {
44 ws.SetReadLimit(maxMessageSize)
45 ws.SetReadDeadline(time.Now().Add(pongWait))
46 ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
48 _, message, err := ws.ReadMessage()
52 message = append(message, '\n')
53 if _, err := w.Write(message); err != nil {
59 func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) {
62 s := bufio.NewScanner(r)
64 ws.SetWriteDeadline(time.Now().Add(writeWait))
65 if err := ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
71 log.Println("scan:", s.Err())
75 ws.SetWriteDeadline(time.Now().Add(writeWait))
76 ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
77 time.Sleep(closeGracePeriod)
81 func ping(ws *websocket.Conn, done chan struct{}) {
82 ticker := time.NewTicker(pingPeriod)
87 if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil {
88 log.Println("ping:", err)
96 func internalError(ws *websocket.Conn, msg string, err error) {
98 ws.WriteMessage(websocket.TextMessage, []byte("Internal server error."))
101 var upgrader = websocket.Upgrader{}
103 func serveWs(w http.ResponseWriter, r *http.Request) {
104 ws, err := upgrader.Upgrade(w, r, nil)
106 log.Println("upgrade:", err)
112 outr, outw, err := os.Pipe()
114 internalError(ws, "stdout:", err)
120 inr, inw, err := os.Pipe()
122 internalError(ws, "stdin:", err)
128 proc, err := os.StartProcess(cmdPath, flag.Args(), &os.ProcAttr{
129 Files: []*os.File{inr, outw, outw},
132 internalError(ws, "start:", err)
139 stdoutDone := make(chan struct{})
140 go pumpStdout(ws, outr, stdoutDone)
141 go ping(ws, stdoutDone)
145 // Some commands will exit when stdin is closed.
148 // Other commands need a bonk on the head.
149 if err := proc.Signal(os.Interrupt); err != nil {
150 log.Println("inter:", err)
155 case <-time.After(time.Second):
156 // A bigger bonk on the head.
157 if err := proc.Signal(os.Kill); err != nil {
158 log.Println("term:", err)
163 if _, err := proc.Wait(); err != nil {
164 log.Println("wait:", err)
168 func serveHome(w http.ResponseWriter, r *http.Request) {
169 if r.URL.Path != "/" {
170 http.Error(w, "Not found", http.StatusNotFound)
173 if r.Method != "GET" {
174 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
177 http.ServeFile(w, r, "home.html")
182 if len(flag.Args()) < 1 {
183 log.Fatal("must specify at least one argument")
186 cmdPath, err = exec.LookPath(flag.Args()[0])
190 http.HandleFunc("/", serveHome)
191 http.HandleFunc("/ws", serveWs)
192 log.Fatal(http.ListenAndServe(*addr, nil))