Last active
April 14, 2018 05:38
-
-
Save na--/b205af9648155b477757183dac68b135 to your computer and use it in GitHub Desktop.
HTTP testing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "bytes" | |
| "flag" | |
| "io" | |
| "log" | |
| "net" | |
| "net/http" | |
| "net/url" | |
| "strings" | |
| "sync" | |
| "sync/atomic" | |
| "time" | |
| "github.com/valyala/fasthttp" | |
| ) | |
| const allTestNames = "stdlib-simple,stdlib-optimized,fasthttp,telnet" | |
| var vus uint | |
| var duration, pause time.Duration | |
| var urlToTest, testNames string | |
| var expectedResponse string | |
| var expectedResponseBytes []byte | |
| var expectedResponseLen int64 | |
| func init() { | |
| flag.UintVar(&vus, "u", 8, "Number of virtual users (concurrent goroutines)") | |
| flag.DurationVar(&duration, "d", 5*time.Second, "Test duration") | |
| flag.DurationVar(&pause, "p", 3*time.Second, "Time to sleep between tests") | |
| flag.StringVar(&expectedResponse, "er", "Hello", "Expected response") | |
| flag.StringVar(&urlToTest, "url", "http://127.0.0.1:8000", "The URL to hammer") | |
| flag.StringVar(&testNames, "t", allTestNames, "A comma separated list of tests to perform") | |
| flag.Parse() | |
| expectedResponseBytes = []byte(expectedResponse) | |
| expectedResponseLen = int64(len(expectedResponseBytes)) | |
| } | |
| func getTest(name string) func() bool { | |
| switch name { | |
| case "stdlib-simple": | |
| return func() bool { | |
| resp, err := http.Get(urlToTest) | |
| if err != nil { | |
| return false | |
| } | |
| defer resp.Body.Close() | |
| buf := bytes.NewBuffer([]byte{}) | |
| respLen, err := io.Copy(buf, resp.Body) | |
| return err == nil && | |
| respLen == expectedResponseLen && | |
| bytes.Equal(expectedResponseBytes, buf.Bytes()) | |
| } | |
| case "stdlib-optimized": | |
| transport := &http.Transport{ | |
| Proxy: nil, | |
| DialContext: (&net.Dialer{ | |
| Timeout: 100 * time.Millisecond, | |
| KeepAlive: 30 * time.Second, | |
| DualStack: false, | |
| }).DialContext, | |
| DisableKeepAlives: false, | |
| DisableCompression: true, | |
| MaxIdleConns: 0, // no limit | |
| IdleConnTimeout: 1 * time.Second, | |
| ResponseHeaderTimeout: 100 * time.Millisecond, | |
| TLSHandshakeTimeout: 1 * time.Second, | |
| ExpectContinueTimeout: 1 * time.Second, | |
| } | |
| req, err := http.NewRequest("GET", urlToTest, nil) | |
| if err != nil { | |
| panic(err) | |
| } | |
| respBodyBuffer := make([]byte, 0, expectedResponseLen) | |
| return func() bool { | |
| resp, err := transport.RoundTrip(req) | |
| if err != nil { | |
| log.Println(err) | |
| return false | |
| } | |
| defer resp.Body.Close() | |
| buf := bytes.NewBuffer(respBodyBuffer) | |
| respLen, err := io.Copy(buf, resp.Body) | |
| return err == nil && | |
| respLen == expectedResponseLen && | |
| bytes.Equal(expectedResponseBytes, buf.Bytes()) | |
| } | |
| case "fasthttp": | |
| req := fasthttp.AcquireRequest() | |
| req.SetRequestURI(urlToTest) | |
| client := &fasthttp.Client{} | |
| return func() bool { | |
| resp := fasthttp.AcquireResponse() | |
| err := client.Do(req, resp) | |
| return err == nil && bytes.Equal(expectedResponseBytes, resp.Body()) | |
| } | |
| case "telnet": | |
| parsedURL, err := url.Parse(urlToTest) | |
| if err != nil { | |
| panic(err) | |
| } | |
| tcpAddr, err := net.ResolveTCPAddr("tcp4", parsedURL.Host) | |
| if err != nil { | |
| panic(err) | |
| } | |
| request := []byte("GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n") | |
| respBuffer := make([]byte, 1000+expectedResponseLen) | |
| var conn *net.TCPConn | |
| return func() bool { | |
| if conn == nil { | |
| //log.Printf("Dialing tcp directly...") | |
| conn, err = net.DialTCP("tcp4", nil, tcpAddr) | |
| if err != nil { | |
| return false | |
| } | |
| } | |
| _, err := conn.Write(request) | |
| if err != nil { | |
| conn.Close() | |
| conn = nil | |
| return false | |
| } | |
| pos := int64(0) | |
| for { | |
| // Read until expectedResponse | |
| //TODO: add some timeouts | |
| n, err := conn.Read(respBuffer[pos:]) | |
| if err != nil { | |
| conn.Close() | |
| conn = nil | |
| return false | |
| } | |
| pos += int64(n) | |
| if bytes.Equal(expectedResponseBytes, respBuffer[pos-expectedResponseLen:pos]) { | |
| return true | |
| } | |
| } | |
| } | |
| default: | |
| panic("No such test type") | |
| } | |
| } | |
| func execTest(name string) float64 { | |
| var requests, errors uint64 | |
| wg := sync.WaitGroup{} | |
| log.Printf("[%s] Start hammering %s with %d VUs for %s!", name, urlToTest, vus, duration) | |
| for i := uint(0); i < vus; i++ { | |
| wg.Add(1) | |
| go func(vu uint, test func() bool) { | |
| defer wg.Done() | |
| var localReqCountCopy, newReqCount uint64 | |
| //log.Printf("[%s] VU %d started!", name, vu) | |
| for { | |
| if !test() { | |
| atomic.AddUint64(&errors, 1) | |
| } | |
| newReqCount = atomic.AddUint64(&requests, 1) | |
| if localReqCountCopy > newReqCount { | |
| //log.Printf("[%s] VU %d finished!", name, vu) | |
| return | |
| } | |
| localReqCountCopy = newReqCount | |
| } | |
| }(i, getTest(name)) | |
| } | |
| log.Printf("[%s] Launched %d VUs, sleeping for %s...", name, vus, duration) | |
| time.Sleep(duration) | |
| log.Printf("[%s] Done!", name) | |
| errCount := atomic.SwapUint64(&errors, 0) | |
| reqCount := atomic.SwapUint64(&requests, 0) - errCount | |
| rps := float64(reqCount) / (float64(duration) / float64(time.Second)) | |
| log.Printf("[%s] Made %d requests with %d errors; RPS=%.0f", name, reqCount, errCount, rps) | |
| log.Printf("[%s] Waiting for VUs to finish...", name) | |
| wg.Wait() | |
| return rps | |
| } | |
| func main() { | |
| log.SetFlags(log.Lmicroseconds) | |
| for n, testName := range strings.Split(testNames, ",") { | |
| if n > 0 { | |
| log.Printf("Sleeping for %s before the next test...\n\n", pause) | |
| time.Sleep(pause) | |
| } | |
| log.Printf("Starting test %s...", testName) | |
| execTest(testName) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "net/http" | |
| ) | |
| func sayHello(w http.ResponseWriter, r *http.Request) { | |
| w.Write([]byte("Hello")) | |
| } | |
| func main() { | |
| http.HandleFunc("/", sayHello) | |
| if err := http.ListenAndServe(":8000", nil); err != nil { | |
| panic(err) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment