web-dev-qa-db-fra.com

Corps de demande de lecture Golang

J'écris mon propre logginMiddleware. En gros, je dois consigner le corps de la demande et de la réponse. Le problème que j'ai rencontré est que lorsque je lis corps, il devient vide et je ne peux pas le lire deux fois. Je comprends que cela arrive car il est de type ReadCloser. Y a-t-il un moyen de rembobiner le corps au début? 

34
Rustam Ibragimov

Inspection et moquage du corps de la demande

Lorsque vous lisez le corps pour la première fois, vous devez le stocker. Ainsi, une fois que vous avez terminé, vous pouvez définir un nouveau io.ReadCloser en tant que corps de la requête construit à partir des données d'origine. Ainsi, lorsque vous avancez dans la chaîne, le prochain gestionnaire peut lire le même corps.

Une option consiste à lire tout le corps en utilisant ioutil.ReadAll() , qui vous donne le corps sous forme de tranche d’octets.

Vous pouvez utiliser bytes.NewBuffer() pour obtenir un io.Reader à partir d’une tranche d’octets.

La dernière pièce manquante est de faire du io.Reader un io.ReadCloser, car bytes.Buffer ne possède pas de méthode Close(). Pour cela, vous pouvez utiliser ioutil.NopCloser() qui encapsule un io.Reader et renvoie un io.ReadCloser, dont la méthode Close() sera un no-op (ne fait rien).

Notez que vous pouvez même modifier le contenu de la tranche d’octets que vous utilisez pour créer le "nouveau" corps. Vous en avez le plein contrôle.

Il faut cependant être prudent, car il peut y avoir d'autres champs HTTP tels que content-length et checksums, qui peuvent devenir invalides si vous ne modifiez que les données. Si les gestionnaires suivants vérifient ceux-ci, vous devrez également les modifier également!

Inspection/modification du corps de la réponse

Si vous souhaitez également lire le corps de la réponse, vous devez alors envelopper le http.ResponseWriter vous obtenez et transmettre le wrapper sur la chaîne. Ce wrapper peut mettre en cache les données envoyées, que vous pouvez inspecter après, à la volée (à mesure que les gestionnaires ultérieurs y écrivent).

Voici un simple wrapper ResponseWriter, qui met en cache les données, afin qu'elles soient disponibles après le retour du gestionnaire suivant:

type MyResponseWriter struct {
    http.ResponseWriter
    buf *bytes.Buffer
}

func (mrw *MyResponseWriter) Write(p []byte) (int, error) {
    return mrw.buf.Write(p)
}

Notez que MyResponseWriter.Write() écrit simplement les données dans un tampon. Vous pouvez également choisir de l'inspecter à la volée (dans la méthode Write()) et d'écrire les données immédiatement dans le nom enveloppé/incorporé ResponseWriter. Vous pouvez même modifier les données. Vous avez le plein contrôle.

Il faut toutefois faire attention à nouveau, car les gestionnaires suivants peuvent également envoyer des en-têtes de réponse HTTP liés aux données de réponse - telles que la longueur ou des sommes de contrôle - qui peuvent également devenir non valides si vous modifiez les données de réponse.

Exemple complet

En assemblant les pièces, voici un exemple de travail complet:

func loginmw(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        body, err := ioutil.ReadAll(r.Body)
        if err != nil {
            log.Printf("Error reading body: %v", err)
            http.Error(w, "can't read body", http.StatusBadRequest)
            return
        }

        // Work / inspect body. You may even modify it!

        // And now set a new body, which will simulate the same data we read:
        r.Body = ioutil.NopCloser(bytes.NewBuffer(body))

        // Create a response wrapper:
        mrw := &MyResponseWriter{
            ResponseWriter: w,
            buf:            &bytes.Buffer{},
        }

        // Call next handler, passing the response wrapper:
        handler.ServeHTTP(mrw, r)

        // Now inspect response, and finally send it out:
        // (You can also modify it before sending it out!)
        if _, err := io.Copy(w, mrw.buf); err != nil {
            log.Printf("Failed to send out response: %v", err)
        }
    })
}
58
icza