1
2
3
4
5 package main
6
7 import (
8 "bufio"
9 "flag"
10 "io"
11 "log"
12 "net/http"
13 "os"
14 "os/exec"
15 "time"
16
17 "github.com/gorilla/websocket"
18 )
19
20 var (
21 addr = flag.String("addr", "127.0.0.1:8080", "http service address")
22 cmdPath string
23 )
24
25 const (
26
27 writeWait = 10 * time.Second
28
29
30 maxMessageSize = 8192
31
32
33 pongWait = 60 * time.Second
34
35
36 pingPeriod = (pongWait * 9) / 10
37
38
39 closeGracePeriod = 10 * time.Second
40 )
41
42 func pumpStdin(ws *websocket.Conn, w io.Writer) {
43 defer ws.Close()
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 })
47 for {
48 _, message, err := ws.ReadMessage()
49 if err != nil {
50 break
51 }
52 message = append(message, '\n')
53 if _, err := w.Write(message); err != nil {
54 break
55 }
56 }
57 }
58
59 func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) {
60 defer func() {
61 }()
62 s := bufio.NewScanner(r)
63 for s.Scan() {
64 ws.SetWriteDeadline(time.Now().Add(writeWait))
65 if err := ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
66 ws.Close()
67 break
68 }
69 }
70 if s.Err() != nil {
71 log.Println("scan:", s.Err())
72 }
73 close(done)
74
75 ws.SetWriteDeadline(time.Now().Add(writeWait))
76 ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
77 time.Sleep(closeGracePeriod)
78 ws.Close()
79 }
80
81 func ping(ws *websocket.Conn, done chan struct{}) {
82 ticker := time.NewTicker(pingPeriod)
83 defer ticker.Stop()
84 for {
85 select {
86 case <-ticker.C:
87 if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil {
88 log.Println("ping:", err)
89 }
90 case <-done:
91 return
92 }
93 }
94 }
95
96 func internalError(ws *websocket.Conn, msg string, err error) {
97 log.Println(msg, err)
98 ws.WriteMessage(websocket.TextMessage, []byte("Internal server error."))
99 }
100
101 var upgrader = websocket.Upgrader{}
102
103 func serveWs(w http.ResponseWriter, r *http.Request) {
104 ws, err := upgrader.Upgrade(w, r, nil)
105 if err != nil {
106 log.Println("upgrade:", err)
107 return
108 }
109
110 defer ws.Close()
111
112 outr, outw, err := os.Pipe()
113 if err != nil {
114 internalError(ws, "stdout:", err)
115 return
116 }
117 defer outr.Close()
118 defer outw.Close()
119
120 inr, inw, err := os.Pipe()
121 if err != nil {
122 internalError(ws, "stdin:", err)
123 return
124 }
125 defer inr.Close()
126 defer inw.Close()
127
128 proc, err := os.StartProcess(cmdPath, flag.Args(), &os.ProcAttr{
129 Files: []*os.File{inr, outw, outw},
130 })
131 if err != nil {
132 internalError(ws, "start:", err)
133 return
134 }
135
136 inr.Close()
137 outw.Close()
138
139 stdoutDone := make(chan struct{})
140 go pumpStdout(ws, outr, stdoutDone)
141 go ping(ws, stdoutDone)
142
143 pumpStdin(ws, inw)
144
145
146 inw.Close()
147
148
149 if err := proc.Signal(os.Interrupt); err != nil {
150 log.Println("inter:", err)
151 }
152
153 select {
154 case <-stdoutDone:
155 case <-time.After(time.Second):
156
157 if err := proc.Signal(os.Kill); err != nil {
158 log.Println("term:", err)
159 }
160 <-stdoutDone
161 }
162
163 if _, err := proc.Wait(); err != nil {
164 log.Println("wait:", err)
165 }
166 }
167
168 func serveHome(w http.ResponseWriter, r *http.Request) {
169 if r.URL.Path != "/" {
170 http.Error(w, "Not found", http.StatusNotFound)
171 return
172 }
173 if r.Method != http.MethodGet {
174 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
175 return
176 }
177 http.ServeFile(w, r, "home.html")
178 }
179
180 func main() {
181 flag.Parse()
182 if len(flag.Args()) < 1 {
183 log.Fatal("must specify at least one argument")
184 }
185 var err error
186 cmdPath, err = exec.LookPath(flag.Args()[0])
187 if err != nil {
188 log.Fatal(err)
189 }
190 http.HandleFunc("/", serveHome)
191 http.HandleFunc("/ws", serveWs)
192 log.Fatal(http.ListenAndServe(*addr, nil))
193 }
194
View as plain text