I'm writing a webhook in Go that parses a JSON payload. I'm attempting to log the raw payload and then decode it immediately after but it fails when I try. If I perform the actions separately, they both work fine independently.
Can someone explain why I can't use ioutil.ReadAll and json.NewDecoder together?
func webhook(w http.ResponseWriter, r *http.Request) {
body, _ := ioutil.ReadAll(r.Body)
log.Printf("incoming message - %s", body)
var p payload
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&p)
if err != nil {
// Returns EOF
log.Printf("invalid payload - %s", err)
}
defer r.Body.Close()
}
Can someone explain why I can't use ioutil.ReadAll and json.NewDecoder
together?
The request body is an io.ReadCloser that reads bytes, more or less, directly from a network connection. The contents of the Body aren't stored in memory by default. That's why after the first time you've read the Body the next time you try to read it you'll get EOF.
So if you need to process the request Body more than once, you yourself will have to store the contents into memory, which is what you are already doing with:
body, _ := ioutil.ReadAll(r.Body)
You can then reuse body as many times as you like, and since you have the Body contents at your disposal as a []byte value, you can use json.Unmarshal instead of json.NewDecoder(...).Decode.
This is unrelated to your question, but please do not ignore the error returned from ioutil.ReadAll.
Also you can drop the defer r.Body.Close() line, because you do not have to close the request body in your server handlers. (emphasis mine)
For server requests the Request Body is always non-nil but will return
EOF immediately when no body is present. The Server will close the
request body. The ServeHTTP Handler does not need to.
r.Body is meant to be read exactly once.
When you use the ioutil.ReadAll function you do read all the data from the body. That's why the decoder which also relies on r.Body in fact gets nothing to decode.
Minor additional point about json.Decoder and json.Unmarshal: at first glance it looks like the only difference between the two is just that the former operates on a stream and the latter on a []byte, but they actually have different semantics.
json.Unmarshal will return an error if the data contains more than one json object. So, for example, it will parse {}, but it will not parse {}{}.
json.Decoder parses one complete object per call to Decode, so if you give it {}{}, it will parse those two objects and then the third call will return io.EOF and it's More method will return false.
In a normal http body, you probably only want a single object, so you'd want to use Unmarshal if you're not worried about loading all the data into memory at once. You can also use Decoder and manually check that there is only one object if you care to do so.
Related
I'm learning about golang handlers and have some misunderstandings. It is associated with the Response header. When I use http.ServeFile function it will populate response header as I expect. Here is the code with the output:
mux := http.NewServeMux()
mux.HandleFunc("/nesto", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./ui/static/js/main.js")
fmt.Println("Header:", w.Header())
})
http.ListenAndServe(":4000", mux)
The output:
Header: map[Accept-Ranges:[bytes] Content-Length:[224] Content-Type:[application/javascript] Last-Modified:[Thu, 05 Jul 2018 13:44:05 GMT]]
To me it's expected behavior, since it has http.ResponseWriter as the first parameter and will use the Write method internally to write to the response body. Comments regarding this method are as follows:
// If WriteHeader has not yet been called, Write calls
// WriteHeader(http.StatusOK) before writing the data. If the Header
// does not contain a Content-Type line, Write adds a Content-Type set
// to the result of passing the initial 512 bytes of written data to
// DetectContentType. Additionally, if the total size of all written
// data is under a few KB and there are no Flush calls, the
// Content-Length header is added automatically.
So, totally normal behavior for me.
But when I use the Write method directly, the Header remains empty.
mux := http.NewServeMux()
mux.HandleFunc("/nesto", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Some message."))
fmt.Println("Header:", w.Header())
})
http.ListenAndServe(":4000", mux)
The output:
Header: map[]
Why is this happening? Shouldn't the Header be filled in, after all, the documentation itself (comment regarding the method) says so?
The documentation describes the headers written to the network, not the response writer Header().
The documentation is a little sloppy. The call to Write before WriteHeader triggers the actions described in documentation. The actions do not necessarily execute in the first call to Write:
The server waits for the application to write 512 bytes of data or for the handler to return before calling DetectContentType.
The server buffers a few KB of data. If the handler returns and that buffer has not been flushed to the network, then the server adds a content length to the headers written to the network.
See the code for more details.
You see the content length and content type headers after the call to ServeFile because ServeFile explicitly sets these headers in the response writer Header(). The content length is set to the size of the file on disk. The content type is set using the file name extension.
I have a client application which reads in the full body of a http response into a buffer and performs some processing on it:
body, _ = ioutil.ReadAll(containerObject.Resp.Body)
The problem is that this application runs on an embedded device, so responses that are too large fill up the device RAM, causing Ubuntu to kill the process.
To avoid this, I check the content-length header and bypass processing if the document is too large. However, some servers (I'm looking at you, Microsoft) send very large html responses without setting content-length and crash the device.
The only way I can see of getting around this is to read the response body up to a certain length. If it reaches this limit, then a new reader could be created which first streams the in-memory buffer, then continues reading from the original Resp.Body. Ideally, I would assign this new reader to the containerObject.Resp.Body so that callers would not know the difference.
I'm new to GoLang and am not sure how to go about coding this. Any suggestions or alternative solutions would be greatly appreciated.
Edit 1: The caller expects a Resp.Body object, so the solution needs to be compatible with that interface.
Edit 2: I cannot parse small chunks of the document. Either the entire document is processed or it is passed unchanged to the caller, without loading it into memory.
If you need to read part of the response body, then reconstruct it in place for other callers, you can use a combination of an io.MultiReader and ioutil.NopCloser
resp, err := http.Get("http://google.com")
if err != nil {
return err
}
defer resp.Body.Close()
part, err := ioutil.ReadAll(io.LimitReader(resp.Body, maxReadSize))
if err != nil {
return err
}
// do something with part
// recombine the buffered part of the body with the rest of the stream
resp.Body = ioutil.NopCloser(io.MultiReader(bytes.NewReader(part), resp.Body))
// do something with the full Response.Body as an io.Reader
If you can't defer resp.Body.Close() because you intend to return the response before it's read in its entirety, you will need to augment the replacement body so that the Close() method applies to the original body. Rather than using the ioutil.NopCloser as the io.ReadCloser, create your own that refers to the correct method calls.
type readCloser struct {
io.Closer
io.Reader
}
resp.Body = readCloser{
Closer: resp.Body,
Reader: io.MultiReader(bytes.NewReader(part), resp.Body),
}
I have a http request which I need to inspect the body of. But when I do, the request fails. I'm assuming this had to do with the Reader needing to be reset, but googling along the lines of go ioutil reset ReadCloser hasn't turned anything up that looks promising.
c is a *middleware.Context,
c.Req.Request is a http.Request, and
c.Req.Request.Body is an io.ReadCloser
contents, _ := ioutil.ReadAll(c.Req.Request.Body)
log.Info("Request: %s", string(contents))
proxy.ServeHTTP(c.RW(), c.Req.Request)
Specifically the error I get is http: proxy error: http: ContentLength=133 with Body length 0
You can't reset it, because you've already read from it and there's nothing left in the stream.
What you can do is take the buffered bytes you already have, and replace the Body with a new io.ReadCloser
contents, _ := ioutil.ReadAll(c.Req.Request.Body)
log.Info("Request: %s", string(contents))
c.Req.Request.Body = ioutil.NopCloser(bytes.NewReader(contents))
proxy.ServeHTTP(c.RW(), c.Req.Request)
My first idea was to get the response body within the filter, then use one of minify libraries like tdewolff/minify and write to response, but i cant find the way to get response body.
Is there any better solutions?
It seems, by looking at the docs, that a filter can access the Controller type, which contains the Response. This response contains Out which is a ResponseWriter (and thus also an io.Writer). We need to replace only the Write method to redirect the write to the minifier, which then writes to the response writer. We need to use io.Pipe and a goroutine for this.
type MinifyResponseWriter struct {
http.ResponseWriter
io.Writer
}
func (f MinifyResponseWriter) Write(b []byte) (int, error) {
return f.Writer.Write(b)
}
func MinifyFilter(c *Controller, fc []Filter) {
pr, pw := io.Pipe()
go func(w io.Writer) {
m := minify.New()
m.AddFunc("text/css", css.Minify)
m.AddFunc("text/html", html.Minify)
m.AddFunc("text/javascript", js.Minify)
m.AddFunc("image/svg+xml", svg.Minify)
m.AddFuncRegexp(regexp.MustCompile("[/+]json$"), json.Minify)
m.AddFuncRegexp(regexp.MustCompile("[/+]xml$"), xml.Minify)
if err := m.Minify("mimetype", w, pr); err != nil {
panic(err)
}
}(c.Response.Out)
c.Response.Out = MinifyResponseWriter{c.Response.Out, pw}
}
Something along those lines (not tested). Here we take the incoming io.Writer (which is part of the ResponseWriter), and wrap a struct around that. It keeps the original methods for the response writer, but the Write method is overrided to be replaced by the PipeWriter. This means that any write to the new response writer goes to PipeWriter, which is coupled to PipeReader. Minify Reads from that reader and writes to the original response writer.
Because we change the value of c.Response.Out, we need to pass it explicitly to the goroutine. Make sure you obtain the correct mimetype (through extension?) or call the appropriate minify function directly.
in http.Request type Body is closed when request is send by client. Why it need to be closed, why it can not be string, which you can read over and over?
This is called a stream. It's useful because it lets you handle data without having the whole set of data available in memory. It also lets you give the results of the operations you may do faster : you don't wait for the whole set to be computed.
As soon as you want to handle big data or worry about performances, you need streams.
It's also a convenient abstraction that lets you handle data one by one even when the whole set is available without having to handle an offset to iterate over the whole.
You can store the request stream as a string using the bytes and the io package:
func handler(w http.ResponseWriter, r *http.Request) {
var bodyAsString string
b := new(bytes.Buffer)
_, err := io.Copy(b, r)
if err == io.EOF {
bodyAsString = b.String()
}
}