I'm perpetually caught out by reading an io.ReadCloser and then forgetting that I've read it before, and when I read it again, I get an empty payload. I wish there was some lint check for my stupidity. Nonetheless, I think I can use TeeReader, but it fails to meet my expectations here:
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
buf := &bytes.Buffer{}
tee := io.TeeReader(r.Body, buf)
body, err := ioutil.ReadAll(tee)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
log.Println("body", string(body))
payload, err := httputil.DumpRequest(r, true)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Println("dump request", string(payload))
w.WriteHeader(http.StatusOK)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
The body is missing from my "dump request" log line.
I.e. when I run curl -i -X POST --data '{"username":"xyz","password":"xyz"}' http://localhost:8080
I want the original request in full:
2019/01/14 11:09:50 dump request POST / HTTP/1.1
Host: localhost:8080
Accept: */*
Content-Length: 35
Content-Type: application/x-www-form-urlencoded
User-Agent: curl/7.63.0
{"username":"xyz","password":"xyz"}
What am I missing?
You cannot rewind an io.ReadCloser, unless the underlying value is also an io.ReadSeeker.
An io.ReadCloser, by very definition, has exactly two methods: Read and Close. So there is obviously there is no option to rewind.
An io.ReadSeeker, by contrast, has two methods: Read and Seek, the latter which allows rewinding.
If you need to accept only io.ReadClosers which are also seekable, you can easily combine these two:
type ReadSeekCloser interface {
io.Reader
io.Seeker
io.Closer
}
Now you can use your custom ReadSeekCloser type in place of an io.ReadCloser, and you'll have the option to rewind your reader.
Of course, few io.ReadClosers in the wild actually conform to this interface (os.File will be the main one that does). If you have an io.ReadCloser that does not implement the Seek method (such as a network stream), probably the easiest way to make it Seekable is to dump the contents to a file, then open that file. There would be other ways to make an in-memory buffer seekable (bytes.NewReader for instance), but variations will require reading the stream into memory or onto disk, first.
How do I rewind a io.ReadCloser in Go[...]?
You cannot. A ReadCloser can be read and closed. Unless the actual underlying type has some method to rewind you simply cannot.
(For your case you may just use the bytes.Buffer, possibly after adding a Close method via io/ioutil.ReadCloser as the Request.Body; but this is not "rewinding" but "replacing".)
https://golang.org/pkg/net/http/httputil/#DumpRequest
DumpRequest returns the given request in its HTTP/1.x wire representation. It should only be used by servers to debug client requests.
Clearly DumpRequest is for Dubug use.
But if you don't care about that. The godoc also metioned:
If body is true, DumpRequest also returns the body. To do so, it consumes req.Body and then replaces it with a new io.ReadCloser that yields the same bytes.
So you can call DumpRequest first then ReadAll from Body, cause the Body is still the same after DumpRequest.
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
payload, err := httputil.DumpRequest(r, true)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Println("dump request", string(payload))
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
log.Println("body", string(body))
w.WriteHeader(http.StatusOK)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Request bodies aren't seek-able. There is no rewind, they aren't buffered in memory. When that data is read, it is read from the network stream. Imagine a large upload, buffering all of that data in memory by default would be wasteful.
That said, there are some things you can do to get the output you desire. It involves replacing r.Body after you've read it.
You mention wishing for a lint check. I've found that declaring a buffer and using only that buffer can help. If you like, you can even replace r.Body with that buffer. You will still need to remember to "rewind" with Seek.
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
b := &bytes.Buffer{}
_, err := io.Copy(b, r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
bodyCopy := bytes.NewReader(b.Bytes())
log.Println("body", b.String())
r.Body = io.NopCloser(bodyCopy)
bodyCopy.Seek(0, 0)
payload, err := httputil.DumpRequest(r, true)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Println("dump request", string(payload))
w.WriteHeader(http.StatusOK)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Related
Intuitively, I would think that when you create a MaxByteReader and pass in the http.ResponseWriter, it would write out the status code for you. But that isn't the case, what does the writer actually do?
example:
func maxBytesMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 1)
next.ServeHTTP(w, r)
})
}
func mainHandler(w http.ResponseWriter, r *http.Request) {
var i interface{}
err := json.NewDecoder(r.Body).Decode(&i)
if err != nil {
fmt.Println(err.Error())
}
}
func TestMaxBytesMiddleware(t *testing.T) {
handlerToTest := maxBytesMiddleware(http.HandlerFunc(mainHandler))
req := httptest.NewRequest(http.MethodPost, "http://test.com", bytes.NewReader(json.RawMessage(`{"hello":"world"}`)))
recorder := httptest.NewRecorder()
handlerToTest.ServeHTTP(recorder, req)
if recorder.Result().StatusCode != http.StatusRequestEntityTooLarge {
t.Errorf("expected %d got %d", http.StatusRequestEntityTooLarge, recorder.Result().StatusCode)
}
}
but when this test runs I get this:
http: request body too large
--- FAIL: TestMaxBytesMiddleware (0.00s)
main_test.go:37: expected 413 got 200
if I want the desired functionality of what I thought this function did, I need to change my mainHandler to something like this:
func mainHandler(w http.ResponseWriter, r *http.Request) {
var i interface{}
err := json.NewDecoder(r.Body).Decode(&i)
if err != nil {
if err.Error() == "http: request body too large" {
w.WriteHeader(http.StatusRequestEntityTooLarge)
return
}
fmt.Println(err.Error())
}
}
So what is that writer even there for?
If the MaxBytesReader stops before reading the whole body, it sets some flags on the writer that make sure that the HTTP connection will be closed after the response is sent. Normally the server would be willing to read another request from the same connection (HTTP keepalive), but it can't do that if there are unread bits of the previous request still in the pipeline, so it has to close the connection, forcing the client to make a new connection if it wants to send more requests.
This is accomplished using the private requestTooLarge method of http.ResponseWriter.
Can we send response(or write on user side)with statement like:
json.NewEncoder(w).Encode("some data")
in api before doing some calculation part which are not required in response but needed to store in database. I am thinking like we can give response in less time to the user and other part of function will be continue to work until the return statement.
Correct me if I am thinking in wrong direction.
One way would be to do your additional work which is not required for the response in another goroutine:
func someHandler(w http.ResponseWriter, r *http.Request) {
go func() {
// Do anything here, this won't delay the response
// But don't touch the writer or request, as they may not be available here
}()
if err := json.NewEncoder(w).Encode("some data"); err != nil {
log.Printf("Error sending response: %v", err)
}
}
Note that in the launched gorotuine you can't use the http.ResponseWriter nor the http.Request, as they are only valid to use until you return from your handler. If you need something from them, you must make a copy of the needed parts before you launch the goroutine.
If you want to complete the additional task before you return from the handler, you can still use a goroutine, and use a sync.WaitGroup to wait for it to complete and only then return from the handler. You may or may not flush the response:
func someHandler(w http.ResponseWriter, r *http.Request) {
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
// You may use the writer and request here
}()
if err := json.NewEncoder(w).Encode("some data"); err != nil {
log.Printf("Error sending response: %v", err)
}
// Optionally you may flush the data written so far (icnluding HTTP headers)
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
wg.Wait()
}
Note that here the goroutine is allowed to use the http.ResponseWriter and http.Request, because the handler does not return until the additional task is completed.
I've been reading up on the various timeouts that are available on an http request and they all seem to act as hard deadlines on the total time of a request.
I am running an http download, I don't want to implement a hard timeout past the initial handshake as I don't know anything about my users connection and don't want to timeout on slow connections. What I would ideally like is to timeout after a period of inactivity (when nothing has been downloaded for x seconds). Is there any way to do this as a built in or do I have to interrupt based on stating the file?
The working code is a little hard to isolate but I think these are the relevant parts, there is another loop that stats the file to provide progress but I will need to refactor a bit to use this to interrupt the download:
// httspClientOnNetInterface returns an http client using the named network interface, (via proxy if passed)
func HttpsClientOnNetInterface(interfaceIP []byte, httpsProxy *Proxy) (*http.Client, error) {
log.Printf("Got IP addr : %s\n", string(interfaceIP))
// create address for the dialer
tcpAddr := &net.TCPAddr{
IP: interfaceIP,
}
// create the dialer & transport
netDialer := net.Dialer{
LocalAddr: tcpAddr,
}
var proxyURL *url.URL
var err error
if httpsProxy != nil {
proxyURL, err = url.Parse(httpsProxy.String())
if err != nil {
return nil, fmt.Errorf("Error parsing proxy connection string: %s", err)
}
}
httpTransport := &http.Transport{
Dial: netDialer.Dial,
Proxy: http.ProxyURL(proxyURL),
}
httpClient := &http.Client{
Transport: httpTransport,
}
return httpClient, nil
}
/*
StartDownloadWithProgress will initiate a download from a remote url to a local file,
providing download progress information
*/
func StartDownloadWithProgress(interfaceIP []byte, httpsProxy *Proxy, srcURL, dstFilepath string) (*Download, error) {
// start an http client on the selected net interface
httpClient, err := HttpsClientOnNetInterface(interfaceIP, httpsProxy)
if err != nil {
return nil, err
}
// grab the header
headResp, err := httpClient.Head(srcURL)
if err != nil {
log.Printf("error on head request (download size): %s", err)
return nil, err
}
// pull out total size
size, err := strconv.Atoi(headResp.Header.Get("Content-Length"))
if err != nil {
headResp.Body.Close()
return nil, err
}
headResp.Body.Close()
errChan := make(chan error)
doneChan := make(chan struct{})
// spawn the download process
go func(httpClient *http.Client, srcURL, dstFilepath string, errChan chan error, doneChan chan struct{}) {
resp, err := httpClient.Get(srcURL)
if err != nil {
errChan <- err
return
}
defer resp.Body.Close()
// create the file
outFile, err := os.Create(dstFilepath)
if err != nil {
errChan <- err
return
}
defer outFile.Close()
log.Println("starting copy")
// copy to file as the response arrives
_, err = io.Copy(outFile, resp.Body)
// return err
if err != nil {
log.Printf("\n Download Copy Error: %s \n", err.Error())
errChan <- err
return
}
doneChan <- struct{}{}
return
}(httpClient, srcURL, dstFilepath, errChan, doneChan)
// return Download
return (&Download{
updateFrequency: time.Microsecond * 500,
total: size,
errRecieve: errChan,
doneRecieve: doneChan,
filepath: dstFilepath,
}).Start(), nil
}
Update
Thanks to everyone who had input into this.
I've accepted JimB's answer as it seems like a perfectly viable approach that is more generalised than the solution I chose (and probably more useful to anyone who finds their way here).
In my case I already had a loop monitoring the file size so I threw a named error when this did not change for x seconds. It was much easier for me to pick up on the named error through my existing error handling and retry the download from there.
I probably crash at least one goroutine in the background with my approach (I may fix this later with some signalling) but as this is a short running application (its an installer) so this is acceptable (at least tolerable)
Doing the copy manually is not particularly difficult. If you're unsure how to properly implement it, it's only a couple dozen lines from the io package to copy and modify to suit your needs (I only removed the ErrShortWrite clause, because we can assume that the std library io.Writer implementations are correct)
Here is a copy work-alike function, that also takes a cancelation context and an idle timeout parameter. Every time there is a successful read, it signals to the cancelation goroutine to continue and start a new timer.
func idleTimeoutCopy(dst io.Writer, src io.Reader, timeout time.Duration,
ctx context.Context, cancel context.CancelFunc) (written int64, err error) {
read := make(chan int)
go func() {
for {
select {
case <-ctx.Done():
return
case <-time.After(timeout):
cancel()
case <-read:
}
}
}()
buf := make([]byte, 32*1024)
for {
nr, er := src.Read(buf)
if nr > 0 {
read <- nr
nw, ew := dst.Write(buf[0:nr])
written += int64(nw)
if ew != nil {
err = ew
break
}
}
if er != nil {
if er != io.EOF {
err = er
}
break
}
}
return written, err
}
While I used time.After for brevity, it's more efficient to reuse the Timer. This means taking care to use the correct reset pattern, as the return value of the Reset function is broken:
t := time.NewTimer(timeout)
for {
select {
case <-ctx.Done():
return
case <-t.C:
cancel()
case <-read:
if !t.Stop() {
<-t.C
}
t.Reset(timeout)
}
}
You could skip calling Stop altogether here, since in my opinion if the timer fires while calling Reset, it was close enough to cancel anyway, but it's often good to have the code be idiomatic in case this code is extended in the future.
Go version: go1.8.1 windows/amd64
Sample code for HTTP request is:
func (c *Client) RoundTripSoap12(action string, in, out Message) error {
fmt.Println("****************************************************************")
headerFunc := func(r *http.Request) {
r.Header.Add("Content-Type", fmt.Sprintf("text/xml; charset=utf-8"))
r.Header.Add("SOAPAction", fmt.Sprintf(action))
r.Cookies()
}
return doRoundTrip(c, headerFunc, in, out)
}
func doRoundTrip(c *Client, setHeaders func(*http.Request), in, out Message) error {
req := &Envelope{
EnvelopeAttr: c.Envelope,
NSAttr: c.Namespace,
Header: c.Header,
Body: Body{Message: in},
}
if req.EnvelopeAttr == "" {
req.EnvelopeAttr = "http://schemas.xmlsoap.org/soap/envelope/"
}
if req.NSAttr == "" {
req.NSAttr = c.URL
}
var b bytes.Buffer
err := xml.NewEncoder(&b).Encode(req)
if err != nil {
return err
}
cli := c.Config
if cli == nil {
cli = http.DefaultClient
}
r, err := http.NewRequest("POST", c.URL, &b)
if err != nil {
return err
}
setHeaders(r)
if c.Pre != nil {
c.Pre(r)
}
fmt.Println("*************", r)
resp, err := cli.Do(r)
if err != nil {
fmt.Println("error occured is as follows ", err)
return err
}
fmt.Println("response headers are: ", resp.Header.Get("sprequestguid"))
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
// read only the first Mb of the body in error case
limReader := io.LimitReader(resp.Body, 1024*1024)
body, _ := ioutil.ReadAll(limReader)
return fmt.Errorf("%q: %q", resp.Status, body)
}
return xml.NewDecoder(resp.Body).Decode(out)
I will call the RoundTripSoap12 function on the corresponding HTTP client.
When I send a request for the first time I will be getting some headers in the HTTP response, so these HTTP response headers should be sent as-is in my next HTTP request.
You may be interested in the httputil package and the reverse proxy example provided if you wish to proxy requests transparently:
https://golang.org/src/net/http/httputil/reverseproxy.go
You can copy the headers from one request to another one fairly easily - the Header is a separate object, if r and rc are http.Requests and you don't mind them sharing a header (you may need to clone instead if you want independent requests):
rc.Header = r.Header // note shallow copy
fmt.Println("Headers", r.Header, rc.Header)
https://play.golang.org/p/q2KUHa_qiP
Or you can look through keys and values and only copy certain headers, and/or do a clone instead to ensure you share no memory. See the http util package here for examples of this - see the functions cloneHeader and copyHeader inside reverseproxy.go linked above.
I'm trying to send data (files or whatever) through HTTP from the client to a server and read them as stream in the server.
But I noticed the chunk size or buffer size when the request's body is read it is fixed to 32kb. I tried doing it with TCP before using HTTP and the buffer size was the expected assigned size.
The data received from the request is being written to a file
Questions:
Is it possible to increase the chunk / buffer size?
if it is possible, by having a bigger buffer size will it increase performance due to less write calls to to the file being created?
If it is not possible, should I worry about performance loss by doing more write calls to the file being created?
Would it be better to use TCP? I really need the headers and http response
Here is some code for illustration:
client.go:
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
)
func main() {
addr := "http://localhost:8080"
path := "path/to/file"
sendHTTP(addr, path)
}
func sendHTTP(addr, path string) {
f, err := os.Open(path)
if err != nil {
log.Fatal("Error opening file:", err)
}
client := &http.Client{}
req, err := http.NewRequest("POST", addr, f)
if err != nil {
f.Close()
log.Fatal("Error creating request:", err)
}
_, err = client.Do(req)
if err != nil {
f.Close()
log.Fatal("Error doing request:", err)
}
}
server.go:
package main
import (
"fmt"
"io"
"log"
"net/http"
)
func main() {
addr := ":8080"
http.HandleFunc("/", handler)
http.ListenAndServe(addr, nil)
}
func handler(_ http.ResponseWriter, r *http.Request) {
buf := make([]byte, 512*1024) // 512kb
for {
br, err := r.Body.Read(buf)
if err == io.EOF {
break
} else if err != nil {
log.Println("Error reading request:", err)
break
}
fmt.Println(br) // is always 32kb
}
}
The call r.Body.Read(buf) waits for data from the network and returns up to len(buf) bytes of the available data. The amount of available data at the time of the call depends on timing and buffer sizes on the client, server and network. It's not easy to control.
The data received from the request is being written to a file
To write the data to the file in the most efficient way, copy from the request body to the file using io.Copy. Here's an example where f is the *os.File you want to write:
_, err := io.Copy(f, r.Body)
if err != nil {
// handle error
}
At the time I am writing this answer, the io.Copy function calls f.ReadFrom(r.Body) to copy the request body to a file.