发送 HTTP 响应被阻止,直到 shell 命令完成
Sending HTTP response is blocked until shell command finishes
思路:有一个web服务器准备好接收消息,会在服务器上触发commands/tests的执行。我从一个执行简单 ping
的简单案例开始。下面的代码处理发送到 /ping
的 POST
消息,其中包含以下 json 格式:
{ "ip": "valid_ip_addr", "count": "4" }
服务器然后运行命令ping -c 4 valid_ip_address
期望的结果: 如果命令可以 .Start()
发回 200 OK
。如果有问题,请发回错误信息。
问题: 我在检查 .Start()
没有给出任何错误后立即发送 200 OK
响应,但这是在命令后收到的已完成。
代码: 共有三个函数: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)
}
}
用于测试的卷曲
curl -X POST http://localhost:5050/ping -d '{"ip": "127.0.0.1", "count": "4"}'
我建议使用 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")
}
}
思路:有一个web服务器准备好接收消息,会在服务器上触发commands/tests的执行。我从一个执行简单 ping
的简单案例开始。下面的代码处理发送到 /ping
的 POST
消息,其中包含以下 json 格式:
{ "ip": "valid_ip_addr", "count": "4" }
服务器然后运行命令ping -c 4 valid_ip_address
期望的结果: 如果命令可以 .Start()
发回 200 OK
。如果有问题,请发回错误信息。
问题: 我在检查 .Start()
没有给出任何错误后立即发送 200 OK
响应,但这是在命令后收到的已完成。
代码: 共有三个函数: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)
}
}
用于测试的卷曲
curl -X POST http://localhost:5050/ping -d '{"ip": "127.0.0.1", "count": "4"}'
我建议使用 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")
}
}