Parallel URL Checker
Checks multiple URLs concurrently using goroutines, WaitGroup for synchronization, channels for collecting results, and select with time.After for timeouts.
package main
import (
"fmt"
"net/http"
"sync"
"time"
)
type Result struct {
URL string
Status int
Duration time.Duration
Err error
}
func (r Result) String() string {
if r.Err != nil {
return fmt.Sprintf(" %-40s ERROR %v", r.URL, r.Err)
}
return fmt.Sprintf(" %-40s %d %v", r.URL, r.Status, r.Duration.Round(time.Millisecond))
}
func checkURL(url string, timeout time.Duration) Result {
start := time.Now()
client := &http.Client{Timeout: timeout}
resp, err := client.Get(url)
duration := time.Since(start)
if err != nil {
return Result{URL: url, Err: err, Duration: duration}
}
defer resp.Body.Close()
return Result{URL: url, Status: resp.StatusCode, Duration: duration}
}
func checkAll(urls []string, perURLTimeout, totalTimeout time.Duration) []Result {
results := make(chan Result, len(urls))
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
results <- checkURL(u, perURLTimeout)
}(url)
}
// Close results channel once all goroutines finish
go func() {
wg.Wait()
close(results)
}()
var collected []Result
timer := time.After(totalTimeout)
for {
select {
case r, ok := <-results:
if !ok {
return collected
}
collected = append(collected, r)
case <-timer:
fmt.Println(" Global timeout reached, returning partial results.")
return collected
}
}
}
func main() {
urls := []string{
"https://go.dev",
"https://github.com",
"https://httpbin.org/delay/1",
"https://httpbin.org/status/404",
"https://nonexistent.invalid",
"https://httpbin.org/status/500",
"https://example.com",
}
fmt.Printf("Checking %d URLs concurrently...\n\n", len(urls))
fmt.Printf(" %-40s %-6s %s\n", "URL", "STATUS", "DURATION")
fmt.Println(" " + "---------------------------------------- ------ --------")
start := time.Now()
results := checkAll(urls, 5*time.Second, 10*time.Second)
for _, r := range results {
fmt.Println(r)
}
fmt.Printf("\nChecked %d/%d URLs in %v\n", len(results), len(urls), time.Since(start).Round(time.Millisecond))
// Count successes
ok := 0
for _, r := range results {
if r.Err == nil && r.Status >= 200 && r.Status < 400 {
ok++
}
}
fmt.Printf("Healthy: %d/%d\n", ok, len(results))
}