在shell命令完成之前,阻止发送HTTP响应

时间:2017-09-21 09:05:43

标签: http go exec

想法:有一个Web服务器准备好接收将触发服务器上的命令/测试执行的消息。我从一个简单的案例开始,执行一个简单的ping。下面的代码处理发送到POST的{​​{1}}消息,其中包含以下json格式:

/ping

然后,服务器将运行命令{ "ip": "valid_ip_addr", "count": "4" }

期望的结果:如果该命令可以ping -c 4 valid_ip_address发送回.Start()。如果有问题,请发回错误消息。

问题:我在检查200 OK没有出现任何错误后立即发送200 OK响应,但是在命令完成后才收到此消息。

代码:有三个功能:.Start()main()handler()。问题发生在最后一个。

ping()

卷曲测试

package main

import (
    "bufio"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "os/exec"
)

var err error

type Ping struct {
    Count string `json:"count"`
    Ip    string `json:"ip"`
}

func main() {
    http.HandleFunc("/ping", handler)
    http.ListenAndServe(":5050", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {

    switch r.Method {
    case "POST":

        p := Ping{}

        err := json.NewDecoder(r.Body).Decode(&p)
        if err != nil {
            fmt.Printf("400 Bad request. Problem decoding the received json.\nDetails:\n%s\n", err.Error())
            http.Error(w, err.Error(), 400)
            return
        }

        fmt.Println("POST /ping ", p)
        ping(w, p)

    default:
        http.Error(w, "Only POST is accepted.", 501)
    }
}

func ping(w http.ResponseWriter, a Ping) {

    cmdName := "ping"
    cmdArgs := []string{"-c", a.Count, a.Ip}

    cmd := exec.Command(cmdName, cmdArgs...)
    cmdReader, err := cmd.StdoutPipe()
    if err != nil {
        fmt.Fprintln(os.Stderr, "Error creating StdoutPipe for Cmd", err)
        http.Error(w, "Error creating StdoutPipe for Cmd\n"+err.Error(), 500)
        return
    }

    // the following is used to print output of the command
    // as it makes progress...
    scanner := bufio.NewScanner(cmdReader)
    go func() {
        for scanner.Scan() {
            fmt.Printf("%s\n", scanner.Text())
            //
            // TODO:
            // send output to server
        }
    }()

    err = cmd.Start()
    if err != nil {
        fmt.Fprintln(os.Stderr, "Error starting Cmd", err)
        http.Error(w, "Error starting Cmd\n"+err.Error(), 500)
        return
    }

    // send 200 OK
    fmt.Fprintf(w, "ping started")

    err = cmd.Wait()
    if err != nil {
        fmt.Fprintln(os.Stderr, "Error waiting for Cmd", err)
    }

}

1 个答案:

答案 0 :(得分:1)

我建议使用select超时等待错误。请查看以下代码。

func ping(w http.ResponseWriter, a Ping) {

    cmdName := "ping"
    cmdArgs := []string{"-c", a.Count, a.Ip}

    cmd := exec.Command(cmdName, cmdArgs...)
    cmdReader, err := cmd.StdoutPipe()
    if err != nil {
        fmt.Fprintln(os.Stderr, "Error creating StdoutPipe for Cmd", err)
        http.Error(w, "Error creating StdoutPipe for Cmd\n"+err.Error(), 500)
        return
    }

    // the following is used to print output of the command
    // as it makes progress...
    scanner := bufio.NewScanner(cmdReader)
    go func() {
        for scanner.Scan() {
            fmt.Printf("%s\n", scanner.Text())
            //
            // TODO:
            // send output to server
        }
    }()

    err = cmd.Start()
    if err != nil {
        fmt.Fprintln(os.Stderr, "Error starting Cmd", err)
        http.Error(w, "Error starting Cmd\n"+err.Error(), 500)
        return
    }

    // not sending response here anymore. Using the channel instead

    errChan := make(chan error)

    go func(ec chan error) {
        err = cmd.Wait()
        if err != nil {
            errChan <- err
        }
    }(errChan)

    select {
    case err := <-errChan:
        http.Error(w, "Error: "+err.Error(), 500)
    // timeout 50ms just in case. But I presume you would get an error (if there is one in cmd) even before execution will get to this point
    case <-time.After(time.Millisecond * 50):
        fmt.Fprintf(w, "ping started")
    }
}