// Package web is the entry point for the backend of the web interface of // Elvish.
package web import ( ) // Program is the web subprogram. var Program prog.Program = program{} type program struct{} func (program) ( *prog.Flags) bool { return .Web } func (program) ( [3]*os.File, *prog.Flags, []string) error { if len() > 0 { return prog.BadUsage("arguments are not allowed with -web") } if .CodeInArg { return prog.BadUsage("-c cannot be used together with -web") } := Web{BinPath: .Bin, SockPath: .Sock, DbPath: .DB, Port: .Port} return .Main(, nil) } type Web struct { BinPath string SockPath string DbPath string Port int } type httpHandler struct { ev *eval.Evaler } type ExecuteResponse struct { OutBytes string OutValues []interface{} ErrBytes string Err string } func ( *Web) ( [3]*os.File, []string) error { := shell.MakePaths([2], shell.Paths{Bin: .BinPath, Sock: .SockPath, Db: .DbPath}) := shell.InitRuntime([2], , true) defer shell.CleanupRuntime([2], ) := httpHandler{} http.HandleFunc("/", .handleMainPage) http.HandleFunc("/execute", .handleExecute) := fmt.Sprintf("localhost:%d", .Port) log.Println("going to listen", ) := http.ListenAndServe(, nil) log.Println() return nil } func ( httpHandler) ( http.ResponseWriter, *http.Request) { , := .Write([]byte(mainPageHTML)) if != nil { log.Println("cannot write response:", ) } } func ( httpHandler) ( http.ResponseWriter, *http.Request) { , := ioutil.ReadAll(.Body) if != nil { log.Println("cannot read request body:", ) return } := string() , , , := evalAndCollect(.ev, ) := "" if != nil { = .Error() } , := json.Marshal( &ExecuteResponse{string(), , string(), }) if != nil { log.Println("cannot marshal response body:", ) } _, = .Write() if != nil { log.Println("cannot write response:", ) } } const ( outFileBufferSize = 1024 outChanBufferSize = 32 ) // evalAndCollect evaluates a piece of code with null stdin, and stdout and // stderr connected to pipes (value part of stderr being a blackhole), and // return the results collected on stdout and stderr, and the possible error // that occurred. func ( *eval.Evaler, string) ( []byte, []interface{}, []byte, error) { , := makeBytesWriterAndCollect() , := makeValuesWriterAndCollect() , := makeBytesWriterAndCollect() := []*eval.Port{ eval.DummyInputPort, {File: , Chan: }, {File: , Chan: eval.BlackholeChan}, } = .Eval( parse.Source{Name: "[web]", Code: }, eval.EvalCfg{Ports: }) .Close() close() .Close() return <-, <-, <-, } // makeBytesWriterAndCollect makes an in-memory file that can be written to, and // the written bytes will be collected in a byte slice that will be put on a // channel as soon as the writer is closed. func () (*os.File, <-chan []byte) { , , := os.Pipe() // os.Pipe returns error only on resource exhaustion. if != nil { panic() } := make(chan []byte) go func() { var ( []byte [outFileBufferSize]byte ) for { , := .Read([:]) = append(, [:]...) if != nil { if != io.EOF { log.Println("error when reading output pipe:", ) } break } } .Close() <- }() return , } // makeValuesWriterAndCollect makes a Value channel for writing, and the written // values will be collected in a Value slice that will be put on a channel as // soon as the writer is closed. func () (chan interface{}, <-chan []interface{}) { := make(chan interface{}, outChanBufferSize) := make(chan []interface{}) go func() { var []interface{} for { for := range { = append(, ) } <- } }() return , }