fasthttp icon indicating copy to clipboard operation
fasthttp copied to clipboard

Is it possible to group pending response from HTTP Client and process new HTTP Request?

Open sujit-baniya opened this issue 5 years ago • 10 comments

Hi, Using HTTP Client, is it possible to group pending response from HTTP Client and process new HTTP Request?

There are cases when the client is too slow and it slow the entire HTTP client Processing. So wondering if this library provides any approaches?

sujit-baniya avatar Jun 12 '20 13:06 sujit-baniya

I'm not sure what you mean? This is a HTTP1 only library. HTTP1 doesn't support out of order responses, only HTTP2 does.

If a client does another request on another connection those will automatically be handled in parallel.

erikdubbelboer avatar Jun 12 '20 19:06 erikdubbelboer

Actually, I was wondering if I could send as much payload to one URL by opening one TCP Connection to that URL and TCP Connection would process each payload one after another.

Here this piece of code sends Request by creating multiple TCP Connection

func main() {
	start := time.Now()
	// var wg sync.WaitGroup
	sliceLength := 500
	for i := 0; i < sliceLength; i++ {
		go func(i int) {
			Request("http://116.203.188.34/send")
			// context.Send(pid, Hello{Who: })
		}(i)
	}
	// wg.Wait()
	elapsed := time.Since(start)
	log.Printf("Binomial took %s", elapsed)
	console.ReadLine()
}

var HttpClient = &fasthttp.Client{
	NoDefaultUserAgentHeader: true, // Don't send: User-Agent: fasthttp
	MaxConnsPerHost:          10000,
	ReadBufferSize:           4096, // Make sure to set this big enough that your whole request can be read at once.
	WriteBufferSize:          4096, // Same but for your response.
	ReadTimeout:              8 * time.Second,
	WriteTimeout:             8 * time.Second,
	MaxIdleConnDuration:      30 * time.Second,

	DisableHeaderNamesNormalizing: true, // If you set the case on your headers correctly you can enable this.
}

func Request(url string) {
	destUrl := []byte(url)

	req := fasthttp.AcquireRequest()
	res := fasthttp.AcquireResponse()

	req.SetRequestURIBytes(destUrl)

	if err := HttpClient.Do(req, res); err != nil {
		println(err.Error())
	}

	fasthttp.ReleaseRequest(req)
	if res.StatusCode() == 200 {
		// fmt.Println(BytesToString(res.Body()))
		fasthttp.ReleaseResponse(res)
	}

}

Is it possible to append as many payloads as possible to one TCP connection and TCP connection would process Payload in FIFO manner

sujit-baniya avatar Jun 15 '20 15:06 sujit-baniya

That is called HTTP Pipelining and you can use fasthttp.PipelineClient for that. But not all servers might support it. The fasthttp Server does.

erikdubbelboer avatar Jun 15 '20 16:06 erikdubbelboer

Great! Thanks @erikdubbelboer Is there any example for that function

sujit-baniya avatar Jun 15 '20 16:06 sujit-baniya

@erikdubbelboer I tried following code but getting Timeout error even I set to 10 min

package bootstrap

import (
	"github.com/valyala/fasthttp" //nolint:goimports
	"net/url"
	"reflect"
	"sync"
	"time"
	"unsafe" //nolint:goimports
)

var HTTPClients = make(map[string]*fasthttp.PipelineClient) //nolint:gochecknoglobals

func CreatePipelineClient(url string) *fasthttp.PipelineClient {
	return &fasthttp.PipelineClient { //nolint:gofmt
		Addr: url,
		MaxConns:            10000,
		MaxPendingRequests:  1000,
		MaxBatchDelay:       0,
		Dial:                nil,
		DialDualStack:       false,
		IsTLS:               false,
		TLSConfig:           nil,
		MaxIdleConnDuration: 30 * time.Minute,
		ReadBufferSize:      4096,
		WriteBufferSize:     4096,
		ReadTimeout:         10 * time.Minute,
		WriteTimeout:        10 * time.Minute,
		Logger:              nil,
	}
}

func Request(uri string) {
	var syncMap sync.Map
	u, _ := url.Parse(uri)
	if _, ok := HTTPClients[u.Host]; !ok {
		syncMap.Store(u.Host, CreatePipelineClient(u.Host))
		HTTPClients[u.Host] = CreatePipelineClient(u.Host)
	}
	client := HTTPClients[u.Host]
	destUrl := []byte(uri)

	req := fasthttp.AcquireRequest()
	res := fasthttp.AcquireResponse()

	req.SetRequestURIBytes(destUrl)

	if err := client.Do(req, res); err != nil {
		println(err.Error())
	}

	fasthttp.ReleaseRequest(req)
	if res.StatusCode() == 200 {
		// fmt.Println(BytesToString(res.Body()))
		fasthttp.ReleaseResponse(res)
	}

}

func BytesToString(b []byte) string {
	bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	sh := reflect.StringHeader{Data: bh.Data, Len: bh.Len}
	return *(*string)(unsafe.Pointer(&sh)) //nolint:wsl
}

Error:

2020/06/16 00:03:48 error in PipelineClient("116.203.188.34"): timeout
2020/06/16 00:03:48 error in PipelineClient("116.203.188.34"): timeout

AND

2020/06/16 00:04:33 error in PipelineClient("116.203.188.34"): dialing to the given TCP address timed out
2020/06/16 00:04:33 error in PipelineClient("116.203.188.34"): dialing to the given TCP address timed out

Can you please suggest me on what mistake have I done in above code?

sujit-baniya avatar Jun 15 '20 18:06 sujit-baniya

These both works for me with your code:

func main() {
  Request("http://34.188.203.116/test")

  Request("http://static.34.188.203.116.clients.your-server.de/test")
}

How are you calling Request?

erikdubbelboer avatar Jun 16 '20 10:06 erikdubbelboer

I was using following

func main() {
start := time.Now()
	// var wg sync.WaitGroup
	sliceLength := 5000
	for i := 0; i < sliceLength; i++ {
		go func(i int) {
			bootstrap.Request("http://116.203.188.34/send")
		}(i)
	}
	// wg.Wait()
	elapsed := time.Since(start)
	log.Printf("Binomial took %s", elapsed)
	console.ReadLine()
}

When there's high number of concurrent request, I get above error.

pipeline connection has been stopped
pipeline connection has been stopped

2020/06/16 18:14:23 error in PipelineClient("116.203.188.34"): EOF
pipeline connection has been stopped

sujit-baniya avatar Jun 16 '20 11:06 sujit-baniya

Looks like the server doesn't want to handle that many connections at the same time. Not much fasthttp can do about that.

erikdubbelboer avatar Jun 18 '20 20:06 erikdubbelboer

I think this is server related, @itsursujit did you managed to solve this issue? If so, please let us know so we can close this 👍

Fenny avatar Aug 16 '20 10:08 Fenny

@Fenny I didn't manage to solve this issue. I didn't have a chance to look into this as I was then using net/http

sujit-baniya avatar Aug 17 '20 02:08 sujit-baniya