diff --git a/README.md b/README.md index 305dced..4bbf37f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,14 @@ # Loadr -1. Run go run main.go -2. ??? -3. Profit +A lightweight REST load testing tool with rubust support for different verbs, token auth, and stats. Example: -`go run main.go -rate=100 -url="https://google.com"` +`go run main.go -rate=20 -max=100 -url=https://api.example.com/resource -type=POST -json=./data.json -token=YourBearerTokenHere` + +Flags: +- `-rate`: Number of requests per second. Default is 10. +- `-max`: Maximum number of requests to send. If 0, there is no limit. Default is 0. +- `-url`: The URL to make requests to. Default is "https://example.com". +- `-type`: Type of HTTP request. Can be GET, POST, PUT, DELETE, etc. Default is "GET". +- `-json`: Path to the JSON file with request data. If not provided, no data is sent with the requests. +- `-token`: Bearer token for authorization. If not provided, no Authorization header is sent with the requests. diff --git a/main.go b/main.go index 16b95a6..768449d 100644 --- a/main.go +++ b/main.go @@ -1,43 +1,114 @@ package main import ( + "bytes" "flag" "fmt" + "io" "net/http" + "os" + "strings" "sync/atomic" "time" ) +// Global HTTP client used for making requests. var client = &http.Client{} -func makeGetRequest(url string) { - _, err := client.Get(url) +// makeRequest sends an HTTP request with the specified verb, URL, bearer token, and JSON data. +// It prints out the status code, response time, and response size. +func makeRequest(verb, url, token string, jsonData []byte) { + startTime := time.Now() + + // Create a new request with the provided verb, URL, and JSON data if provided. + var req *http.Request + var err error + if jsonData != nil { + req, err = http.NewRequest(verb, url, bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + } else { + req, err = http.NewRequest(verb, url, nil) + } if err != nil { - fmt.Println("Error making GET request:", err) + fmt.Println("Error creating request:", err) return } - fmt.Println("Request Sent") + + // Add the bearer token to the request's Authorization header if provided. + if token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + + // Send the request. + resp, err := client.Do(req) + if err != nil { + fmt.Println("Error making request:", err) + return + } + defer resp.Body.Close() + + // Calculate the duration of the request. + duration := time.Since(startTime) + + // Read the response body to determine its size. + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println("Error reading response body:", err) + return + } + + // Print out the request details. + fmt.Printf("Request Sent. Status Code: %d, Duration: %s, Response Size: %d bytes\n", resp.StatusCode, duration, len(body)) +} + +// readJSONFile reads the contents of the JSON file at the given path and returns the bytes. +func readJSONFile(filePath string) ([]byte, error) { + if filePath == "" { + return nil, nil + } + return os.ReadFile(filePath) } func main() { - // Define command-line flags + // Define command-line flags for configuring the load test. requestsPerSecond := flag.Float64("rate", 10, "Number of requests per second") + maxRequests := flag.Int("max", 0, "Maximum number of requests to send (0 for unlimited)") url := flag.String("url", "https://example.com", "The URL to make requests to") + requestType := flag.String("type", "GET", "Type of HTTP request (GET, POST, PUT, DELETE, etc.)") + jsonFilePath := flag.String("json", "", "Path to the JSON file with request data") + bearerToken := flag.String("token", "", "Bearer token for authorization") - // Parse the flags + // Parse the command-line flags. flag.Parse() + // Read the JSON file if the path is provided. + jsonData, err := readJSONFile(*jsonFilePath) + if err != nil { + fmt.Println("Error reading JSON file:", err) + return + } + + // Calculate the rate limit based on the requests per second. rateLimit := time.Second / time.Duration(*requestsPerSecond) ticker := time.NewTicker(rateLimit) defer ticker.Stop() + // Initialize the request count. var requestCount int32 = 0 + // Start sending requests at the specified rate. for range ticker.C { - go func(u string) { - makeGetRequest(u) - count := atomic.AddInt32(&requestCount, 1) - fmt.Printf("Number of requests: %d\n", count) - }(*url) + // Stop if the maximum number of requests is reached. + if *maxRequests > 0 && int(requestCount) >= *maxRequests { + break + } + // Send the request in a new goroutine. + go func(u, t, verb string, data []byte) { + makeRequest(verb, u, t, data) + atomic.AddInt32(&requestCount, 1) + }(*url, *bearerToken, strings.ToUpper(*requestType), jsonData) } + + // Print out the total number of requests sent after the load test is finished. + fmt.Printf("Finished sending requests. Total requests: %d\n", requestCount) }