github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/web/web.go (about) 1 // Package web is the entry point for the backend of the web interface of 2 // Elvish. 3 package web 4 5 //go:generate ./embed-html 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "log" 13 "net/http" 14 "os" 15 16 "github.com/elves/elvish/eval" 17 "github.com/elves/elvish/parse" 18 ) 19 20 type Web struct { 21 ev *eval.Evaler 22 port int 23 } 24 25 type ExecuteResponse struct { 26 OutBytes string 27 OutValues []eval.Value 28 ErrBytes string 29 Err string 30 } 31 32 func NewWeb(ev *eval.Evaler, port int) *Web { 33 return &Web{ev, port} 34 } 35 36 func (web *Web) Run(args []string) int { 37 if len(args) > 0 { 38 fmt.Fprintln(os.Stderr, "arguments to -web are not supported yet") 39 return 2 40 } 41 42 http.HandleFunc("/", web.handleMainPage) 43 http.HandleFunc("/execute", web.handleExecute) 44 addr := fmt.Sprintf("localhost:%d", web.port) 45 log.Println("going to listen", addr) 46 err := http.ListenAndServe(addr, nil) 47 48 log.Println(err) 49 return 0 50 } 51 52 func (web *Web) handleMainPage(w http.ResponseWriter, r *http.Request) { 53 _, err := w.Write([]byte(mainPageHTML)) 54 if err != nil { 55 log.Println("cannot write response:", err) 56 } 57 } 58 59 func (web *Web) handleExecute(w http.ResponseWriter, r *http.Request) { 60 bytes, err := ioutil.ReadAll(r.Body) 61 if err != nil { 62 log.Println("cannot read request body:", err) 63 return 64 } 65 text := string(bytes) 66 67 outBytes, outValues, errBytes, err := evalAndCollect(web.ev, "<web>", text) 68 errText := "" 69 if err != nil { 70 errText = err.Error() 71 } 72 responseBody, err := json.Marshal( 73 &ExecuteResponse{string(outBytes), outValues, string(errBytes), errText}) 74 if err != nil { 75 log.Println("cannot marshal response body:", err) 76 } 77 78 _, err = w.Write(responseBody) 79 if err != nil { 80 log.Println("cannot write response:", err) 81 } 82 } 83 84 const ( 85 outFileBufferSize = 1024 86 outChanBufferSize = 32 87 ) 88 89 // evalAndCollect evaluates a piece of code with null stdin, and stdout and 90 // stderr connected to pipes (value part of stderr being a blackhole), and 91 // return the results collected on stdout and stderr, and the possible error 92 // that occurred. 93 func evalAndCollect(ev *eval.Evaler, name, text string) ( 94 outBytes []byte, outValues []eval.Value, errBytes []byte, err error) { 95 96 node, err := parse.Parse(name, text) 97 if err != nil { 98 return 99 } 100 op, err := ev.Compile(node, name, text) 101 if err != nil { 102 return 103 } 104 105 outFile, chanOutBytes := makeBytesWriterAndCollect() 106 outChan, chanOutValues := makeValuesWriterAndCollect() 107 errFile, chanErrBytes := makeBytesWriterAndCollect() 108 109 ports := []*eval.Port{ 110 eval.DevNullClosedChan, 111 {File: outFile, Chan: outChan}, 112 {File: errFile, Chan: eval.BlackholeChan}, 113 } 114 err = ev.EvalWithPorts(ports, op, name, text) 115 116 outFile.Close() 117 close(outChan) 118 errFile.Close() 119 return <-chanOutBytes, <-chanOutValues, <-chanErrBytes, err 120 } 121 122 // makeBytesWriterAndCollect makes an in-memory file that can be written to, and 123 // the written bytes will be collected in a byte slice that will be put on a 124 // channel as soon as the writer is closed. 125 func makeBytesWriterAndCollect() (*os.File, <-chan []byte) { 126 r, w, err := os.Pipe() 127 // os.Pipe returns error only on resource exhaustion. 128 if err != nil { 129 panic(err) 130 } 131 chanCollected := make(chan []byte) 132 133 go func() { 134 var ( 135 collected []byte 136 buf [outFileBufferSize]byte 137 ) 138 for { 139 n, err := r.Read(buf[:]) 140 collected = append(collected, buf[:n]...) 141 if err != nil { 142 if err != io.EOF { 143 log.Println("error when reading output pipe:", err) 144 } 145 break 146 } 147 } 148 r.Close() 149 chanCollected <- collected 150 }() 151 152 return w, chanCollected 153 } 154 155 // makeValuesWriterAndCollect makes a Value channel for writing, and the written 156 // values will be collected in a Value slice that will be put on a channel as 157 // soon as the writer is closed. 158 func makeValuesWriterAndCollect() (chan eval.Value, <-chan []eval.Value) { 159 chanValues := make(chan eval.Value, outChanBufferSize) 160 chanCollected := make(chan []eval.Value) 161 162 go func() { 163 var collected []eval.Value 164 for { 165 for v := range chanValues { 166 collected = append(collected, v) 167 } 168 chanCollected <- collected 169 } 170 }() 171 172 return chanValues, chanCollected 173 }