web-dev-qa-db-fra.com

réponse multiple.Les appels WriteHeader dans un exemple très simple?

J'ai le programme net/http le plus basique que j'utilise pour apprendre l'espace de noms dans Go:

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Println(r.URL)
        go HandleIndex(w, r)
    })

    fmt.Println("Starting Server...")
    log.Fatal(http.ListenAndServe(":5678", nil))
}

func HandleIndex(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(200)
    w.Write([]byte("Hello, World!"))
}

Lorsque j'exécute le programme et que je me connecte à localhost:5678 Dans Chrome, j'obtiens ceci dans la console:

Starting Server...
/
2015/01/15 13:41:29 http: multiple response.WriteHeader calls
/favicon.ico
2015/01/15 13:41:29 http: multiple response.WriteHeader calls

Mais je ne vois pas comment c'est possible. J'imprime l'URL, démarre un nouveau goroutine, écris l'en-tête une fois et lui donne un corps statique de Hello, World! Il semble que l'une des deux choses se produit. Soit quelque chose en arrière-plan écrit un autre en-tête, soit HandleIndex est appelé deux fois pour la même demande. Que puis-je faire pour arrêter d'écrire plusieurs en-têtes?

EDIT: Cela semble avoir quelque chose à voir avec la ligne go HandleIndex(w, r) parce que si je supprime go et que j'en fais juste un appel de fonction au lieu d'une goroutine, je n'ai aucun problème et le le navigateur obtient ses données. Étant donné qu'il s'agit d'un goroutine, j'obtiens l'erreur Multiple WriteHeader et le navigateur n'affiche pas "Hello World". Pourquoi en faire un goroutine qui le brise?

28
Corey Ogburn

Jetez un œil à la fonction anonyme que vous enregistrez en tant que gestionnaire des demandes entrantes:

func(w http.ResponseWriter, r *http.Request) {
    fmt.Println(r.URL)
    go HandleIndex(w, r)
}

Il imprime l'URL (sur la sortie standard) puis appelle HandleIndex() dans un nouveau goroutine et continue l'exécution.

Si vous avez une fonction de gestionnaire dans laquelle vous ne définissez pas l'état de réponse avant le premier appel à Write, Go définira automatiquement l'état de réponse à 200 (HTTP OK). Si la fonction de gestionnaire n'écrit rien dans la réponse (et ne définit pas l'état de réponse et se termine normalement), cela est également traité comme une gestion réussie de la demande et l'état de réponse 200 sera renvoyé. Votre fonction anonyme ne la définit pas, elle n'écrit même rien dans la réponse. Alors Go fera exactement cela: définissez le statut de la réponse sur 200 HTTP OK.

Notez que la gestion de chaque demande s'exécute dans son propre goroutine.

Donc, si vous appelez HandleIndex dans un nouveau goroutine, votre fonction anonyme d'origine continuera: elle se terminera et donc l'en-tête de réponse sera défini - pendant ce temps (simultanément) votre nouveau goroutine démarré définira également l'en-tête de réponse - d'où l'erreur "multiple response.WriteHeader calls".

Si vous supprimez le "go", Votre fonction HandleIndex définira l'en-tête de réponse dans le même goroutine avant le retour de votre fonction de gestionnaire, et le "net/http" le saura et n'essaiera pas de définissez à nouveau l'en-tête de réponse afin que l'erreur que vous rencontrez ne se produise pas.

53
icza

Vous avez déjà reçu une réponse correcte qui résout votre problème, je donnerai quelques informations sur le cas général (une telle erreur apparaît souvent).

Dans la documentation , vous voyez que WriteHeader envoie un code d'état http et vous ne pouvez pas envoyer plus d'un code d'état. Si vous Write quelque chose, cela équivaut à envoyer 200 codes de statut puis à écrire des choses.

Ainsi, le message que vous voyez apparaît si vous utilisez l’utilisateur w.WriteHeader plusieurs fois explicitement ou utilise w.Write avant w.WriteHeader.

3
Salvador Dali

la cause principale est que vous avez appelé WriteHeader plus d'une fois. à partir des codes sources

func (w *response) WriteHeader(code int) {
    if w.conn.hijacked() {
        w.conn.server.logf("http: response.WriteHeader on hijacked connection")
        return
    }
    if w.wroteHeader {
        w.conn.server.logf("http: multiple response.WriteHeader calls")
        return
    }
    w.wroteHeader = true
    w.status = code

    if w.calledHeader && w.cw.header == nil {
        w.cw.header = w.handlerHeader.clone()
    }

    if cl := w.handlerHeader.get("Content-Length"); cl != "" {
        v, err := strconv.ParseInt(cl, 10, 64)
        if err == nil && v >= 0 {
            w.contentLength = v
        } else {
            w.conn.server.logf("http: invalid Content-Length of %q", cl)
            w.handlerHeader.Del("Content-Length")
        }
    }
}

donc quand vous avez écrit une fois, la variable writeHeader serait vraie, puis vous avez à nouveau écrit l'en-tête, elle ne serait pas efficace et a donné un avertissement "http: multiple respnse.WriteHeader calls". en fait, la fonction Write appelle également WriteHeader, donc placer la fonction WriteHeader après la fonction Write provoque également cette erreur, et le WriteHeader ultérieur ne fonctionne pas.

à partir de votre cas, allez handleindex s'exécute dans un autre thread et l'original retourne déjà, si vous ne faites rien, il appellera WriteHeader pour définir 200. lors de l'exécution de handleindex, il appelle un autre WriteHeader, à ce moment-là writeHeader est vrai, puis le message "http : multiple response.WriteHeader calls "est généré.

1
user7402259

De la documentation:

// WriteHeader sends an HTTP response header with status code. 
// If WriteHeader is not called explicitly, the first call to Write  
// will trigger an implicit WriteHeader(http.StatusOK).

Ce qui se passe dans votre cas, c'est que vous lancez go HandleIndex du gestionnaire. Le premier gestionnaire termine. Le WriteHeader standard écrit dans ResponseWriter. Ensuite, la routine go HandleIndex est lancée et essaie également d'écrire un en-tête et d'écrire.

Supprimez simplement le go de HandleIndex et cela fonctionnera.

1
fabrizioM

Parce que les navigateurs modernes envoient une demande supplémentaire pour / favicon.ico qui est également gérée dans votre gestionnaire/request.

Si vous exécutez une commande ping sur votre serveur avec curl par exemple, vous ne verrez qu'une seule demande envoyée:

 curl localhost:5678

Pour être sûr que vous pouvez ajouter un EndPoint dans votre http.HandleFunc

http.HandleFunc("/Home", func(w http.ResponseWriter, r *http.Request) 
0
abdel

Oui, utilisez HandleIndex(w, r) au lieu de go HandleIndex(w, r) résoudra votre problème, je pense que vous l'avez déjà compris.

La raison est simple, lors du traitement de plusieurs demandes en même temps, le serveur http démarrera plusieurs goroutines et votre fonction de gestionnaire sera appelée séparément dans chacun des goroutines sans en bloquer d'autres. Vous n'avez pas besoin de démarrer votre propre goroutine dans le gestionnaire, sauf si vous en avez pratiquement besoin, mais ce sera un autre sujet.

0
Andy Xu