// 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") cmdPath string 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 ) func pumpStdin(ws *websocket.Conn, w io.Writer) { defer ws.Close() ws.SetReadLimit(maxMessageSize) for { _, message, err := ws.ReadMessage() if err != nil { break } message = append(message, '\n') if _, err := w.Write(message); err != nil { break } } log.Println("exit stdin pump") } func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) { defer ws.Close() s := bufio.NewScanner(r) for s.Scan() { ws.SetWriteDeadline(time.Now().Add(writeWait)) if err := ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil { break } } if s.Err() != nil { log.Println("scan:", s.Err()) } close(done) log.Println("exit stdout pump") } func internalError(ws *websocket.Conn, msg string, err error) { log.Println(msg, 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("upgrade:", err) return } defer ws.Close() outr, outw, err := os.Pipe() if err != nil { internalError(ws, "stdout:", err) return } defer outr.Close() defer outw.Close() inr, inw, err := os.Pipe() if err != nil { internalError(ws, "stdin:", err) return } defer inr.Close() defer inw.Close() proc, err := os.StartProcess(cmdPath, flag.Args(), &os.ProcAttr{ Files: []*os.File{inr, outw, outw}, }) if err != nil { internalError(ws, "start:", err) return } inr.Close() outw.Close() done := make(chan struct{}) go pumpStdout(ws, outr, done) pumpStdin(ws, inw) // Some commands will exit when stdin is closed. inw.Close() // Other comamnds need a bonk on the head. if err := proc.Signal(os.Interrupt); err != nil { log.Println("inter:", err) } select { case <-done: case <-time.After(time.Second): // A bigger bonk on the head. if err := proc.Signal(os.Kill); err != nil { log.Println("term:", err) } <-done } if _, err := proc.Wait(); err != nil { log.Println("wait:", err) } log.Println("exiting handler") } 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") } var err error cmdPath, err = exec.LookPath(flag.Args()[0]) if err != nil { log.Fatal(err) } http.HandleFunc("/", serveHome) http.HandleFunc("/ws", serveWs) log.Fatal(http.ListenAndServe(*addr, nil)) }