2017-09-21 15 views
0

Idée: un serveur Web est prêt à recevoir des messages qui déclencheront l'exécution de commandes/tests sur le serveur. J'ai commencé avec un cas simple où un simple ping est exécuté. Le code traite ci-dessous POST les messages envoyés à /ping qui contiennent le format JSON suivant:L'envoi de la réponse HTTP est bloqué jusqu'à la fin de la commande shell

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

Le serveur puis exécutez la commande ping -c 4 valid_ip_address

Résultat souhaité: si la commande peut .Start() renvoyer un 200 OK. En cas de problème, renvoyez un message d'erreur.

Problème: J'envoie une réponse 200 OK juste après avoir vérifié que .Start() n'a pas donné d'erreurs, mais cela est reçu après la commande terminée.

code: Il y a trois fonctions: main(), handler() et ping(). Le problème a lieu dans le dernier.

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) 
    } 

} 

boucle pour le test

curl -X POST http://localhost:5050/ping -d '{"ip": "127.0.0.1", "count": "4"}' 

Répondre

1

Je recommande d'utiliser select avec délai d'attente pour une erreur. Découvrez le code suivant.

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") 
    } 
} 
+0

Merci beaucoup! Je vais aussi jeter un coup d'œil aux canaux parce que je ne suis pas très au courant. Si je comprends bien, vous envoyez des erreurs potentielles à la chaîne. Puis 'select' bloque jusqu'à ce que l'un des deux cas puisse se poursuivre ... – tgogos

+0

@tgogos Je recommanderais de regarder dans les prochains matériaux: https://gobyexample.com/channels https://gobyexample.com/channel-synchronization https://gobyexample.com/channel-directions https://gobyexample.com/select https://gobyexample.com/timeouts https://gobyexample.com/non-blocking-channel-operations –