From a278fa162bfaab0c6c87bef5b18d5476e3764b2d Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Fri, 27 Apr 2018 13:16:50 -0700 Subject: [PATCH] Add repeat option support Add an option to repeat the same request multiple times, reusing the same HTTP connection. To have accurate measurement of TLS and support skipped steps (DNS, TCP and TLS reuse), the timing code has been refactored to use Go 1.8 support for TLS tracing as well as a more descriptive variable namings for steps time. This commit removes support for Go versions lower than 1.8. --- main.go | 73 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/main.go b/main.go index bf03b74..d975097 100644 --- a/main.go +++ b/main.go @@ -52,6 +52,7 @@ var ( // Command line flags. httpMethod string postBody string + repeat int followRedirects bool onlyHeader bool insecure bool @@ -72,6 +73,7 @@ const maxRedirects = 10 func init() { flag.StringVar(&httpMethod, "X", "GET", "HTTP method to use") flag.StringVar(&postBody, "d", "", "the body of a POST or PUT request; from file use @filename") + flag.IntVar(&repeat, "r", 0, "Repeat the same request N times on the same connection") flag.BoolVar(&followRedirects, "L", false, "follow 30x redirects") flag.BoolVar(&onlyHeader, "I", false, "don't read body of request") flag.BoolVar(&insecure, "k", false, "allow insecure SSL connections") @@ -127,8 +129,11 @@ func main() { } url := parseURL(args[0]) + tr := newTransport(url) - visit(url) + for i := repeat + 1; i > 0; i-- { + visit(tr, url) + } } // readClientCert - helper function to read client certificate @@ -197,9 +202,42 @@ func headerKeyValue(h string) (string, string) { return strings.TrimRight(h[:i], " "), strings.TrimLeft(h[i:], " :") } +func newTransport(url *url.URL) *http.Transport { + tr := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + switch url.Scheme { + case "https": + host, _, err := net.SplitHostPort(url.Host) + if err != nil { + host = url.Host + } + + tr.TLSClientConfig = &tls.Config{ + ServerName: host, + InsecureSkipVerify: insecure, + Certificates: readClientCert(clientCertFile), + } + + // Because we create a custom TLSClientConfig, we have to opt-in to HTTP/2. + // See https://github.com/golang/go/issues/14275 + err = http2.ConfigureTransport(tr) + if err != nil { + log.Fatalf("failed to prepare transport for HTTP/2: %v", err) + } + } + + return tr +} + // visit visits a url and times the interaction. // If the response is a 30x, visit follows the redirect. -func visit(url *url.URL) { +func visit(tr *http.Transport, url *url.URL) { req := newRequest(httpMethod, url, postBody) var tStart, tDNSStart, tDNSEnd, tConnectStart, tConnectEnd, tTLSStart, tTLSEnd, tConnected, tTTBF, tDone time.Time @@ -229,35 +267,6 @@ func visit(url *url.URL) { } req = req.WithContext(httptrace.WithClientTrace(context.Background(), trace)) - tr := &http.Transport{ - Proxy: http.ProxyFromEnvironment, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - } - - switch url.Scheme { - case "https": - host, _, err := net.SplitHostPort(req.Host) - if err != nil { - host = req.Host - } - - tr.TLSClientConfig = &tls.Config{ - ServerName: host, - InsecureSkipVerify: insecure, - Certificates: readClientCert(clientCertFile), - } - - // Because we create a custom TLSClientConfig, we have to opt-in to HTTP/2. - // See https://github.com/golang/go/issues/14275 - err = http2.ConfigureTransport(tr) - if err != nil { - log.Fatalf("failed to prepare transport for HTTP/2: %v", err) - } - } - client := &http.Client{ Transport: tr, CheckRedirect: func(req *http.Request, via []*http.Request) error { @@ -363,7 +372,7 @@ func visit(url *url.URL) { log.Fatalf("maximum number of redirects (%d) followed", maxRedirects) } - visit(loc) + visit(tr, loc) } }