nano-run/server/mode_bin.go
2020-10-09 19:37:52 +08:00

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
}