Golang lire le corps de la demande

J'écris mon propre logginMiddleware. Fondamentalement, j'ai besoin de consigner le corps de la requête et la réponse. Le problème que j'ai rencontré est que quand je lis le corps, il devient vide et je ne peux pas le lire deux fois. Je comprends que cela arrive parce qu'il est de type ReadCloser. Est-il possible de rembobiner corps pour le début?

28
demandé sur Rustam Ibragimov 2017-03-25 22:36:31

2 réponses

Inspection et moquerie du corps de la requête

Lorsque vous lisez le corps pour la première fois, vous devez le stocker afin qu'une fois que vous en avez terminé, vous puissiez définir un nouveau io.ReadCloser comme le corps de la requête construit à partir des données d'origine. Alors, quand on avance dans la chaîne, le gestionnaire suivant peut lire le même corps.

Une option consiste à lire tout le corps en utilisant ioutil.ReadAll(), ce qui vous donne le corps comme une tranche d'octet.

, Vous pouvez utiliser bytes.NewBuffer() pour obtenir un io.Reader d'une tranche d'octet.

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

Notez que vous pouvez même modifier le contenu de l'octet tranche que vous utilisez pour créer le "nouveau" corps. Vous avez le plein contrôle sur elle.

des précautions doivent être prises, car il pourrait y avoir d'autres champs HTTP comme content-longueur et sommes de contrôle qui peuvent devenir invalides si vous modifiez uniquement les données. Si les gestionnaires suivants les vérifient, vous devrez également les modifier aussi!

Inspection / modification du corps de réponse

Si vous souhaitez également lire le corps de la réponse, vous devezhttp.ResponseWriter vous obtenez, et passez l'emballage sur la chaîne. Ce wrapper peut mettre en cache les données envoyées, que vous pouvez inspecter soit après, à la volée (comme les gestionnaires suivants écrivent il).

Voici un simple wrapper ResponseWriter, qui met simplement en cache les données, donc il sera disponible 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 wrapped / embedded ResponseWriter. Vous pouvez même modifier les données. Vous avez le plein contrôle.

des précautions doivent être prises à nouveau, car les gestionnaires suivants peuvent également envoyer HTTP les en –têtes de réponse liés aux données de réponse– telles que la longueur ou les sommes de contrôle-peuvent également devenir invalides si vous modifiez les données de réponse.

Exemple Complet

Mettre les morceaux ensemble, 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)
        }
    })
}
49
répondu icza 2018-03-20 16:28:33

Je pourrais utiliser le paquet GetBody from Request.

Regardez ce commentaire dans le code source de la requête.aller dans net / http

Getbody définit une fonction optionnelle pour renvoyer une nouvelle copie de Corps. Il est utilisé pour les demandes des clients lorsqu'une redirection nécessite la lecture du corps plus d'une fois. Utilisation de getbody still nécessite le réglage du corps. Pour les demandes de serveur, il est inutilisé."

GetBody func() (io.ReadCloser, error)

De cette façon, vous pouvez obtenir la requête du corps sans le faire vide.

Échantillon:

getBody := request.GetBody copyBody, _ := getBody() http.DefaultClient.Do(request)

0
répondu Yuri Giovani 2018-08-29 14:22:26