Problème :
Dans un programme Go avec une date limite contextuelle, la lecture d'un corps de réponse à l'aide de ioutil.ReadAll() entraîne une erreur de dépassement du délai attendu. Cependant, l'utilisation de json.NewDecoder(resp.Body).Decode() renvoie nil à la place.
Exemple de code :
<code class="go">package main import ( "context" "encoding/json" "fmt" "io/ioutil" "net/http" "time" ) var url string = "http://ip.jsontest.com/" func main() { readDoesntFail() readFails() } type IpResponse struct { Ip string } func readDoesntFail() { ctx, _ := context.WithTimeout(context.Background(), time.Second*5) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { panic(err) } resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } ipResponse := new(IpResponse) time.Sleep(time.Second * 6) fmt.Println("before reading response body, context error is:", ctx.Err()) err = json.NewDecoder(resp.Body).Decode(ipResponse) if err != nil { panic(err) } fmt.Println("Expected panic but there was none") } func readFails() { ctx, _ := context.WithTimeout(context.Background(), time.Second*5) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { panic(err) } resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } time.Sleep(time.Second * 6) fmt.Println("before reading response body, context error is:", ctx.Err()) _, err = ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("received expected error", err) } }</code>
Réponse :
Dans le package net/http, des tampons peuvent être utilisés pour traiter les requêtes. Par conséquent, le corps de la réponse entrante peut être partiellement ou entièrement lu et mis en mémoire tampon avant que vous tentiez de le lire. Par conséquent, un contexte expirant ne peut pas vous empêcher de terminer la lecture du corps. C'est précisément ce qui se produit dans cette situation.
Pour mieux comprendre, modifions le code pour créer un serveur HTTP de test qui reporte intentionnellement la réponse :
<code class="go">ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { s := []byte(`{"ip":"12.34.56.78"}`) w.Write(s[:10]) if f, ok := w.(http.Flusher); ok { f.Flush() } time.Sleep(time.Second * 6) w.Write(s[10:]) })) defer ts.Close() url = ts.URL readDoesntFail() readFails()</code>
L'exemple modifié envoie un JSON objet similaire à la réponse de ip.jsontest.com, mais ne transmet que les 10 premiers octets du corps avant de le vider. Il suspend ensuite la transmission pendant 6 secondes, donnant au client la possibilité d'expirer.
Lorsque nous exécutons readDoesntFail(), nous observons le comportement suivant :
before reading response body, context error is: context deadline exceeded panic: Get "http://127.0.0.1:38230": context deadline exceeded goroutine 1 [running]: main.readDoesntFail() /tmp/sandbox721114198/prog.go:46 +0x2b4 main.main() /tmp/sandbox721114198/prog.go:28 +0x93
Dans ce scénario, json.Decoder.Decode() tente de lire à partir de la connexion car les données n'ont pas encore été mises en mémoire tampon. Une fois le contexte expiré, une lecture plus approfondie de la connexion entraîne une erreur de dépassement du délai. Cependant, dans l'exemple d'origine, json.Decoder.Decode() lit des données déjà mises en mémoire tampon, ce qui rend le contexte expiré non pertinent.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!