func benchmarkNetHTTPClientEndToEndBigResponseInmemory(b *testing.B, parallelism int) { bigResponse := createFixedBody(1024 * 1024) h := func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") w.Write(bigResponse) } ln := fasthttputil.NewInmemoryListener() ch := make(chan struct{}) go func() { if err := http.Serve(ln, http.HandlerFunc(h)); err != nil && !strings.Contains( err.Error(), "use of closed network connection") { b.Fatalf("error when serving requests: %s", err) } close(ch) }() c := &http.Client{ Transport: &http.Transport{ Dial: func(_, _ string) (net.Conn, error) { return ln.Dial() }, MaxIdleConnsPerHost: parallelism * runtime.GOMAXPROCS(-1), }, Timeout: 5 * time.Second, } requestURI := "/foo/bar?baz=123" url := "http://unused.host" + requestURI b.SetParallelism(parallelism) b.RunParallel(func(pb *testing.PB) { req, err := http.NewRequest("GET", url, nil) if err != nil { b.Fatalf("unexpected error: %s", err) } for pb.Next() { resp, err := c.Do(req) if err != nil { b.Fatalf("unexpected error: %s", err) } if resp.StatusCode != http.StatusOK { b.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode, http.StatusOK) } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { b.Fatalf("unexpected error when reading response body: %s", err) } if !bytes.Equal(bigResponse, body) { b.Fatalf("unexpected response %q. Expecting %q", body, bigResponse) } } }) ln.Close() select { case <-ch: case <-time.After(time.Second): b.Fatalf("server wasn't stopped") } }
func TestHostClientMultipleAddrs(t *testing.T) { ln := fasthttputil.NewInmemoryListener() s := &Server{ Handler: func(ctx *RequestCtx) { ctx.Write(ctx.Host()) ctx.SetConnectionClose() }, } serverStopCh := make(chan struct{}) go func() { if err := s.Serve(ln); err != nil { t.Fatalf("unexpected error: %s", err) } close(serverStopCh) }() dialsCount := make(map[string]int) c := &HostClient{ Addr: "foo,bar,baz", Dial: func(addr string) (net.Conn, error) { dialsCount[addr]++ return ln.Dial() }, } for i := 0; i < 9; i++ { statusCode, body, err := c.Get(nil, "http://foobar/baz/aaa?bbb=ddd") if err != nil { t.Fatalf("unexpected error: %s", err) } if statusCode != StatusOK { t.Fatalf("unexpected status code %d. Expecting %d", statusCode, StatusOK) } if string(body) != "foobar" { t.Fatalf("unexpected body %q. Expecting %q", body, "foobar") } } if err := ln.Close(); err != nil { t.Fatalf("unexpected error: %s", err) } select { case <-serverStopCh: case <-time.After(time.Second): t.Fatalf("timeout") } if len(dialsCount) != 3 { t.Fatalf("unexpected dialsCount size %d. Expecting 3", len(dialsCount)) } for _, k := range []string{"foo", "bar", "baz"} { if dialsCount[k] != 3 { t.Fatalf("unexpected dialsCount for %q. Expecting 3", k) } } }
func testPipelineClientDoConcurrent(t *testing.T, concurrency int, maxBatchDelay time.Duration, maxConns int) { ln := fasthttputil.NewInmemoryListener() s := &Server{ Handler: func(ctx *RequestCtx) { ctx.WriteString("OK") }, } serverStopCh := make(chan struct{}) go func() { if err := s.Serve(ln); err != nil { t.Fatalf("unexpected error: %s", err) } close(serverStopCh) }() c := &PipelineClient{ Dial: func(addr string) (net.Conn, error) { return ln.Dial() }, MaxConns: maxConns, MaxPendingRequests: concurrency, MaxBatchDelay: maxBatchDelay, Logger: &customLogger{}, } clientStopCh := make(chan struct{}, concurrency) for i := 0; i < concurrency; i++ { go func() { testPipelineClientDo(t, c) clientStopCh <- struct{}{} }() } for i := 0; i < concurrency; i++ { select { case <-clientStopCh: case <-time.After(3 * time.Second): t.Fatalf("timeout") } } if c.PendingRequests() != 0 { t.Fatalf("unexpected number of pending requests: %d. Expecting zero", c.PendingRequests()) } if err := ln.Close(); err != nil { t.Fatalf("unexpected error: %s", err) } select { case <-serverStopCh: case <-time.After(time.Second): t.Fatalf("timeout") } }
func benchmarkPipelineClient(b *testing.B, parallelism int) { h := func(ctx *RequestCtx) { ctx.WriteString("foobar") } ln := fasthttputil.NewInmemoryListener() ch := make(chan struct{}) go func() { if err := Serve(ln, h); err != nil { b.Fatalf("error when serving requests: %s", err) } close(ch) }() var clients []*PipelineClient for i := 0; i < runtime.GOMAXPROCS(-1); i++ { c := &PipelineClient{ Dial: func(addr string) (net.Conn, error) { return ln.Dial() }, ReadBufferSize: 1024 * 1024, WriteBufferSize: 1024 * 1024, MaxPendingRequests: parallelism, } clients = append(clients, c) } clientID := uint32(0) requestURI := "/foo/bar?baz=123" url := "http://unused.host" + requestURI b.SetParallelism(parallelism) b.RunParallel(func(pb *testing.PB) { n := atomic.AddUint32(&clientID, 1) c := clients[n%uint32(len(clients))] var req Request req.SetRequestURI(url) var resp Response for pb.Next() { if err := c.Do(&req, &resp); err != nil { b.Fatalf("unexpected error: %s", err) } if resp.StatusCode() != StatusOK { b.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK) } body := resp.Body() if string(body) != "foobar" { b.Fatalf("unexpected response %q. Expecting %q", body, "foobar") } } }) ln.Close() select { case <-ch: case <-time.After(time.Second): b.Fatalf("server wasn't stopped") } }
func benchmark(b *testing.B, h fasthttp.RequestHandler, isTLS bool) { ln := fasthttputil.NewInmemoryListener() serverStopCh := startServer(b, ln, h, isTLS) c := newClient(ln, isTLS) b.RunParallel(func(pb *testing.PB) { runRequests(b, pb, c) }) ln.Close() <-serverStopCh }
func TestHostClientMaxConnDuration(t *testing.T) { ln := fasthttputil.NewInmemoryListener() connectionCloseCount := uint32(0) s := &Server{ Handler: func(ctx *RequestCtx) { ctx.WriteString("abcd") if ctx.Request.ConnectionClose() { atomic.AddUint32(&connectionCloseCount, 1) } }, } serverStopCh := make(chan struct{}) go func() { if err := s.Serve(ln); err != nil { t.Fatalf("unexpected error: %s", err) } close(serverStopCh) }() c := &HostClient{ Addr: "foobar", Dial: func(addr string) (net.Conn, error) { return ln.Dial() }, MaxConnDuration: 10 * time.Millisecond, } for i := 0; i < 5; i++ { statusCode, body, err := c.Get(nil, "http://aaaa.com/bbb/cc") if err != nil { t.Fatalf("unexpected error: %s", err) } if statusCode != StatusOK { t.Fatalf("unexpected status code %d. Expecting %d", statusCode, StatusOK) } if string(body) != "abcd" { t.Fatalf("unexpected body %q. Expecting %q", body, "abcd") } time.Sleep(c.MaxConnDuration) } if err := ln.Close(); err != nil { t.Fatalf("unexpected error: %s", err) } select { case <-serverStopCh: case <-time.After(time.Second): t.Fatalf("timeout") } if connectionCloseCount == 0 { t.Fatalf("expecting at least one 'Connection: close' request header") } }
func testWorkerPoolPanicError(t *testing.T, wp *workerPool) { wp.Start() ln := fasthttputil.NewInmemoryListener() clientsCount := 10 clientCh := make(chan struct{}, clientsCount) for i := 0; i < clientsCount; i++ { go func() { conn, err := ln.Dial() if err != nil { t.Fatalf("unexpected error: %s", err) } data, err := ioutil.ReadAll(conn) if err != nil { t.Fatalf("unexpected error: %s", err) } if len(data) > 0 { t.Fatalf("unexpected data read: %q. Expecting empty data", data) } if err = conn.Close(); err != nil { t.Fatalf("unexpected error: %s", err) } clientCh <- struct{}{} }() } for i := 0; i < clientsCount; i++ { conn, err := ln.Accept() if err != nil { t.Fatalf("unexpected error: %s", err) } if !wp.Serve(conn) { t.Fatalf("worker pool mustn't be full") } } for i := 0; i < clientsCount; i++ { select { case <-clientCh: case <-time.After(time.Second): t.Fatalf("timeout") } } if err := ln.Close(); err != nil { t.Fatalf("unexpected error: %s", err) } wp.Stop() }
func TestClientDoTimeoutDisableNormalizing(t *testing.T) { ln := fasthttputil.NewInmemoryListener() s := &Server{ Handler: func(ctx *RequestCtx) { ctx.Response.Header.Set("foo-BAR", "baz") }, DisableHeaderNamesNormalizing: true, } serverStopCh := make(chan struct{}) go func() { if err := s.Serve(ln); err != nil { t.Fatalf("unexpected error: %s", err) } close(serverStopCh) }() c := &Client{ Dial: func(addr string) (net.Conn, error) { return ln.Dial() }, DisableHeaderNamesNormalizing: true, } var req Request req.SetRequestURI("http://aaaai.com/bsdf?sddfsd") var resp Response for i := 0; i < 5; i++ { if err := c.DoTimeout(&req, &resp, time.Second); err != nil { t.Fatalf("unexpected error: %s", err) } hv := resp.Header.Peek("foo-BAR") if string(hv) != "baz" { t.Fatalf("unexpected header value: %q. Expecting %q", hv, "baz") } hv = resp.Header.Peek("Foo-Bar") if len(hv) > 0 { t.Fatalf("unexpected non-empty header value %q", hv) } } if err := ln.Close(); err != nil { t.Fatalf("unexpected error: %s", err) } select { case <-serverStopCh: case <-time.After(time.Second): t.Fatalf("timeout") } }
func benchmarkClientEndToEndBigResponseInmemory(b *testing.B, parallelism int) { bigResponse := createFixedBody(1024 * 1024) h := func(ctx *RequestCtx) { ctx.SetContentType("text/plain") ctx.Write(bigResponse) } ln := fasthttputil.NewInmemoryListener() ch := make(chan struct{}) go func() { if err := Serve(ln, h); err != nil { b.Fatalf("error when serving requests: %s", err) } close(ch) }() c := &Client{ MaxConnsPerHost: runtime.GOMAXPROCS(-1) * parallelism, Dial: func(addr string) (net.Conn, error) { return ln.Dial() }, } requestURI := "/foo/bar?baz=123" url := "http://unused.host" + requestURI b.SetParallelism(parallelism) b.RunParallel(func(pb *testing.PB) { var req Request req.SetRequestURI(url) var resp Response for pb.Next() { if err := c.DoTimeout(&req, &resp, 5*time.Second); err != nil { b.Fatalf("unexpected error: %s", err) } if resp.StatusCode() != StatusOK { b.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode(), StatusOK) } body := resp.Body() if !bytes.Equal(bigResponse, body) { b.Fatalf("unexpected response %q. Expecting %q", body, bigResponse) } } }) ln.Close() select { case <-ch: case <-time.After(time.Second): b.Fatalf("server wasn't stopped") } }
func benchmarkNetHTTPClientGetEndToEndInmemory(b *testing.B, parallelism int) { ln := fasthttputil.NewInmemoryListener() ch := make(chan struct{}) go func() { if err := http.Serve(ln, http.HandlerFunc(nethttpEchoHandler)); err != nil && !strings.Contains( err.Error(), "use of closed network connection") { b.Fatalf("error when serving requests: %s", err) } close(ch) }() c := &http.Client{ Transport: &http.Transport{ Dial: func(_, _ string) (net.Conn, error) { return ln.Dial() }, MaxIdleConnsPerHost: parallelism * runtime.GOMAXPROCS(-1), }, } requestURI := "/foo/bar?baz=123" url := "http://unused.host" + requestURI b.SetParallelism(parallelism) b.RunParallel(func(pb *testing.PB) { for pb.Next() { resp, err := c.Get(url) if err != nil { b.Fatalf("unexpected error: %s", err) } if resp.StatusCode != http.StatusOK { b.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode, http.StatusOK) } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { b.Fatalf("unexpected error when reading response body: %s", err) } if string(body) != requestURI { b.Fatalf("unexpected response %q. Expecting %q", body, requestURI) } } }) ln.Close() select { case <-ch: case <-time.After(time.Second): b.Fatalf("server wasn't stopped") } }
func benchmarkClientGetEndToEndInmemory(b *testing.B, parallelism int) { ln := fasthttputil.NewInmemoryListener() ch := make(chan struct{}) go func() { if err := Serve(ln, fasthttpEchoHandler); err != nil { b.Fatalf("error when serving requests: %s", err) } close(ch) }() c := &Client{ MaxConnsPerHost: runtime.GOMAXPROCS(-1) * parallelism, Dial: func(addr string) (net.Conn, error) { return ln.Dial() }, } requestURI := "/foo/bar?baz=123" url := "http://unused.host" + requestURI b.SetParallelism(parallelism) b.RunParallel(func(pb *testing.PB) { var buf []byte for pb.Next() { statusCode, body, err := c.Get(buf, url) if err != nil { b.Fatalf("unexpected error: %s", err) } if statusCode != StatusOK { b.Fatalf("unexpected status code: %d. Expecting %d", statusCode, StatusOK) } if string(body) != requestURI { b.Fatalf("unexpected response %q. Expecting %q", body, requestURI) } buf = body } }) ln.Close() select { case <-ch: case <-time.After(time.Second): b.Fatalf("server wasn't stopped") } }
func testWorkerPoolMaxWorkersCount(t *testing.T) { ready := make(chan struct{}) wp := &workerPool{ WorkerFunc: func(conn net.Conn) error { buf := make([]byte, 100) n, err := conn.Read(buf) if err != nil { t.Fatalf("unexpected error: %s", err) } buf = buf[:n] if string(buf) != "foobar" { t.Fatalf("unexpected data read: %q. Expecting %q", buf, "foobar") } if _, err = conn.Write([]byte("baz")); err != nil { t.Fatalf("unexpected error: %s", err) } <-ready return nil }, MaxWorkersCount: 10, Logger: defaultLogger, } wp.Start() ln := fasthttputil.NewInmemoryListener() clientCh := make(chan struct{}, wp.MaxWorkersCount) for i := 0; i < wp.MaxWorkersCount; i++ { go func() { conn, err := ln.Dial() if err != nil { t.Fatalf("unexpected error: %s", err) } if _, err = conn.Write([]byte("foobar")); err != nil { t.Fatalf("unexpected error: %s", err) } data, err := ioutil.ReadAll(conn) if err != nil { t.Fatalf("unexpected error: %s", err) } if string(data) != "baz" { t.Fatalf("unexpected value read: %q. Expecting %q", data, "baz") } if err = conn.Close(); err != nil { t.Fatalf("unexpected error: %s", err) } clientCh <- struct{}{} }() } for i := 0; i < wp.MaxWorkersCount; i++ { conn, err := ln.Accept() if err != nil { t.Fatalf("unexpected error: %s", err) } if !wp.Serve(conn) { t.Fatalf("worker pool must have enough workers to serve the conn") } } go func() { if _, err := ln.Dial(); err != nil { t.Fatalf("unexpected error: %s", err) } }() conn, err := ln.Accept() if err != nil { t.Fatalf("unexpected error: %s", err) } for i := 0; i < 5; i++ { if wp.Serve(conn) { t.Fatalf("worker pool must be full") } } if err = conn.Close(); err != nil { t.Fatalf("unexpected error: %s", err) } close(ready) for i := 0; i < wp.MaxWorkersCount; i++ { select { case <-clientCh: case <-time.After(time.Second): t.Fatalf("timeout") } } if err := ln.Close(); err != nil { t.Fatalf("unexpected error: %s", err) } wp.Stop() }
func TestHostClientMaxConnsWithDeadline(t *testing.T) { var ( emptyBodyCount uint8 ln = fasthttputil.NewInmemoryListener() timeout = 50 * time.Millisecond wg sync.WaitGroup ) s := &Server{ Handler: func(ctx *RequestCtx) { if len(ctx.PostBody()) == 0 { emptyBodyCount++ } ctx.WriteString("foo") }, } serverStopCh := make(chan struct{}) go func() { if err := s.Serve(ln); err != nil { t.Fatalf("unexpected error: %s", err) } close(serverStopCh) }() c := &HostClient{ Addr: "foobar", Dial: func(addr string) (net.Conn, error) { return ln.Dial() }, MaxConns: 1, } for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() req := AcquireRequest() req.SetRequestURI("http://foobar/baz") req.Header.SetMethod("POST") req.SetBodyString("bar") resp := AcquireResponse() for { if err := c.DoDeadline(req, resp, time.Now().Add(timeout)); err != nil { if err == ErrNoFreeConns { time.Sleep(time.Millisecond) continue } t.Fatalf("unexpected error: %s", err) } break } if resp.StatusCode() != StatusOK { t.Fatalf("unexpected status code %d. Expecting %d", resp.StatusCode(), StatusOK) } body := resp.Body() if string(body) != "foo" { t.Fatalf("unexpected body %q. Expecting %q", body, "abcd") } }() } wg.Wait() if err := ln.Close(); err != nil { t.Fatalf("unexpected error: %s", err) } select { case <-serverStopCh: case <-time.After(time.Second): t.Fatalf("timeout") } if emptyBodyCount > 0 { t.Fatalf("at least one request body was empty") } }
func TestHostClientPendingRequests(t *testing.T) { const concurrency = 10 doneCh := make(chan struct{}) readyCh := make(chan struct{}, concurrency) s := &Server{ Handler: func(ctx *RequestCtx) { readyCh <- struct{}{} <-doneCh }, } ln := fasthttputil.NewInmemoryListener() serverStopCh := make(chan struct{}) go func() { if err := s.Serve(ln); err != nil { t.Fatalf("unexpected error: %s", err) } close(serverStopCh) }() c := &HostClient{ Addr: "foobar", Dial: func(addr string) (net.Conn, error) { return ln.Dial() }, } pendingRequests := c.PendingRequests() if pendingRequests != 0 { t.Fatalf("non-zero pendingRequests: %d", pendingRequests) } resultCh := make(chan error, concurrency) for i := 0; i < concurrency; i++ { go func() { req := AcquireRequest() req.SetRequestURI("http://foobar/baz") resp := AcquireResponse() if err := c.DoTimeout(req, resp, 10*time.Second); err != nil { resultCh <- fmt.Errorf("unexpected error: %s", err) return } if resp.StatusCode() != StatusOK { resultCh <- fmt.Errorf("unexpected status code %d. Expecting %d", resp.StatusCode(), StatusOK) return } resultCh <- nil }() } // wait while all the requests reach server for i := 0; i < concurrency; i++ { select { case <-readyCh: case <-time.After(time.Second): t.Fatalf("timeout") } } pendingRequests = c.PendingRequests() if pendingRequests != concurrency { t.Fatalf("unexpected pendingRequests: %d. Expecting %d", pendingRequests, concurrency) } // unblock request handlers on the server and wait until all the requests are finished. close(doneCh) for i := 0; i < concurrency; i++ { select { case err := <-resultCh: if err != nil { t.Fatalf("unexpected error: %s", err) } case <-time.After(time.Second): t.Fatalf("timeout") } } pendingRequests = c.PendingRequests() if pendingRequests != 0 { t.Fatalf("non-zero pendingRequests: %d", pendingRequests) } // stop the server if err := ln.Close(); err != nil { t.Fatalf("unexpected error: %s", err) } select { case <-serverStopCh: case <-time.After(time.Second): t.Fatalf("timeout") } }