Julien's dev blog

Gist: Estimate a remote server's HTTP read-timeout in Go

Example Go code to estimate the read-header timeout of a remote HTTP server.

Last updated on: 2025-06-08

package main

import (
	"context"
	"fmt"
	"io"
	"net"
	"os"
	"strconv"
	"time"
)

func main() {
	targetNetAddr := "127.0.0.1:8080"
	targetHTTPHost := "example.org"
	sourceIPAddr := net.IP{127, 0, 0, 1}
	ua := "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"
	slowThrottle := time.Second

	fmt.Printf("Starting header-read timeout estimate...\n")
	fmt.Printf("- Targetting:     %s\n", targetNetAddr)
	fmt.Printf("- With HTTP Host: %s\n", targetHTTPHost)
	fmt.Printf("- From:           %s\n", sourceIPAddr)
	fmt.Printf("\n")

	netDialer := &net.Dialer{LocalAddr: &net.TCPAddr{IP: sourceIPAddr}, Timeout: time.Second}
	conn, err := netDialer.DialContext(context.Background(), "tcp", targetNetAddr)
	if err != nil {
		fmt.Printf("Failed to establish TCP connection: %s\n", err)
		os.Exit(1)
	}

	readTimeoutEstimateStart := time.Now()
	_, err = io.WriteString(conn, "GET / HTTP/1.1"+"\r\n"+
		"Host: "+targetHTTPHost+"\r\n"+
		"User-Agent: "+ua+"\r\n")
	if err != nil {
		fmt.Printf("Failed to write beginning of HTTP request: %s\n", err)
		os.Exit(1)
	}
	for i := 0; true; i++ {
		_, err = io.WriteString(conn, "X-"+strconv.Itoa(i)+": foo"+"\r\n")
		if err != nil {
			break
		}
		elapsed := time.Since(readTimeoutEstimateStart)
		fmt.Printf("Wrote slow header #%d (%s elapsed)\n", i, fmt.Sprintf("%.1fs", elapsed.Seconds()))
		if elapsed > time.Minute {
			fmt.Printf("Timeout is more than 1 minute!\n")
			break
		}
		time.Sleep(slowThrottle)
	}

	estimatedTimeout := time.Since(readTimeoutEstimateStart) - slowThrottle
	fmt.Printf("Estimated header-read timeout: %s\n", fmt.Sprintf("%.1fs", estimatedTimeout.Seconds()))
}
main.go