Why does func Get of fasthttp in golang have `dst` parameter? - http

I found fasthttp godoc as fellow:
func Get
func Get(dst []byte, url string) (statusCode int, body []byte, err error)
Get appends url contents to dst and returns it as body.
The function follows redirects. Use Do* for manually handling redirects.
New body buffer is allocated if dst is nil.
But, when I run fellow code
package main
import (
"fmt"
fh "github.com/valyala/fasthttp"
)
func main() {
url := "https://www.okcoin.cn/api/v1/ticker.do?symbol=btc_cny"
dst := []byte("ok100")
_, body, err := fh.Get(dst, url)
if err != nil {
fmt.Println(err)
}
fmt.Println("body:", string(body))
fmt.Println("dst:", string(dst))
}
body does not have "ok100", and dst is still "ok100".
why?

Looking at the code where it is used in fasthttp's client.go func clientGetURLDeadlineFreeConn (line 672), you can see that if there is a timeout, dst's contents are copied to body at line 712. So based on what I read in the code (and debugged with Delve using your code), I saw that dst doesn't get updated in this usage. It seems that it can be used to provide default content to body in the event of a timeout - probably worth a direct question to fasthttp's author for more detail.
func clientGetURLDeadlineFreeConn(dst []byte, url string, deadline time.Time, c clientDoer) (statusCode int, body []byte, err error) {
timeout := -time.Since(deadline)
if timeout <= 0 {
return 0, dst, ErrTimeout
}
var ch chan clientURLResponse
chv := clientURLResponseChPool.Get()
if chv == nil {
chv = make(chan clientURLResponse, 1)
}
ch = chv.(chan clientURLResponse)
req := AcquireRequest()
// Note that the request continues execution on ErrTimeout until
// client-specific ReadTimeout exceeds. This helps limiting load
// on slow hosts by MaxConns* concurrent requests.
//
// Without this 'hack' the load on slow host could exceed MaxConns*
// concurrent requests, since timed out requests on client side
// usually continue execution on the host.
go func() {
statusCodeCopy, bodyCopy, errCopy := doRequestFollowRedirects(req, dst, url, c)
ch <- clientURLResponse{
statusCode: statusCodeCopy,
body: bodyCopy,
err: errCopy,
}
}()
tc := acquireTimer(timeout)
select {
case resp := <-ch:
ReleaseRequest(req)
clientURLResponseChPool.Put(chv)
statusCode = resp.statusCode
body = resp.body
err = resp.err
case <-tc.C:
body = dst
err = ErrTimeout
}
releaseTimer(tc)
return statusCode, body, err
}
In client.go func doRequestFollowRedirects (line 743) it is used at line 748: bodyBuf.B = dst
func doRequestFollowRedirects(req *Request, dst []byte, url string, c clientDoer) (statusCode int, body []byte, err error) {
resp := AcquireResponse()
bodyBuf := resp.bodyBuffer()
resp.keepBodyBuffer = true
oldBody := bodyBuf.B
bodyBuf.B = dst
redirectsCount := 0
for {
req.parsedURI = false
req.Header.host = req.Header.host[:0]
req.SetRequestURI(url)
if err = c.Do(req, resp); err != nil {
break
}
statusCode = resp.Header.StatusCode()
if statusCode != StatusMovedPermanently && statusCode != StatusFound && statusCode != StatusSeeOther {
break
}
redirectsCount++
if redirectsCount > maxRedirectsCount {
err = errTooManyRedirects
break
}
location := resp.Header.peek(strLocation)
if len(location) == 0 {
err = errMissingLocation
break
}
url = getRedirectURL(url, location)
}
body = bodyBuf.B
bodyBuf.B = oldBody
resp.keepBodyBuffer = false
ReleaseResponse(resp)
return statusCode, body, err
}

Related

Sending data in Chunks using single HTTP Post connection

I receive the contents of a file from a data source in chunks. As and when I receive the chunk I want to send the chunk data to a service using http POST request. And by keeping alive the same http POST connection used for sending the first chunk I want to send the remaining chunks of data.
I came up with the following code snippet to implement something similar.
Server-Side
func handle(w http.ResponseWriter, req *http.Request) {
buf := make([]byte, 256)
var n int
for {
n, err := req.Body.Read(buf)
if n == 0 && err == io.EOF {
break
}
fmt.Printf(string(buf[:n]))
}
fmt.Printf(string(buf[:n]))
fmt.Printf("Transfer Complete")
}
Client-Side
type alphaReader struct {
reader io.Reader
}
func newAlphaReader(reader io.Reader) *alphaReader {
return &alphaReader{reader: reader}
}
func (a *alphaReader) Read(p []byte) (int, error) {
n, err := a.reader.Read(p)
return n, err
}
func (a *alphaReader) Reset(str string) {
a.reader = strings.NewReader(str)
}
func (a *alphaReader) Close() error {
return nil
}
func main() {
tr := http.DefaultTransport
alphareader := newAlphaReader(strings.NewReader("First Chunk"))
client := &http.Client{
Transport: tr,
Timeout: 0,
}
req := &http.Request{
Method: "POST",
URL: &url.URL{
Scheme: "http",
Host: "localhost:8080",
Path: "/upload",
},
ProtoMajor: 1,
ProtoMinor: 1,
ContentLength: -1,
Body: alphareader,
}
fmt.Printf("Doing request\n")
_, err := client.Do(req)
alphareader.Reset("Second Chunk")
fmt.Printf("Done request. Err: %v\n", err)
}
Here I want that when I do alphareader.Reset("Second Chunk"), the string "Second Chunk" should be sent using the POST connection made earlier. But that is not happening. The connection gets closed after sending the First Chunk of data. Also I have not written the Close() method properly which I'm not sure how to implement.
I'm newbie to golang and any suggestions would be greatly helpful regarding the same.
A *strings.Reader returns io.EOF after the initial string has been read and your wrapper does nothing to change that, so it cannot be reused. You're looking for io.Pipe to turn the request body into an io.Writer.
package main
import (
"io"
"net/http"
)
func main() {
pr, pw := io.Pipe()
req, err := http.NewRequest("POST", "http://localhost:8080/upload", pr)
if err != nil {
// TODO: handle error
}
go func() {
defer pw.Close()
if _, err := io.WriteString(pw, "first chunk"); err != nil {
_ = err // TODO: handle error
}
if _, err := io.WriteString(pw, "second chunk"); err != nil {
_ = err // TODO: handle error
}
}()
res, err := http.DefaultClient.Do(req)
if err != nil {
// TODO: handle error
}
res.Body.Close()
}
Also, don't initialize the request using a struct literal. Use one of the constructors instead. In your code you're not setting the Host and Header fields, for instance.

Close http connection programmatically after X ms in golang

I'm doing X parallel http requests and when one of them does not respond in X ms (imagine is 100ms) or less I want to cut this connection. The code I wrote does not seem to work so, how can I cut the connection and get the response as nil?
This is my sample code:
cx, cancel := context.WithCancel(context.Background())
ch := make(chan *HttpResponse)
var responses []*HttpResponse
timeout := 1.000 //1ms for testing purposes
var client = &http.Client{
Timeout: 1 * time.Second,
}
startTime := time.Now()
for _, url := range urls {
go func(url string) {
fmt.Printf("Fetching %s \n", url)
req, _ := http.NewRequest("POST", url, bytes.NewReader(request)) //request is json string
req.WithContext(cx)
resp, err := client.Do(req)
ch <- &HttpResponse{url, resp, err}
var timeElapsed = time.Since(startTime)
msec := timeElapsed.Seconds() * float64(time.Second/time.Millisecond)
if msec >= timeout {
cancel()
}
if err != nil && resp != nil && resp.StatusCode == http.StatusOK {
resp.Body.Close()
}
}(url)
}
for {
select {
case r := <-ch:
fmt.Printf("%s was fetched\n", r.Url)
if r.Err != nil {
fmt.Println("with an error", r.Err)
}
responses = append(responses, r)
if len(responses) == len(*feeds) {
return responses
}
case <-time.After(100):
//Do something
}
}
Your code waits until a requests finishes (and get a resposne or an error), and then calculate the time passed, and if it was longer than the time expect, your code would cancel all the requests.
req, _ := http.NewRequest("POST", url, bytes.NewReader(request)) //request is json string
req.WithContext(cx) //Here you use a common cx, which all requests share.
resp, err := client.Do(req) //Here the request is being sent and you wait it until done.
ch <- &HttpResponse{url, resp, err}
var timeElapsed = time.Since(startTime)
msec := timeElapsed.Seconds() * float64(time.Second/time.Millisecond)
if msec >= timeout {
cancel() //here you cancel all the requests.
}
The fix is to utilize the context package right.
req, _ := http.NewRequest("POST", url, bytes.NewReader(request)) //request is json string
ctx,cancel := context.WithTimeout(request.Context(),time.Duration(timeout)*time.Millisecond)
resp,err:=client.Do(req.WithContext(ctx))
defer cancel()
With that, you will get a nil resp (and an error) and get the connection cut when time out.

Request.ParseMultipartForm leaks memory

I am writing a Go server which handles image uploading via Multipart Requests, but after a while i get really bad memory usages (up to 100% RAM).
I am using the basic http.ListenAndServeTLS and http.HandleFunc to do so.
Below a snippet of my code and the svg i got from pprof after a lot of image uploads.
func HandleUploadImage(res http.ResponseWriter, req *http.Request) {
defer func() {
// Release mem
if req != nil {
fmt.Println("Releasing request")
if req.MultipartForm != nil {
fmt.Println("Releasing MultipartForm")
req.MultipartForm.RemoveAll()
}
if req.Body != nil {
fmt.Println("Closing req.Body")
req.Body.Close()
}
}
}()
// parse request
const _2MB = 1 << 20
if err := req.ParseMultipartForm(_2MB); nil != err {
u.Log("Error", "Couldn't parse MultiPartForm", map[string]interface{}{"Error": err}, u.GetFile_line(), "")
return
}
id := req.FormValue(formValueString)
// get the json data
if err := json.Unmarshal([]byte(id), marshalStruct); nil != err {
u.Log("Error", "Couldn't Unmarshal JSON", map[string]interface{}{"Error": err}, u.GetFile_line(), "")
return
}
// Do something with the image and save it
// Marshal the reponse to JSON.
resJSON, err := response.ToJSON()
if err != nil {
u.Log("Error", "Couldn't create JSON from response struct", map[string]interface{}{"Error": err, "ResponseStruct": response}, u.GetFile_line(), username)
return
}
}
pprof_image

Recursion in golang is giving deadlock or negative WaitGroup counter when using goroutines, channels and sync.Waitgroup

I am trying to find the list of all directories using a recursive function. The code to the function is
func FindDirs(dir string, nativePartitions []int64, wg *sync.WaitGroup, dirlistchan chan string) {
// defer wg.Done here will give negative waitgroup panic, commenting it will give negative waitgroup counter panic
fd, err := os.Open(dir)
if err != nil {
panic(err)
}
filenames, err := fd.Readdir(0)
if err != nil {
panic(err)
}
for _, i := range filenames {
var buff bytes.Buffer
buff.WriteString(dir)
switch dir {
case "/":
default:
buff.WriteString("/")
}
buff.WriteString(i.Name())
/*err := os.Chdir(dir)
if err != nil {
return err
}*/
t := new(syscall.Statfs_t)
err = syscall.Statfs(buff.String(), t)
if err != nil {
//fmt.Println("Error accessing", buff.String())
}
if checkDirIsNative(t.Type, nativePartitions) && i.IsDir(){
dirlistchan <- buff.String()
FindDirs(buff.String(), nativePartitions, wg, dirlistchan) //recursion happens here
} else {
//fmt.Println(i.Name(), "is not native")
}
}
}
and in the main function, I am calling it as
wg := new(sync.WaitGroup)
dirlistchan := make(chan string, 1000)
wg.Add(1)
go func() {
filtermounts.FindDirs(parsedConfig.ScanFrom, []int64{filtermounts.EXT4_SUPER_MAGIC}, wg, dirlistchan)
}()
go func() {
wg.Wait()
close(dirlistchan)
}()
for i := range dirlistchan {
fmt.Println(i)
}
wg.Wait()
and I am getting a
fatal error: all goroutines are asleep - deadlock!
I was able to get this working if I am printing the result instead of using channels, or append to a slice using mutex. (verified with the linux find command to see if the results are same.) Please find the function after omitting channels and using sync.Mutex and append.
func FindDirs(dir string, nativePartitions []int64, dirlist *[]string, mutex *sync.Mutex) []string{
fd, err := os.Open(dir)
defer fd.Close()
if err != nil {
panic(err)
}
filenames, err := fd.Readdir(0)
if err != nil {
panic(err)
}
for _, i := range filenames {
var buff bytes.Buffer
buff.WriteString(dir)
switch dir {
case "/":
default:
buff.WriteString("/")
}
buff.WriteString(i.Name())
/*err := os.Chdir(dir)
if err != nil {
return err
}*/
t := new(syscall.Statfs_t)
err = syscall.Statfs(buff.String(), t)
if err != nil {
//fmt.Println("Error accessing", buff.String())
}
if checkDirIsNative(t.Type, nativePartitions) && i.IsDir(){
//dirlistchan <- buff.String()
mutex.Lock()
*dirlist = append(*dirlist, buff.String())
mutex.Unlock()
//fmt.Println(buff.String())
FindDirs(buff.String(), nativePartitions, dirlist, mutex)
} else {
//fmt.Println(i.Name(), "is not native")
}
}
return *dirlist
}
But I cannot think of a way to make this work with channels and goroutines. Any help is greatly appreciated.
Note: Here is a link to the golang playground with the code. I couldn't find a workaround to get the syscall thing to work on the playground either. It works on my system though.
Thanks.
Short answer : You are not closing the channel.
Fix : add defer wg.Done() at beginning of the go routine that calls FindDirs
go func() {
defer wg.Done()
filtermounts.FindDirs(parsedConfig.ScanFrom, []int64{filtermounts.EXT4_SUPER_MAGIC}, wg, dirlistchan)
}()
Why did it happen
The go routine that is responsponsible for closing the channel waits for wg there is no wg.Done in the code above. So close never happens
Now the for loop blocks on the channel for close or a value for ever, this cause the error
fatal error: all goroutines are asleep - deadlock!
So here is your code ,this may be run as
go run filename.go /path/to/folder
Code
package main
import (
"bytes"
"fmt"
"os"
"sync"
"syscall"
)
func main() {
wg := new(sync.WaitGroup)
dirlistchan := make(chan string, 1000)
wg.Add(1)
go func() {
defer wg.Done()
FindDirs(os.Args[1], []int64{61267}, wg, dirlistchan)
}()
go func() {
wg.Wait()
close(dirlistchan)
}()
for i := range dirlistchan {
fmt.Println(i)
}
wg.Wait()
}
func FindDirs(dir string, nativePartitions []int64, wg *sync.WaitGroup, dirlistchan chan string) {
fd, err := os.Open(dir)
if err != nil {
panic(err)
}
filenames, err := fd.Readdir(0)
if err != nil {
panic(err)
}
for _, i := range filenames {
var buff bytes.Buffer
buff.WriteString(dir)
switch dir {
case "/":
default:
buff.WriteString("/")
}
buff.WriteString(i.Name())
/*err := os.Chdir(dir)
if err != nil {
return err
}*/
t := new(syscall.Statfs_t)
err = syscall.Statfs(buff.String(), t)
if err != nil {
//fmt.Println("Error accessing", buff.String())
}
if checkDirIsNative(t.Type, nativePartitions) && i.IsDir() {
dirlistchan <- buff.String()
FindDirs(buff.String(), nativePartitions, wg, dirlistchan) //recursion happens here
} else {
//fmt.Println(i.Name(), "is not native")
}
}
}
func checkDirIsNative(dirtype int64, nativetypes []int64) bool {
for _, i := range nativetypes {
if dirtype == i {
return true
}
}
return false
}
Find the go.play link here
As has been stated already you should close the channel if you want the main goroutine to exit.
Example of implementation :
In function func FindDirs you could make an additional channel for every recursive func FindDirs call that this function is going to make and pass that new channel in the argument. Then simultaneously listen to all those new channels and forward the strings back to the channel that function got in the argument.
After all new channels has been closed close the channel given in the argument.
In other words every func call should have its own channel that it sends to. The string is then forwarded all the way to main function.
Dynamic select described here : how to listen to N channels? (dynamic select statement)

Getting "127.0.0.1 can't assign requested address" - http.Client

What I'm doing is fairly straight-forward. I need to create a "proxy" server that is very minimal and fast. Currently I have a baseline server that is proxied to (nodejs) and a proxy-service (go). Please excuse the lack of actual "proxy'ing" - just testing for now.
Baseline Service
var http = require('http');
http.createServer(function (req, res) {
// console.log("received request");
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(8080, '127.0.0.1');
console.log('Server running at http://127.0.0.1:8080/');
Proxy Service
package main
import (
"flag"
"log"
"net/http"
"net/url"
)
var (
listen = flag.String("listen", "0.0.0.0:9000", "listen on address")
logp = flag.Bool("log", false, "enable logging")
)
func main() {
flag.Parse()
proxyHandler := http.HandlerFunc(proxyHandlerFunc)
log.Fatal(http.ListenAndServe(*listen, proxyHandler))
log.Println("Started router-server on 0.0.0.0:9000")
}
func proxyHandlerFunc(w http.ResponseWriter, r *http.Request) {
// Log if requested
if *logp {
log.Println(r.URL)
}
/*
* Tweak the request as appropriate:
* - RequestURI may not be sent to client
* - Set new URL
*/
r.RequestURI = ""
u, err := url.Parse("http://localhost:8080/")
if err != nil {
log.Fatal(err)
}
r.URL = u
// And proxy
// resp, err := client.Do(r)
c := make(chan *http.Response)
go doRequest(c)
resp := <-c
if resp != nil {
err := resp.Write(w)
if err != nil {
log.Println("Error writing response")
} else {
resp.Body.Close()
}
}
}
func doRequest(c chan *http.Response) {
// new client for every request.
client := &http.Client{}
resp, err := client.Get("http://127.0.0.1:8080/test")
if err != nil {
log.Println(err)
c <- nil
} else {
c <- resp
}
}
My issue, as mentioned within the title, is that I am getting errors stating 2013/10/28 21:22:30 Get http://127.0.0.1:8080/test: dial tcp 127.0.0.1:8080: can't assign requested address from the doRequest function, and I have no clue why. Googling this particular error yields seemingly irrelevant results.
There are 2 major problems with this code.
You are not handling the client stalling or using keep alives (handled below by getTimeoutServer)
You are not handling the server (what your http.Client is talking to) timing out (handled below by TimeoutConn).
This is probably why you are exhausting your local ports. I know from past experience node.js will keep-alive you very aggressively.
There are lots of little issues, creating objects every-time when you don't need to. Creating unneeded goroutines (each incoming request is in its own goroutine before you handle it).
Here is a quick stab (that I don't have time to test well). Hopefully it will put you on the right track: (You will want to upgrade this to not buffer the responses locally)
package main
import (
"bytes"
"errors"
"flag"
"fmt"
"log"
"net"
"net/http"
"net/url"
"runtime"
"strconv"
"time"
)
const DEFAULT_IDLE_TIMEOUT = 5 * time.Second
var (
listen string
logOn bool
localhost, _ = url.Parse("http://localhost:8080/")
client = &http.Client{
Transport: &http.Transport{
Proxy: NoProxyAllowed,
Dial: func(network, addr string) (net.Conn, error) {
return NewTimeoutConnDial(network, addr, DEFAULT_IDLE_TIMEOUT)
},
},
}
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
flag.StringVar(&listen, "listen", "0.0.0.0:9000", "listen on address")
flag.BoolVar(&logOn, "log", true, "enable logging")
flag.Parse()
server := getTimeoutServer(listen, http.HandlerFunc(proxyHandlerFunc))
log.Printf("Starting router-server on %s\n", listen)
log.Fatal(server.ListenAndServe())
}
func proxyHandlerFunc(w http.ResponseWriter, req *http.Request) {
if logOn {
log.Printf("%+v\n", req)
}
// Setup request URL
origURL := req.URL
req.URL = new(url.URL)
*req.URL = *localhost
req.URL.Path, req.URL.RawQuery, req.URL.Fragment = origURL.Path, origURL.RawQuery, origURL.Fragment
req.RequestURI, req.Host = "", req.URL.Host
// Perform request
resp, err := client.Do(req)
if err != nil {
w.WriteHeader(http.StatusBadGateway)
w.Write([]byte(fmt.Sprintf("%d - StatusBadGateway: %s", http.StatusBadGateway, err)))
return
}
defer resp.Body.Close()
var respBuffer *bytes.Buffer
if resp.ContentLength != -1 {
respBuffer = bytes.NewBuffer(make([]byte, 0, resp.ContentLength))
} else {
respBuffer = new(bytes.Buffer)
}
if _, err = respBuffer.ReadFrom(resp.Body); err != nil {
w.WriteHeader(http.StatusBadGateway)
w.Write([]byte(fmt.Sprintf("%d - StatusBadGateway: %s", http.StatusBadGateway, err)))
return
}
// Write result of request
headers := w.Header()
var key string
var val []string
for key, val = range resp.Header {
headers[key] = val
}
headers.Set("Content-Length", strconv.Itoa(respBuffer.Len()))
w.WriteHeader(resp.StatusCode)
w.Write(respBuffer.Bytes())
}
func getTimeoutServer(addr string, handler http.Handler) *http.Server {
//keeps people who are slow or are sending keep-alives from eating all our sockets
const (
HTTP_READ_TO = DEFAULT_IDLE_TIMEOUT
HTTP_WRITE_TO = DEFAULT_IDLE_TIMEOUT
)
return &http.Server{
Addr: addr,
Handler: handler,
ReadTimeout: HTTP_READ_TO,
WriteTimeout: HTTP_WRITE_TO,
}
}
func NoProxyAllowed(request *http.Request) (*url.URL, error) {
return nil, nil
}
//TimeoutConn-------------------------
//Put me in my own TimeoutConn.go ?
type TimeoutConn struct {
net.Conn
readTimeout, writeTimeout time.Duration
}
var invalidOperationError = errors.New("TimeoutConn does not support or allow .SetDeadline operations")
func NewTimeoutConn(conn net.Conn, ioTimeout time.Duration) (*TimeoutConn, error) {
return NewTimeoutConnReadWriteTO(conn, ioTimeout, ioTimeout)
}
func NewTimeoutConnReadWriteTO(conn net.Conn, readTimeout, writeTimeout time.Duration) (*TimeoutConn, error) {
this := &TimeoutConn{
Conn: conn,
readTimeout: readTimeout,
writeTimeout: writeTimeout,
}
now := time.Now()
err := this.Conn.SetReadDeadline(now.Add(this.readTimeout))
if err != nil {
return nil, err
}
err = this.Conn.SetWriteDeadline(now.Add(this.writeTimeout))
if err != nil {
return nil, err
}
return this, nil
}
func NewTimeoutConnDial(network, addr string, ioTimeout time.Duration) (net.Conn, error) {
conn, err := net.DialTimeout(network, addr, ioTimeout)
if err != nil {
return nil, err
}
if conn, err = NewTimeoutConn(conn, ioTimeout); err != nil {
return nil, err
}
return conn, nil
}
func (this *TimeoutConn) Read(data []byte) (int, error) {
this.Conn.SetReadDeadline(time.Now().Add(this.readTimeout))
return this.Conn.Read(data)
}
func (this *TimeoutConn) Write(data []byte) (int, error) {
this.Conn.SetWriteDeadline(time.Now().Add(this.writeTimeout))
return this.Conn.Write(data)
}
func (this *TimeoutConn) SetDeadline(time time.Time) error {
return invalidOperationError
}
func (this *TimeoutConn) SetReadDeadline(time time.Time) error {
return invalidOperationError
}
func (this *TimeoutConn) SetWriteDeadline(time time.Time) error {
return invalidOperationError
}
We ran into this and after a lot of time trying to debug, I came across this: https://code.google.com/p/go/source/detail?r=d4e1ec84876c
This shifts the burden onto clients to read their whole response
bodies if they want the advantage of reusing TCP connections.
So be sure you read the entire body before closing, there are a couple of ways to do it. This function can come in handy to close to let you see whether you have this issue by logging the extra bytes that haven't been read and cleaning the stream out for you so it can reuse the connection:
func closeResponse(response *http.Response) error {
// ensure we read the entire body
bs, err2 := ioutil.ReadAll(response.Body)
if err2 != nil {
log.Println("Error during ReadAll!!", err2)
}
if len(bs) > 0 {
log.Println("Had to read some bytes, not good!", bs, string(bs))
}
return response.Body.Close()
}
Or if you really don't care about the body, you can just discard it with this:
io.Copy(ioutil.Discard, response.Body)
I have encountered this problem too, and i add an option {DisableKeepAlives: true} to http.Transport fixed this issue, you can have a try.
I came here when running a massive amount of SQL queries per second on a system without limiting the number of idle connections over a long period of time. As pointed out in this issue comment on github explicitly setting db.SetMaxIdleConns(5) completely solved my problem.

Resources