Files
hey/hey.go
Jaana Burcu Dogan 28260456e2 Merge pull request #20 from verygoodsoftwarenotvirus/master
Rename inconsistent initialisms
2016-10-15 14:26:33 -07:00

226 lines
5.7 KiB
Go

// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Command hey is an HTTP load generator.
package main
import (
"flag"
"fmt"
"net/http"
gourl "net/url"
"os"
"regexp"
"runtime"
"strings"
"github.com/rakyll/hey/requester"
)
const (
headerRegexp = `^([\w-]+):\s*(.+)`
authRegexp = `^(.+):([^\s].+)`
)
type headerSlice []string
func (h *headerSlice) String() string {
return fmt.Sprintf("%s", *h)
}
func (h *headerSlice) Set(value string) error {
*h = append(*h, value)
return nil
}
var (
headerslice headerSlice
m = flag.String("m", "GET", "")
headers = flag.String("h", "", "")
body = flag.String("d", "", "")
accept = flag.String("A", "", "")
contentType = flag.String("T", "text/html", "")
authHeader = flag.String("a", "", "")
hostHeader = flag.String("host", "", "")
output = flag.String("o", "", "")
c = flag.Int("c", 50, "")
n = flag.Int("n", 200, "")
q = flag.Int("q", 0, "")
t = flag.Int("t", 20, "")
h2 = flag.Bool("h2", false, "")
cpus = flag.Int("cpus", runtime.GOMAXPROCS(-1), "")
disableCompression = flag.Bool("disable-compression", false, "")
disableKeepAlives = flag.Bool("disable-keepalive", false, "")
proxyAddr = flag.String("x", "", "")
enableTrace = flag.Bool("more", false, "")
)
var usage = `Usage: hey [options...] <url>
Options:
-n Number of requests to run. Default is 200.
-c Number of requests to run concurrently. Total number of requests cannot
be smaller than the concurrency level. Default is 50.
-q Rate limit, in seconds (QPS).
-o Output type. If none provided, a summary is printed.
"csv" is the only supported alternative. Dumps the response
metrics in comma-separated values format.
-m HTTP method, one of GET, POST, PUT, DELETE, HEAD, OPTIONS.
-H Custom HTTP header. You can specify as many as needed by repeating the flag.
for example, -H "Accept: text/html" -H "Content-Type: application/xml" .
-t Timeout in seconds. Default is 20, use 0 to disable.
-A HTTP Accept header.
-d HTTP request body.
-T Content-type, defaults to "text/html".
-a Basic authentication, username:password.
-x HTTP Proxy address as host:port.
-h2 Enable HTTP/2.
-disable-compression Disable compression.
-disable-keepalive Disable keep-alive, prevents re-use of TCP
connections between different HTTP requests.
-cpus Number of used cpu cores.
(default for current machine is %d cores)
-host HTTP Host header.
-more Provides information on DNS lookup, dialup, request and response timings.
`
func main() {
flag.Usage = func() {
fmt.Fprint(os.Stderr, fmt.Sprintf(usage, runtime.NumCPU()))
}
flag.Var(&headerslice, "H", "")
flag.Parse()
if flag.NArg() < 1 {
usageAndExit("")
}
runtime.GOMAXPROCS(*cpus)
num := *n
conc := *c
q := *q
if num <= 0 || conc <= 0 {
usageAndExit("n and c cannot be smaller than 1.")
}
if num < conc {
usageAndExit("n cannot be less than c")
}
url := flag.Args()[0]
method := strings.ToUpper(*m)
// set content-type
header := make(http.Header)
header.Set("Content-Type", *contentType)
// set any other additional headers
if *headers != "" {
usageAndExit("flag '-h' is deprecated, please use '-H' instead.")
}
// set any other additional repeatable headers
for _, h := range headerslice {
match, err := parseInputWithRegexp(h, headerRegexp)
if err != nil {
usageAndExit(err.Error())
}
header.Set(match[1], match[2])
}
if *accept != "" {
header.Set("Accept", *accept)
}
// set basic auth if set
var username, password string
if *authHeader != "" {
match, err := parseInputWithRegexp(*authHeader, authRegexp)
if err != nil {
usageAndExit(err.Error())
}
username, password = match[1], match[2]
}
if *output != "csv" && *output != "" {
usageAndExit("Invalid output type; only csv is supported.")
}
var proxyURL *gourl.URL
if *proxyAddr != "" {
var err error
proxyURL, err = gourl.Parse(*proxyAddr)
if err != nil {
usageAndExit(err.Error())
}
}
req, err := http.NewRequest(method, url, nil)
if err != nil {
usageAndExit(err.Error())
}
req.Header = header
if username != "" || password != "" {
req.SetBasicAuth(username, password)
}
// set host header if set
if *hostHeader != "" {
req.Host = *hostHeader
}
(&requester.Work{
Request: req,
RequestBody: *body,
N: num,
C: conc,
QPS: q,
Timeout: *t,
DisableCompression: *disableCompression,
DisableKeepAlives: *disableKeepAlives,
H2: *h2,
ProxyAddr: proxyURL,
Output: *output,
EnableTrace: *enableTrace,
}).Run()
}
func usageAndExit(msg string) {
if msg != "" {
fmt.Fprintf(os.Stderr, msg)
fmt.Fprintf(os.Stderr, "\n\n")
}
flag.Usage()
fmt.Fprintf(os.Stderr, "\n")
os.Exit(1)
}
func parseInputWithRegexp(input, regx string) ([]string, error) {
re := regexp.MustCompile(regx)
matches := re.FindStringSubmatch(input)
if len(matches) < 1 {
return nil, fmt.Errorf("could not parse the provided input; input = %v", input)
}
return matches, nil
}