Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
175 lines (150 sloc) 4.07 KB
// Package web is the entry point for the backend of the web interface of
// Elvish.
package web
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/elves/elvish/pkg/eval"
"github.com/elves/elvish/pkg/parse"
"github.com/elves/elvish/pkg/program/shell"
)
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 *Web) Main(fds [3]*os.File, _ []string) int {
ev, _ := shell.InitRuntime(web.BinPath, web.SockPath, web.DbPath)
defer shell.CleanupRuntime(ev)
h := httpHandler{ev}
http.HandleFunc("/", h.handleMainPage)
http.HandleFunc("/execute", h.handleExecute)
addr := fmt.Sprintf("localhost:%d", web.Port)
log.Println("going to listen", addr)
err := http.ListenAndServe(addr, nil)
log.Println(err)
return 0
}
func (h httpHandler) handleMainPage(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(mainPageHTML))
if err != nil {
log.Println("cannot write response:", err)
}
}
func (h httpHandler) handleExecute(w http.ResponseWriter, r *http.Request) {
bytes, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Println("cannot read request body:", err)
return
}
code := string(bytes)
outBytes, outValues, errBytes, err := evalAndCollect(h.ev, code)
errText := ""
if err != nil {
errText = err.Error()
}
responseBody, err := json.Marshal(
&ExecuteResponse{string(outBytes), outValues, string(errBytes), errText})
if err != nil {
log.Println("cannot marshal response body:", err)
}
_, err = w.Write(responseBody)
if err != nil {
log.Println("cannot write response:", err)
}
}
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 evalAndCollect(ev *eval.Evaler, code string) (
outBytes []byte, outValues []interface{}, errBytes []byte, err error) {
node, err := parse.AsChunk("[web]", code)
if err != nil {
return
}
src := eval.NewInteractiveSource(code)
op, err := ev.Compile(node, src)
if err != nil {
return
}
outFile, chanOutBytes := makeBytesWriterAndCollect()
outChan, chanOutValues := makeValuesWriterAndCollect()
errFile, chanErrBytes := makeBytesWriterAndCollect()
ports := []*eval.Port{
eval.DevNullClosedChan,
{File: outFile, Chan: outChan},
{File: errFile, Chan: eval.BlackholeChan},
}
err = ev.Eval(op, ports)
outFile.Close()
close(outChan)
errFile.Close()
return <-chanOutBytes, <-chanOutValues, <-chanErrBytes, err
}
// 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 makeBytesWriterAndCollect() (*os.File, <-chan []byte) {
r, w, err := os.Pipe()
// os.Pipe returns error only on resource exhaustion.
if err != nil {
panic(err)
}
chanCollected := make(chan []byte)
go func() {
var (
collected []byte
buf [outFileBufferSize]byte
)
for {
n, err := r.Read(buf[:])
collected = append(collected, buf[:n]...)
if err != nil {
if err != io.EOF {
log.Println("error when reading output pipe:", err)
}
break
}
}
r.Close()
chanCollected <- collected
}()
return w, chanCollected
}
// 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 makeValuesWriterAndCollect() (chan interface{}, <-chan []interface{}) {
chanValues := make(chan interface{}, outChanBufferSize)
chanCollected := make(chan []interface{})
go func() {
var collected []interface{}
for {
for v := range chanValues {
collected = append(collected, v)
}
chanCollected <- collected
}
}()
return chanValues, chanCollected
}
You can’t perform that action at this time.