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 withx509: 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: HITwithAge: 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 withpreload. - 9, reuse: 21 ms instead of 63 ms. No DNS, no TCP, no TLS: the cheapest performance win in the series.
Exercises:
- Run it against your own service from inside and outside your network, and explain every difference using the guide each step points to.
- Add an
-insecure-labmode that skips step 3 for local testing, and make sure it can't be enabled in production builds. - On Linux, add a step that reads
TCP_INFOfor the live connection (RTT, congestion window) using the TCP/IP guide'stcpinfolab. - 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
- Which step of the walkthrough would you look at first if users report "the site is slow, but only the first visit"? Why?
- Why does the SSRF check run both before connecting and inside the dialer?
- 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.