135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
DEFAULT_CODE_PATH = "/userfunc/user"
|
|
DEFAULT_INTERNAL_CODE_PATH = "/bin/userfunc"
|
|
)
|
|
|
|
var specialized bool
|
|
|
|
type BinaryServer struct {
|
|
fetchedCodePath string
|
|
internalCodePath string
|
|
}
|
|
|
|
func (bs *BinaryServer) SpecializeHandler(w http.ResponseWriter, r *http.Request) {
|
|
if specialized {
|
|
w.WriteHeader(400)
|
|
w.Write([]byte("Not a generic container"))
|
|
return
|
|
}
|
|
|
|
_, err := os.Stat(bs.fetchedCodePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
w.Write([]byte(bs.fetchedCodePath + ": not found"))
|
|
return
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// Future: Check if executable is correct architecture/executable.
|
|
|
|
// Copy the executable to ensure that file is executable and immutable.
|
|
userFunc, err := ioutil.ReadFile(bs.fetchedCodePath)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("Failed to read executable."))
|
|
return
|
|
}
|
|
err = ioutil.WriteFile(bs.internalCodePath, userFunc, 0555)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("Failed to write executable to target location."))
|
|
return
|
|
}
|
|
|
|
fmt.Println("Specializing ...")
|
|
specialized = true
|
|
fmt.Println("Done")
|
|
}
|
|
|
|
func (bs *BinaryServer) InvocationHandler(w http.ResponseWriter, r *http.Request) {
|
|
if !specialized {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("Generic container: no requests supported"))
|
|
return
|
|
}
|
|
|
|
// CGI-like passing of environment variables
|
|
execEnv := NewEnv(nil)
|
|
execEnv.SetEnv(&EnvVar{"REQUEST_METHOD", r.Method})
|
|
execEnv.SetEnv(&EnvVar{"REQUEST_URI", r.RequestURI})
|
|
execEnv.SetEnv(&EnvVar{"CONTENT_LENGTH", fmt.Sprintf("%d", r.ContentLength)})
|
|
|
|
for header, val := range r.Header {
|
|
execEnv.SetEnv(&EnvVar{fmt.Sprintf("HTTP_%s", strings.ToUpper(header)), val[0]})
|
|
}
|
|
|
|
// Future: could be improved by keeping subprocess open while environment is specialized
|
|
cmd := exec.Command(bs.internalCodePath)
|
|
cmd.Env = execEnv.ToStringEnv()
|
|
|
|
if r.ContentLength != 0 {
|
|
fmt.Println(r.ContentLength)
|
|
stdin, err := cmd.StdinPipe()
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte(fmt.Sprintf("Failed to get STDIN pipe: %s", err)))
|
|
panic(err)
|
|
}
|
|
_, err = io.Copy(stdin, r.Body)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte(fmt.Sprintf("Failed to pipe input: %s", err)))
|
|
}
|
|
stdin.Close()
|
|
}
|
|
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte(fmt.Sprintf("Function error: %s", err)))
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(out)
|
|
}
|
|
|
|
func main() {
|
|
codePath := flag.String("c", DEFAULT_CODE_PATH, "Path to expected fetched executable.")
|
|
internalCodePath := flag.String("i", DEFAULT_INTERNAL_CODE_PATH, "Path to specialized executable.")
|
|
flag.Parse()
|
|
absInternalCodePath, err := filepath.Abs(*internalCodePath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Printf("Using fetched code path: %s\n", *codePath)
|
|
fmt.Printf("Using internal code path: %s\n", absInternalCodePath)
|
|
|
|
server := &BinaryServer{*codePath, absInternalCodePath}
|
|
http.HandleFunc("/", server.InvocationHandler)
|
|
http.HandleFunc("/specialize", server.SpecializeHandler)
|
|
|
|
fmt.Println("Listening on 8888 ...")
|
|
err = http.ListenAndServe(":8888", nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|