122 lines
2.5 KiB
Go
122 lines
2.5 KiB
Go
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
|
|
}
|