patterngoCritical
Golang read request body multiple times
Viewed 0 times
bodytimesrequestmultiplegolangread
Problem
I am writing my own logginMiddleware. Basically, I need to log body of the request and the response. The problem that I faced is that when I read body, it becomes empty and I cannot read it twice.
I understand that it happens because it is of type ReadCloser. Is there a way to rewind body to the beginning?
I understand that it happens because it is of type ReadCloser. Is there a way to rewind body to the beginning?
Solution
Inspecting and mocking request body
When you first read the body, you have to store it so once you're done with it, you can set a new
One option is to read the whole body using
You may use
The last missing piece is to make the
Note that you may even modify the contents of the byte slice you use to create the "new" body. You have full control over it.
Care must be taken though, as there might be other HTTP fields like content-length and checksums which may become invalid if you modify only the data. If subsequent handlers check those, you would also need to modify those too!
Inspecting / modifying response body
If you also want to read the response body, then you have to wrap the
Here's a simple
Note that
Care must be taken again though, as the subsequent handlers may also send HTTP response headers related to the response data –such as length or checksums– which may also become invalid if you alter the response data.
Full example
Putting the pieces together, here's a full working example:
When you first read the body, you have to store it so once you're done with it, you can set a new
io.ReadCloser as the request body constructed from the original data. So when you advance in the chain, the next handler can read the same body.One option is to read the whole body using
ioutil.ReadAll(), which gives you the body as a byte slice.You may use
bytes.NewBuffer() to obtain an io.Reader from a byte slice.The last missing piece is to make the
io.Reader an io.ReadCloser, because bytes.Buffer does not have a Close() method. For this you may use ioutil.NopCloser() which wraps an io.Reader, and returns an io.ReadCloser, whose added Close() method will be a no-op (does nothing).Note that you may even modify the contents of the byte slice you use to create the "new" body. You have full control over it.
Care must be taken though, as there might be other HTTP fields like content-length and checksums which may become invalid if you modify only the data. If subsequent handlers check those, you would also need to modify those too!
Inspecting / modifying response body
If you also want to read the response body, then you have to wrap the
http.ResponseWriter you get, and pass the wrapper on the chain. This wrapper may cache the data sent out, which you can inspect either after, on on-the-fly (as the subsequent handlers write to it).Here's a simple
ResponseWriter wrapper, which just caches the data, so it'll be available after the subsequent handler returns:type MyResponseWriter struct {
http.ResponseWriter
buf *bytes.Buffer
}
func (mrw *MyResponseWriter) Write(p []byte) (int, error) {
return mrw.buf.Write(p)
}Note that
MyResponseWriter.Write() just writes the data to a buffer. You may also choose to inspect it on-the-fly (in the Write() method) and write the data immediately to the wrapped / embedded ResponseWriter. You may even modify the data. You have full control.Care must be taken again though, as the subsequent handlers may also send HTTP response headers related to the response data –such as length or checksums– which may also become invalid if you alter the response data.
Full example
Putting the pieces together, here's a full working example:
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)
}
})
}Code Snippets
type MyResponseWriter struct {
http.ResponseWriter
buf *bytes.Buffer
}
func (mrw *MyResponseWriter) Write(p []byte) (int, error) {
return mrw.buf.Write(p)
}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)
}
})
}Context
Stack Overflow Q#43021058, score: 182
Revisions (0)
No revisions yet.