Frontend Labs
⋯

Part 10 — The whole series in one request

This is the last stop of the series. You've studied the machine (the Linux guide), the network (the OSI and TCP/IP guides), how systems are attacked and defended (the security guides), and the web request lifecycle (this guide). This part puts all of it into one Go program and one HTTPS request, and labels every step with the guide that explains it.

Chapter 25 — One HTTPS request, every layer, every guide

In one sentence

A single GET https://example.com/ touches the process and its file descriptors, DNS, a security decision, a TCP handshake, a TLS handshake with certificate verification, HTTP, caches, browser security policy, and connection reuse, and you can observe each of those from one Go program.

Why this matters

Incidents, design reviews, and interviews all come down to the same skill: following one request through every layer and knowing which layer you're looking at. The guides in this series each teach some of those layers in depth. This chapter is the map that joins them, as running code you can point at any URL.

How it actually works

The program reuses techniques from labs across the series:

Step Technique Built in
1, 10 Process view counting file descriptors and goroutines before and after Linux Ch 73, 78
2 DNS net.Resolver.LookupNetIP TCP/IP Ch 18 (dnsquery)
3 Security pre-flight Dialer.Control blocking non-public IPs HTTPS Ch 22 (ssrfguard), Security in Depth Ch 33
4 TCP httptrace connect timing TCP/IP Ch 21 (dialcheck)
5 TLS VerifyConnection + the verified chain TCP/IP Ch 29 (tlsinspect), HTTPS Ch 16 (certcheck)
6 HTTP httptrace TTFB, Alt-Svc HTTPS Ch 14, 17 (protoprobe, httptrace)
7 Cache response cache headers HTTPS Ch 15 (edgecache)
8 Security headers HSTS, CSP, and the rest HTTPS Ch 19 (httpsserver), Security in Depth Part 6
9 Reuse the same http.Client twice OSI Part 7, HTTPS Ch 17

Build it in Go (30 min) — the walkthrough

// walkthrough: the whole series in one program. It makes one real HTTPS
// request and reports every step -- operating system, network, security,
// HTTP -- with the guide and chapter that explains it.
//
//	go run ./walkthrough https://example.com/
package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httptrace"
	"net/netip"
	"net/url"
	"os"
	"runtime"
	"strings"
	"syscall"
	"time"
)

func step(n int, layer, title, ref string) {
	fmt.Printf("\n%2d. [%s] %s\n    see: %s\n", n, layer, title, ref)
}

func detail(format string, a ...any) { fmt.Printf("    "+format+"\n", a...) }

// openFDs counts this process's open file descriptors by asking the kernel
// about each number in turn (works on Linux and macOS; /proc/self/fd is Linux-only).
func openFDs() int {
	n := 0
	for fd := 0; fd < 4096; fd++ {
		var st syscall.Stat_t
		if syscall.Fstat(fd, &st) == nil {
			n++
		}
	}
	return n
}

// publicOnly is the SSRF guard: runs after DNS, before connect().
func publicOnly(_, address string, _ syscall.RawConn) error {
	ap, err := netip.ParseAddrPort(address)
	if err != nil {
		return err
	}
	ip := ap.Addr().Unmap()
	if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() || ip.IsMulticast() {
		return fmt.Errorf("blocked non-public address %s", ip)
	}
	return nil
}

func main() {
	if len(os.Args) != 2 {
		fmt.Println("usage: walkthrough https://host/path")
		os.Exit(2)
	}
	u, err := url.Parse(os.Args[1])
	if err != nil || u.Scheme != "https" {
		fmt.Println("need an https:// URL")
		os.Exit(2)
	}
	host := u.Hostname()

	step(1, "OS", "the process that will make the request",
		"Linux guide Ch 73 (threads), Ch 75 (GOMAXPROCS), Ch 78 (file descriptors)")
	detail("pid %d, GOMAXPROCS %d, goroutines %d, open file descriptors %d",
		os.Getpid(), runtime.GOMAXPROCS(0), runtime.NumGoroutine(), openFDs())

	step(2, "L7 DNS", "resolve the name", "TCP/IP guide Ch 18 (dnsquery), HTTPS guide Ch 2")
	t0 := time.Now()
	ips, err := net.DefaultResolver.LookupNetIP(context.Background(), "ip", host)
	if err != nil {
		detail("FAILED: %v", err)
		os.Exit(1)
	}
	detail("%s -> %v in %v", host, ips, time.Since(t0).Round(time.Millisecond))

	step(3, "Security", "is it safe to connect? (SSRF pre-flight on the resolved addresses)",
		"HTTPS guide Ch 22 (ssrfguard), Security in Depth Ch 33 (SSRF)")
	for _, ip := range ips {
		if err := publicOnly("tcp", netip.AddrPortFrom(ip, 443).String(), nil); err != nil {
			detail("REFUSED: %v", err)
			os.Exit(1)
		}
	}
	detail("all %d addresses are public; the same check runs again inside the dialer at connect time", len(ips))

	// One client, reused for both requests: a connection pool (HTTPS guide Ch 17).
	var tlsState tls.ConnectionState
	client := &http.Client{
		Timeout: 15 * time.Second,
		Transport: &http.Transport{
			DialContext:       (&net.Dialer{Timeout: 5 * time.Second, Control: publicOnly}).DialContext,
			ForceAttemptHTTP2: true,
			TLSClientConfig: &tls.Config{
				MinVersion: tls.VersionTLS12,
				VerifyConnection: func(cs tls.ConnectionState) error { // runs after normal verification
					tlsState = cs
					return nil
				},
			},
		},
	}

	var connStart, connDone, tlsStart, tlsDone, wrote, first time.Time
	var remote, local string
	var reused bool
	trace := &httptrace.ClientTrace{
		ConnectStart: func(_, _ string) { connStart = time.Now() },
		ConnectDone: func(_, addr string, err error) {
			connDone, remote = time.Now(), addr
		},
		TLSHandshakeStart:    func() { tlsStart = time.Now() },
		TLSHandshakeDone:     func(tls.ConnectionState, error) { tlsDone = time.Now() },
		GotConn:              func(i httptrace.GotConnInfo) { reused, local = i.Reused, i.Conn.LocalAddr().String() },
		WroteRequest:         func(httptrace.WroteRequestInfo) { wrote = time.Now() },
		GotFirstResponseByte: func() { first = time.Now() },
	}

	start := time.Now()
	req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), "GET", u.String(), nil)
	resp, err := client.Do(req)
	if err != nil {
		var ce *tls.CertificateVerificationError
		if errors.As(err, &ce) {
			detail("TLS verification FAILED: %v", ce.Err)
		} else {
			detail("request FAILED: %v", err)
		}
		os.Exit(1)
	}
	body, _ := io.Copy(io.Discard, resp.Body)
	resp.Body.Close()
	end := time.Now()

	step(4, "L3/L4 TCP", "three-way handshake", "OSI guide Part 6, TCP/IP guide Ch 21 (dialcheck), Ch 12 (NAT)")
	detail("%s -> %s, connect took %v (~1 round trip)", local, remote, connDone.Sub(connStart).Round(time.Millisecond))

	step(5, "L5/L6 TLS", "encrypted, authenticated channel", "HTTPS guide Ch 4 & 16, TCP/IP guide Ch 29 & 56, Security from Zero (TLS, PKI)")
	detail("%s, %s, ALPN %q, handshake %v", tls.VersionName(tlsState.Version),
		tls.CipherSuiteName(tlsState.CipherSuite), tlsState.NegotiatedProtocol, tlsDone.Sub(tlsStart).Round(time.Millisecond))
	if len(tlsState.VerifiedChains) > 0 {
		chain := tlsState.VerifiedChains[0]
		leaf := chain[0]
		detail("certificate for %v, issued by %q, %d days left", leaf.DNSNames, leaf.Issuer.CommonName,
			int(time.Until(leaf.NotAfter).Hours()/24))
		detail("chain verified to root %q in this machine's trust store", chain[len(chain)-1].Subject.CommonName)
		checkKey(leaf)
	}

	step(6, "L7 HTTP", "request and response", "HTTPS guide Ch 5 (HTTP), Ch 14 (versions), Ch 17 (httptrace)")
	detail("%s %s, %d bytes; server think time + 1 RTT (TTFB) %v; total %v",
		resp.Proto, resp.Status, body, first.Sub(wrote).Round(time.Millisecond), end.Sub(start).Round(time.Millisecond))
	if alt := resp.Header.Get("Alt-Svc"); alt != "" {
		detail("Alt-Svc: %s  (HTTP/3 offered for next time)", alt)
	}

	step(7, "L7 cache", "who may cache this response, and did a cache answer?", "HTTPS guide Ch 10 & 15 (edgecache), Security in Depth Ch 31")
	for _, h := range []string{"Cache-Control", "Age", "Vary", "ETag", "CF-Cache-Status", "X-Cache"} {
		if v := resp.Header.Get(h); v != "" {
			detail("%-16s %s", h+":", v)
		}
	}

	step(8, "Security", "browser-side defences the server asked for", "HTTPS guide Ch 12 & 19, Security in Depth Part 6")
	for _, h := range []string{"Strict-Transport-Security", "Content-Security-Policy", "X-Content-Type-Options",
		"Referrer-Policy", "X-Frame-Options", "Permissions-Policy"} {
		v := resp.Header.Get(h)
		mark := "present"
		if v == "" {
			mark, v = "MISSING", ""
		}
		if len(v) > 60 {
			v = v[:60] + "..."
		}
		detail("%-26s %-8s %s", h, mark, v)
	}

	step(9, "L4/L5 reuse", "a second request on the same client", "OSI guide Part 7, HTTPS guide Ch 17")
	start = time.Now()
	req2, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), "GET", u.String(), nil)
	if resp2, err := client.Do(req2); err == nil {
		io.Copy(io.Discard, resp2.Body)
		resp2.Body.Close()
		detail("reused connection: %v, total %v (no DNS, TCP, or TLS this time)", reused, time.Since(start).Round(time.Millisecond))
	}

	step(10, "OS", "what the request cost the process", "Linux guide Ch 73 & 78, TCP/IP guide Ch 23 (connection states)")
	detail("goroutines %d, open file descriptors %d (the pooled connection stays open for reuse)",
		runtime.NumGoroutine(), openFDs())
	fmt.Println()
}

// checkKey applies a tiny policy check to the leaf certificate (Security in
// Depth Ch 59/61: policy as code).
func checkKey(c *x509.Certificate) {
	lifetime := int(c.NotAfter.Sub(c.NotBefore).Hours() / 24)
	verdict := "ok"
	if lifetime > 398 {
		verdict = "longer than browsers accept"
	}
	detail("key %s, lifetime %d days (%s)", strings.ToLower(c.PublicKeyAlgorithm.String()), lifetime, verdict)
}

A real run:


 1. [OS] the process that will make the request
    see: Linux guide Ch 73 (threads), Ch 75 (GOMAXPROCS), Ch 78 (file descriptors)
    pid 87003, GOMAXPROCS 14, goroutines 1, open file descriptors 3

 2. [L7 DNS] resolve the name
    see: TCP/IP guide Ch 18 (dnsquery), HTTPS guide Ch 2
    example.com -> [104.20.23.154 172.66.147.243 2606:4700:83b5:72db:f2fe:4b1:ef6b:ff98] in 38ms

 3. [Security] is it safe to connect? (SSRF pre-flight on the resolved addresses)
    see: HTTPS guide Ch 22 (ssrfguard), Security in Depth Ch 33 (SSRF)
    all 3 addresses are public; the same check runs again inside the dialer at connect time

 4. [L3/L4 TCP] three-way handshake
    see: OSI guide Part 6, TCP/IP guide Ch 21 (dialcheck), Ch 12 (NAT)
    192.168.1.2:58567 -> 104.20.23.154:443, connect took 17ms (~1 round trip)

 5. [L5/L6 TLS] encrypted, authenticated channel
    see: HTTPS guide Ch 4 & 16, TCP/IP guide Ch 29 & 56, Security from Zero (TLS, PKI)
    TLS 1.3, TLS_AES_128_GCM_SHA256, ALPN "h2", handshake 25ms
    certificate for [example.com *.example.com], issued by "Cloudflare TLS Issuing ECC CA 3", 83 days left
    chain verified to root "SSL.com TLS ECC Root CA 2022" in this machine's trust store
    key ecdsa, lifetime 90 days (ok)

 6. [L7 HTTP] request and response
    see: HTTPS guide Ch 5 (HTTP), Ch 14 (versions), Ch 17 (httptrace)
    HTTP/2.0 200 OK, 577 bytes; server think time + 1 RTT (TTFB) 19ms; total 63ms
    Alt-Svc: h3=":443"; ma=86400  (HTTP/3 offered for next time)

 7. [L7 cache] who may cache this response, and did a cache answer?
    see: HTTPS guide Ch 10 & 15 (edgecache), Security in Depth Ch 31
    Age:             5408
    CF-Cache-Status: HIT

 8. [Security] browser-side defences the server asked for
    see: HTTPS guide Ch 12 & 19, Security in Depth Part 6
    Strict-Transport-Security  MISSING
    Content-Security-Policy    MISSING
    X-Content-Type-Options     MISSING
    Referrer-Policy            MISSING
    X-Frame-Options            MISSING
    Permissions-Policy         MISSING

 9. [L4/L5 reuse] a second request on the same client
    see: OSI guide Part 7, HTTPS guide Ch 17
    reused connection: true, total 21ms (no DNS, TCP, or TLS this time)

10. [OS] what the request cost the process
    see: Linux guide Ch 73 & 78, TCP/IP guide Ch 23 (connection states)
    goroutines 2, open file descriptors 7 (the pooled connection stays open for reuse)

Reading the output, step by step

  • 1 → 10, the process: 3 file descriptors before (stdin, stdout, stderr) and 7 after: the pooled TLS connection, plus the runtime's netpoller (epoll on Linux, kqueue on macOS) and its wakeup descriptors. The connection stays open on purpose, waiting for reuse (Linux Ch 78). One extra goroutine is reading from it (Linux Ch 73).
  • 2, DNS: three addresses, two IPv4 and one IPv6, in 38 ms. A CDN's anycast front door (HTTPS Ch 7).
  • 3, security before connecting: every address is public, so a server making this request on a user's behalf is not being steered into its own network. Point the program at https://localtest.me/ and it stops here: REFUSED: blocked non-public address ::1.
  • 4, TCP: 17 ms to connect, which is one round trip. Your private source address (192.168.1.2) is rewritten by NAT on the way out (TCP/IP Ch 12).
  • 5, TLS: TLS 1.3 in one round trip, HTTP/2 chosen by ALPN, a 90-day ECDSA certificate verified to a root in this machine's trust store. Point it at https://expired.badssl.com/ and it stops here with x509: certificate has expired.
  • 6, HTTP: TTFB 19 ms ≈ 1 RTT, so the server spent almost no time thinking. That fits step 7.
  • 7, cache: CF-Cache-Status: HIT with Age: 5408. The CDN answered from a copy about 90 minutes old, and the origin never saw this request.
  • 8, security headers: example.com sends none of them. That's fine for a static placeholder page, and a finding for any real application. Compare with https://github.com/, which sends HSTS with preload.
  • 9, reuse: 21 ms instead of 63 ms. No DNS, no TCP, no TLS: the cheapest performance win in the series.

Exercises:

  1. Run it against your own service from inside and outside your network, and explain every difference using the guide each step points to.
  2. Add an -insecure-lab mode that skips step 3 for local testing, and make sure it can't be enabled in production builds.
  3. On Linux, add a step that reads TCP_INFO for the live connection (RTT, congestion window) using the TCP/IP guide's tcpinfo lab.
  4. Add a step that runs Chapter 20's checks on the response path: does a proxy in front add X-Forwarded-For, and would your app trust it?

Turning the walkthrough into a review checklist

The same ten steps make a design-review checklist for any HTTPS service:

 1  Process      runs non-root, limits set, GOMAXPROCS/GOMEMLIMIT right?      Linux Ch 75-76, 82
 2  DNS          TTLs planned, two providers for critical names, DNSSEC?      TCP/IP Ch 55
 3  Outbound     user-supplied URLs fetched through an SSRF-safe client?      HTTPS Ch 22
 4  Transport    timeouts on every hop, idle timeouts nested, pools sized?    HTTPS Ch 19, TCP/IP Ch 57
 5  TLS          automated certificates, inventory, expiry alerts, mTLS east-west?  HTTPS Ch 16, Security Ch 23
 6  HTTP         idempotency on writes, retries at ONE layer, SLOs defined?   HTTPS Ch 21, 23
 7  Cache        every response has an explicit Cache-Control, Vary correct?  HTTPS Ch 15
 8  Headers      HSTS, CSP, nosniff, frame-ancestors on every response?       HTTPS Ch 19
 9  Reuse        keep-alive tuned, graceful shutdown drains connections?      TCP/IP Ch 57, Linux Ch 82
10  Evidence     logs, metrics, traces, tamper-evident audit trail?           Security Ch 51-52

Check yourself

  1. Which step of the walkthrough would you look at first if users report "the site is slow, but only the first visit"? Why?
  2. Why does the SSRF check run both before connecting and inside the dialer?
  3. Why are there more file descriptors at the end than at the start, and is that a leak?

Key Takeaways

  • One request crosses every layer this series teaches, and each one is observable from code.
  • Every step maps to a guide: when a step looks wrong, you know where to read.
  • The ten steps double as a production-readiness checklist.

Generated from real-life-guide-v1.md · 8 Oct 2026 16:36