package server import ( "context" "io/ioutil" "log" "net/http" "os" "os/exec" "strconv" "strings" "time" "nano-run/server/api" ) type markerResponse struct { dataSent bool res http.ResponseWriter } func (m *markerResponse) Header() http.Header { return m.res.Header() } func (m *markerResponse) Write(bytes []byte) (int, error) { m.dataSent = true return m.res.Write(bytes) } func (m *markerResponse) WriteHeader(statusCode int) { m.dataSent = true m.res.WriteHeader(statusCode) } type binHandler struct { command string workDir string shell string environment []string timeout time.Duration gracefulTimeout time.Duration } func (bh *binHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { marker := &markerResponse{res: writer} ctx := request.Context() if bh.timeout > 0 { c, cancel := context.WithTimeout(ctx, bh.timeout) defer cancel() ctx = c } cmd := exec.CommandContext(ctx, bh.shell, "-c", bh.command) //nolint:gosec if bh.workDir == "" { tmpDir, err := ioutil.TempDir("", "") if err != nil { http.Error(writer, err.Error(), http.StatusInternalServerError) return } defer os.RemoveAll(tmpDir) cmd.Dir = tmpDir } else { cmd.Dir = bh.workDir } var env = bh.cloneEnv() for k, v := range request.Header { ke := strings.ToUpper(strings.Replace(k, "-", "_", -1)) env = append(env, ke+"="+strings.Join(v, ",")) } cmd.Stderr = os.Stderr cmd.Stdin = request.Body cmd.Stdout = marker cmd.Env = env api.SetBinFlags(cmd) var done bool if bh.gracefulTimeout > 0 { graceCtx, graceCancel := context.WithTimeout(ctx, bh.gracefulTimeout) defer graceCancel() go func() { <-graceCtx.Done() proc := cmd.Process if proc == nil || done { return } err := proc.Signal(os.Interrupt) if err != nil { log.Println("failed send signal to process:", err) } else { log.Println("sent graceful shutdown to proces") } }() } err := cmd.Run() done = true if codeReset, ok := writer.(interface{ Status(status int) }); ok && err != nil { codeReset.Status(http.StatusBadGateway) } if err != nil { writer.Header().Set("X-Return-Code", strconv.Itoa(cmd.ProcessState.ExitCode())) writer.WriteHeader(http.StatusBadGateway) } else { writer.Header().Set("X-Return-Code", strconv.Itoa(cmd.ProcessState.ExitCode())) writer.WriteHeader(http.StatusNoContent) } } func (bh *binHandler) cloneEnv() []string { var cp = make([]string, len(bh.environment)) copy(cp, bh.environment) return cp }