package main import ( "bufio" "net/http" "net/url" "os" "os/exec" "os/signal" "strings" "syscall" "time" ) const junkaddr = "localhost:18012" const proxybufsize = 4 << 10 type JunkConf struct { Proxy string Cmd *exec.Cmd } func main() { status := cmain(len(os.Args), os.Args) os.Setenv("status", status) if status != "" { os.Exit(1) } } func proxy(w http.ResponseWriter, r *http.Request, proxy_path string) { var err error r.RequestURI = "" r.URL, err = url.Parse(proxy_path) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } resp, err := http.DefaultClient.Do(r) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } for k, v := range resp.Header { for _, x := range v { w.Header().Add(k, x) } } w.WriteHeader(resp.StatusCode) buf := make([]byte, proxybufsize) for { n, err := resp.Body.Read(buf) w.Write(buf[:n]) if err != nil { break } } } func cmain(argc int, argv []string) string { fileserver := http.FileServer(http.Dir("")) cmdmap := map[string]JunkConf{} http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { p1 := r.URL.Path p := strings.Split(p1, "/")[1:] seg := p[0] if seg == "" { w.Header().Add("Set-Cookie", "view=; Path=/; SameSite=Strict; HttpOnly") w.Header().Add("Cache-Control", "no-store") fileserver.ServeHTTP(w, r) return } vc, err := r.Cookie("view") if err == nil && seg != vc.Value && vc.Value != "" { w.Header().Add("Location", "/"+vc.Value+p1) w.WriteHeader(http.StatusFound) return } if conf, ok := cmdmap[seg]; ok { if err != nil || vc.Value == "" { w.Header().Add("Set-Cookie", "view="+seg+"; Path=/; SameSite=Strict; HttpOnly") } proxy_path := conf.Proxy + "/" + strings.Join(p[1:], "/") proxy(w, r, proxy_path) return } if f, err := os.Open(seg + "/junkrc"); err == nil { // info, err := f.Stat() s := bufio.NewScanner(f) conf := JunkConf{} for s.Scan() { line := s.Text() if len(line) > 2 && line[0:2] == "#;" { note := line[2:] i := strings.IndexByte(note, '=') if i == -1 { continue } key, value := note[:i], note[i+1:] switch key { case "proxy": conf.Proxy = value } } } f.Close() println("exec:", seg+"/junkrc") cmd := exec.Command("./junkrc") cmd.Dir = seg // cmd.Stdout = os.Stdout conf.Cmd = cmd err = cmd.Start() if err != nil { w.WriteHeader(http.StatusServiceUnavailable) w.Write([]byte(err.Error())) return } waits := 0 for { _, err := http.Head(conf.Proxy) if err != nil { if cmd.ProcessState != nil { cmd.Wait() w.WriteHeader(http.StatusServiceUnavailable) w.Write([]byte("process exited early: " + cmd.ProcessState.String())) return } else { waits++ time.Sleep(500 * time.Millisecond) if waits > 10 { w.WriteHeader(http.StatusServiceUnavailable) w.Write([]byte("starting process: timeout")) cmd.Process.Kill() cmd.Wait() return } continue } } cmdmap[seg] = conf break } w.Header().Add("Set-Cookie", "view="+seg+"; Path=/; SameSite=Strict; HttpOnly") w.Header().Add("Location", p1) w.WriteHeader(http.StatusFound) // proxy_path := conf.Proxy + "/" + strings.Join(p[1:], "/") // proxy(w, r, proxy_path) return } fileserver.ServeHTTP(w, r) }) lock := make(chan os.Signal, 1) signal.Notify(lock, os.Interrupt, syscall.SIGTERM) go func() { println("started:", "http://"+junkaddr+"/") http.ListenAndServe(junkaddr, nil) }() <-lock for k, v := range cmdmap { println("killing:", k) v.Cmd.Process.Kill() v.Cmd.Wait() } return "" }