duanjuhuo8772
duanjuhuo8772
2017-09-21 09:05

发送HTTP响应被阻止,直到shell命令完成

已采纳

Idea: there is a web server ready to receive messages that will trigger the execution of commands/tests on the server. I have started with a simple case where a simple ping is executed. The code below handles POST messages sent to /ping which contain the following json format:

{ "ip": "valid_ip_addr", "count": "4" }

The server will then run the command ping -c 4 valid_ip_address

Desired outcome: if the command can .Start() send back a 200 OK. If there are problems, send back an error message.

Problem: I send a 200 OK response right after checking that .Start() didn't give any errors, but this is being received after the command has finished.

Code: There are three functions: main(), handler() and ping(). The problem takes place in the last one.

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.
Details:
%s
", 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
"+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
", 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
"+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)
    }

}

curl for testing

curl -X POST http://localhost:5050/ping -d '{"ip": "127.0.0.1", "count": "4"}'
  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

1条回答

  • douzongluo7542 douzongluo7542 4年前

    I would recommend to use select with timeout waiting for an error. Check out the following code.

    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
    "+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
    ", 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
    "+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")
        }
    }
    
    点赞 评论 复制链接分享

相关推荐