In HTTP server I'm trying to pipe a file from a io.Writer to io.Reader.
I'm using io.Pipe
to transfer the data.
The common pattern for this seems to be something like this:
// Some handler func ..
pr, pw := io.Pipe()
go func() {
defer pw.Close()
if err := fetchFile(pw); err != nil {
pw.CloseWithError(err)
}
}()
writer.WriteHeader(http.StatusOK)
io.Copy(responseWriter, pr)
The issue I'm having is that if sometimes the writer returns an error(e.g file not found), but at this point the HTTP status OK header and Content-Type has already been written.
What I came up with was using a bufio.Reader
to peek into the buffer before sending anything down to the client.
Something like this:
pr, pw := io.Pipe()
go func() {
defer pw.Close()
if err := fetchFile(pw); err != nil {
_ = pw.CloseWithError(err)
}
}()
// Do a peek to determine if we have the data flowing or we have encountered some error
bufReader := bufio.NewReader(pr)
_, err := bufReader.Peek(1)
if err != nil {
pr.CloseWithError(err)
writer.WriteHeader(http.StatusInternalServerError)
writer.Write([]byte("This is error"))
return
}
writer.WriteHeader(http.StatusOK)
io.Copy(responseWriter, bufReader)
That works, but looks rather hacky to me and I'm not also sure about all the bufio corner cases.
Is there some better/simpler way to achieve this ?
Note: The above examples are simplified a bit. The actual implementation uses GIN framework and instead of io.Copy
the ctx.DataFromReader()
is used to transfer data.