Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
Where Does a Slow Request Come From: Diagnosing the Network with curl -w, DevTools Timing and Wireshark
A slow request's time splits into DNS, TCP, TLS, server (TTFB) and download phases, and every phase has a recognizable signature. Five cases with curl -w under a 50 ms RTT via netem (no keep-alive, an IPv6 attempt, TLS 1.2, a slow server, a large response), .NET's System.Net metrics, dotnet-counters and a TLS key log file for Wireshark.
HTTP/3 and QUIC: Secure Transport over UDP, Alt-Svc and HTTP/3 in .NET
HTTP/3 uses QUIC, which runs over UDP instead of TCP: TLS 1.3 built in, ordering per stream, one RTT less on a new connection. Discovery via Alt-Svc and the DNS HTTPS record, QuicConnection.IsSupported=false on macOS, Kestrel HTTP/3 working with libmsquic in a Linux container, why HTTP/3 isn't faster on loopback, and the full RTT it saves at 50 ms RTT, with real measurements.
HTTP/2: Binary Framing, Multiplexing, HPACK and HTTP/2 in Kestrel
HTTP/2 doesn't change semantics; it splits messages into binary frames belonging to streams, interleaves them on one TCP connection and compresses headers with HPACK. Pseudo-headers and h2c with curl, one connection instead of 20 for 20 concurrent requests, Kestrel's 100-stream limit, HttpClient's default 1.1, and 103 Early Hints instead of Server Push.
HTTP Caching and Conditional Requests: Cache-Control, ETag and 304
An HTTP cache uses Cache-Control to decide how long a copy stays fresh, and ETag or Last-Modified to cheaply revalidate it with a 304 Not Modified when it expires. no-cache vs no-store, heuristic freshness for static files without Cache-Control, the old index.html after a deploy, and an ETag handler written because HttpClient has no cache of its own.
Reading HTTP/1.1 Off the Wire: Message Format, Method Semantics and Keep-Alive
HTTP/1.1 is a text protocol: a start line, headers, an empty line and a body framed by Content-Length or chunked. Requests written by hand with nc and openssl s_client, a 400 for a request without Host, what the 3d in chunked means, safe/idempotent methods and a handler that only retries idempotent requests, keep-alive, and Kestrel against request smuggling.
The TLS 1.3 Handshake: Certificate Chain, SNI, ALPN and Post-Quantum Key Exchange
TLS 1.3 sets up the encrypted channel in one RTT by putting the key_share in the ClientHello; ECDHE gives forward secrecy and X25519MLKEM768 adds post-quantum protection. The handshake with openssl s_client, a ClientHello growing from 285 to 1,525 bytes, certificate chain errors, SNI/ECH/ALPN, 0-RTT and .NET staying on TLS 1.2 on macOS.
How DNS Works: Resolvers, Record Types, TTL and Querying with dig
DNS resolves a name through a chain of stub resolver, recursive resolver, root, TLD and authoritative servers and caches every answer for its TTL. dig +trace, A/AAAA/CNAME/MX/TXT/CAA/HTTPS records, negative caching, DoH, and a blue-green experiment showing why HttpClient's open connections don't see DNS changes.
How TCP Works: 3-Way Handshake, Congestion Control and HttpClient Socket Exhaustion
TCP opens a connection with a 3-way handshake for a reliable byte stream, starts slowly with slow start and waits in TIME_WAIT on close; that's why new HttpClient per request exhausts ports. 500 TIME_WAITs for 500 requests, 6x slower with TLS, IHttpClientFactory, PooledConnectionLifetime and handler defaults, with real output.
IP Addresses, Subnets, Ports and NAT: localhost, 0.0.0.0 and Connectivity in Containers
An IP address finds an interface, a port finds a socket on that machine; a server bound to 127.0.0.1 can only be reached from inside that machine. RFC 1918 private addresses, subnets with CIDR, NAT, ephemeral ports, address already in use, and why a Kestrel bound to localhost in a container returns an Empty reply, with real output.
What Happens When You Press Enter on a URL: End to End from DNS to Render
When you press Enter, the browser parses the URL, finds the IP with DNS, connects with TCP and TLS 1.3, sends the HTTP request and renders the response; a new connection spends at least three to four RTTs before the first byte. We read every phase line by line against our own Kestrel server with curl -v and curl -w.
What Big-O Doesn't Tell You: Constants, Cache and Measuring with BenchmarkDotNet
Big-O tells you about growth, but real time is decided by constants, the cache, allocations and the input distribution. An O(n²) insertion sort twice as fast as LINQ on 10 books, LinkedList 3.8x slower, Dictionary falling to O(n²) with a bad GetHashCode, and estimating complexity by measuring with the doubling experiment.
Amortized Analysis: Why List<T>.Add Counts as O(1)
List<T>.Add sometimes copies the whole list, but because capacity doubles, the total cost of n Adds never exceeds 3n; that's why it counts as amortized O(1). The aggregate, accounting and potential methods, 50 billion copies with +10 growth, Dictionary's prime sizes and a 38 ms resize spike, with real measurements.