main.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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.
  4. package main
  5. import (
  6. "bufio"
  7. "flag"
  8. "io"
  9. "log"
  10. "net/http"
  11. "os"
  12. "os/exec"
  13. "text/template"
  14. "time"
  15. "github.com/gorilla/websocket"
  16. )
  17. var (
  18. addr = flag.String("addr", "127.0.0.1:8080", "http service address")
  19. cmdPath string
  20. homeTempl = template.Must(template.ParseFiles("home.html"))
  21. )
  22. const (
  23. // Time allowed to write a message to the peer.
  24. writeWait = 10 * time.Second
  25. // Maximum message size allowed from peer.
  26. maxMessageSize = 8192
  27. )
  28. func pumpStdin(ws *websocket.Conn, w io.Writer) {
  29. defer ws.Close()
  30. ws.SetReadLimit(maxMessageSize)
  31. for {
  32. _, message, err := ws.ReadMessage()
  33. if err != nil {
  34. break
  35. }
  36. message = append(message, '\n')
  37. if _, err := w.Write(message); err != nil {
  38. break
  39. }
  40. }
  41. log.Println("exit stdin pump")
  42. }
  43. func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) {
  44. defer ws.Close()
  45. s := bufio.NewScanner(r)
  46. for s.Scan() {
  47. ws.SetWriteDeadline(time.Now().Add(writeWait))
  48. if err := ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
  49. break
  50. }
  51. }
  52. if s.Err() != nil {
  53. log.Println("scan:", s.Err())
  54. }
  55. close(done)
  56. log.Println("exit stdout pump")
  57. }
  58. func internalError(ws *websocket.Conn, msg string, err error) {
  59. log.Println(msg, err)
  60. ws.WriteMessage(websocket.TextMessage, []byte("Internal server error."))
  61. }
  62. var upgrader = websocket.Upgrader{}
  63. func serveWs(w http.ResponseWriter, r *http.Request) {
  64. if r.Method != "GET" {
  65. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  66. return
  67. }
  68. ws, err := upgrader.Upgrade(w, r, nil)
  69. if err != nil {
  70. log.Println("upgrade:", err)
  71. return
  72. }
  73. defer ws.Close()
  74. outr, outw, err := os.Pipe()
  75. if err != nil {
  76. internalError(ws, "stdout:", err)
  77. return
  78. }
  79. defer outr.Close()
  80. defer outw.Close()
  81. inr, inw, err := os.Pipe()
  82. if err != nil {
  83. internalError(ws, "stdin:", err)
  84. return
  85. }
  86. defer inr.Close()
  87. defer inw.Close()
  88. proc, err := os.StartProcess(cmdPath, flag.Args(), &os.ProcAttr{
  89. Files: []*os.File{inr, outw, outw},
  90. })
  91. if err != nil {
  92. internalError(ws, "start:", err)
  93. return
  94. }
  95. inr.Close()
  96. outw.Close()
  97. done := make(chan struct{})
  98. go pumpStdout(ws, outr, done)
  99. pumpStdin(ws, inw)
  100. // Some commands will exit when stdin is closed.
  101. inw.Close()
  102. // Other comamnds need a bonk on the head.
  103. if err := proc.Signal(os.Interrupt); err != nil {
  104. log.Println("inter:", err)
  105. }
  106. select {
  107. case <-done:
  108. case <-time.After(time.Second):
  109. // A bigger bonk on the head.
  110. if err := proc.Signal(os.Kill); err != nil {
  111. log.Println("term:", err)
  112. }
  113. <-done
  114. }
  115. if _, err := proc.Wait(); err != nil {
  116. log.Println("wait:", err)
  117. }
  118. log.Println("exiting handler")
  119. }
  120. func serveHome(w http.ResponseWriter, r *http.Request) {
  121. if r.URL.Path != "/" {
  122. http.Error(w, "Not found", 404)
  123. return
  124. }
  125. if r.Method != "GET" {
  126. http.Error(w, "Method not allowed", 405)
  127. return
  128. }
  129. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  130. homeTempl.Execute(w, r.Host)
  131. }
  132. func main() {
  133. flag.Parse()
  134. if len(flag.Args()) < 1 {
  135. log.Fatal("must specify at least one argument")
  136. }
  137. var err error
  138. cmdPath, err = exec.LookPath(flag.Args()[0])
  139. if err != nil {
  140. log.Fatal(err)
  141. }
  142. http.HandleFunc("/", serveHome)
  143. http.HandleFunc("/ws", serveWs)
  144. log.Fatal(http.ListenAndServe(*addr, nil))
  145. }