Foundation · Prepare
Clients, Servers & the Life of a Request
Understand what a backend is: a process that listens for requests, applies rules, and answers over the network.
Primary source: RFC 9110: HTTP Semantics - Architecture & Conformance
Lesson 5 of 7
1d streakThe Architectural Question
Backend frameworks feel like magic when you have never watched a request cross the network boundary they automate.
What this guide proves
- Client and server roles
- Request and response cycle
- Where backend code runs
Client and server are roles played by processes
A client initiates an interaction because it needs a capability; a server waits for interactions and provides that capability. These are roles, not permanent labels for particular machines. A browser is a client of the Task API, while the Task API becomes a client when it opens a PostgreSQL connection. During local development both processes may run on one computer and still communicate through the same network abstractions used in production.
A backend server is normally a long-running process. It binds a socket to an address and port, accepts connections, reads protocol messages, applies rules, and writes responses. An IP address identifies a network interface in a routing context; a port helps the operating system deliver traffic to a listening socket. The pair matters—port 8080 on your laptop is not port 8080 on a remote host.
| Name | Example | Responsibility |
|---|---|---|
| Scheme | http or https | Select protocol and security expectations |
| Host | localhost or api.example.com | Resolve to an address |
| Port | 8080 or an implied default | Reach the listening socket |
| Path | /tasks/42 | Identify the target inside HTTP |
| Query | ?status=OPEN | Refine the requested representation |
Trace connection setup before application code
For http://localhost:8080/tasks, the operating system resolves localhost to a loopback address. The client opens a TCP connection to port 8080, then sends an HTTP request over that connection. With HTTPS, a TLS handshake also authenticates the server and negotiates encryption before protected HTTP data flows. DNS, TCP, TLS, and HTTP fail differently, so naming the stage prevents random code changes.
URL
-> host resolution
-> route to an address
-> TCP connection to a listening socket
-> optional TLS handshake
-> HTTP request bytes
-> server handler
-> HTTP response bytes
-> client interpretationConnection refused is not an HTTP status
Active Recall Checkpoint
curl reports “connection refused” for localhost:8080. Should you change controller code first?
Show answer & explanation
No. First verify that the intended process is running and listening on the expected address and port. No controller can run before the connection reaches the server.
Follow one Task request end to end
Suppose a browser requests the open tasks. It serializes an HTTP message, sends it through the established connection, and waits. The server parses the request, authenticates the caller when required, validates the contract, invokes the relevant use case, and may read PostgreSQL. It then serializes an HTTP response. The browser does not receive a Java object or a database row; it receives protocol data such as status, headers, and JSON.
GET /tasks?status=OPEN HTTP/1.1
Host: localhost:8080
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
[{"id":42,"title":"Read Module 0","status":"OPEN"}]The server is authoritative for rules and durable state. A client may disable an invalid button for usability, but the server must still reject an invalid transition because clients can be modified, outdated, automated, or malicious. Presentation state such as an expanded panel belongs naturally to the client; shared task state belongs behind the server boundary.
Localize failures by the last observed evidence
| Observation | Likely boundary | First check |
|---|---|---|
| Host cannot resolve | DNS or host configuration | Resolve the host independently |
| Connection refused | Process, address, or port | Inspect listeners and server startup |
| TLS certificate error | Identity or trust | Inspect hostname, chain, and clock |
| 404 response | HTTP route or resource | Compare method and path |
| 400 response | Request contract | Inspect payload and validation details |
| 500 response | Unhandled server failure | Correlate response with server logs |
| Timed out | Network or slow work | Measure connection and server deadlines separately |
Timeouts deserve particular care. The server may complete work after the client stops waiting, and the response may be lost after a database commit. That ambiguity is why idempotency, request identifiers, deadlines, and observable logs become important later. A retry is not automatically safe merely because the first response was never seen.
Active Recall Checkpoint
A client times out after sending “complete task.” Can it assume the task is still open?
Show answer & explanation
No. The server may have committed before the response was lost. The client needs a safe retry contract or a follow-up read instead of inferring server state from its local timeout.
Break it deliberately: The silent TCP timeout hang
One of the most devastating production incidents is a silent downstream hang: a client connects to a remote TCP socket, sends a request, and waits indefinitely because no read timeout was configured.
# Start a simulated hanging server using netcat that accepts connections but never responds
nc -l 9999
# In another terminal, a client sends HTTP without a timeout:
curl -i http://localhost:9999/tasks
# Terminal hangs forever. In a multi-threaded application, each hanging request consumes 1 thread.Thread Starvation under Little's Law
Every production network call needs an end-to-end deadline plus timeouts for the phases the chosen client exposes, such as connection establishment and waiting for a response. Values must come from the caller latency budget and measured downstream behavior; no universal one-to-five-second range fits every workload.
Active Recall Checkpoint
Why is a connect timeout alone insufficient to prevent server thread exhaustion?
Show answer & explanation
Because the downstream host may accept the connection and then stall while processing or returning the response. A response or read timeout, bounded by the caller deadline, prevents the calling thread from waiting indefinitely.
Observe the boundary without Spring
Use the JDK’s static web server or another supplied fixture to create a real listener. From a second terminal, inspect the port and send a request. This isolates networking and HTTP from the framework, database, and application rules. When Spring later listens on the same port, you will recognize the unchanged operating-system boundary.
mkdir -p /tmp/task-http
printf '[{"id":42,"status":"OPEN"}]\n' > /tmp/task-http/tasks.json
jwebserver -p 8080 -d /tmp/task-http
# Second terminal
curl -i http://localhost:8080/tasks.json
# Linux listener inspection
ss -ltnp 'sport = :8080'- 01Predict the response before sending the request.
- 02Stop the server and repeat the request; record how the failure changes.
- 03Start it on port 8081 and prove that the old URL still fails.
- 04Separate facts from guesses: listener, connection, HTTP status, headers, and body.
Windows and macOS
netstat or lsof -i :8080 if ss is unavailable. The goal is to observe the listening socket, not memorize one operating-system command.Runtime Architecture & Flow
Request and response
One API call crosses three ownership boundaries
The request moves toward stored data; the response carries an outcome back to the client.
Phase 1 of 5
Client creates the request
A browser, mobile app, or another service chooses the HTTP method, URL, headers, and optional body.
Boundary rule
The client owns presentation. The server owns trusted business rules. The database stores durable state but does not understand the user interface.
Guided Hands-On Lab
Code Walkthrough
Draw the round trip when a browser loads /api/tasks on your machine and label every hop from URL to JSON response.
#!/usr/bin/env bash
set -euo pipefail
# 1. Setup isolated temporary directory fixture with automatic trap cleanup
FIXTURE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/task-http.XXXXXX")"
SERVER_PID=""
cleanup() {
if [[ -n "${SERVER_PID:-}" ]] && kill -0 "$SERVER_PID" 2>/dev/null; then
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
fi
rm -rf "$FIXTURE_DIR"
}
trap cleanup EXIT INT TERM
printf '[{"id":42,"title":"Read Module 0","status":"OPEN"}]\n' > "$FIXTURE_DIR/tasks.json"
PORT=8080
echo "Starting JDK jwebserver on port $PORT..."
jwebserver -p "$PORT" -d "$FIXTURE_DIR" >/dev/null 2>&1 &
SERVER_PID=$!
# Wait for server socket to become ready (up to 5 seconds)
for i in {1..10}; do
if (echo > "/dev/tcp/127.0.0.1/$PORT") 2>/dev/null; then
break
fi
sleep 0.5
done
# 2. Inspect operating system listening socket
echo "--- 1. Operating System Socket Inspection ---"
if command -v ss >/dev/null 2>&1; then
ss -ltnp "sport = :$PORT" || true
elif command -v lsof >/dev/null 2>&1; then
lsof -i ":$PORT" || true
fi
# 3. Perform HTTP Round-Trip
echo "--- 2. HTTP Request & Response Trace ---"
curl -i -s "http://127.0.0.1:$PORT/tasks.json"
# 4. Deliberate Failure: Connection Refused vs Server Error
echo "--- 3. Stopping Server & Testing Failure Boundary ---"
kill "$SERVER_PID"
wait "$SERVER_PID" 2>/dev/null || true
SERVER_PID=""
echo "Attempting request to stopped server (Observing TCP rejection):"
# curl exits with code 7 on connection refused; capture output safely under pipefail
HTTP_ERR=$(curl -i -s "http://127.0.0.1:$PORT/tasks.json" 2>&1 || true)
if echo "$HTTP_ERR" | grep -E -q "Failed to connect|Connection refused|curl: \(7\)"; then
echo "[VERIFIED] TCP Connection Refused: operating system rejected handshake before HTTP layer existed."
else
echo "Expected connection refused, got: $HTTP_ERR"
fiGuided LabBuild & Verify Step by Step
A terminal with JDK 21 (jwebserver), curl, and netstat/ss/lsof tools installed.
- 01
Start isolated socket listener
Launch jwebserver on port 8080 serving a static JSON task fixture in the background.
Check: ss -ltnp confirms port 8080 is bound by a process in LISTEN state.
- 02
Trace HTTP request round-trip
Send curl -i http://localhost:8080/tasks.json and inspect status line, Content-Type header, and JSON body.
Check: HTTP/1.1 200 OK is returned with application/json body.
- 03
Observe TCP connection refused
Stop the server and re-send the request. Observe the exact operating system error.
Check: curl reports (7) Failed to connect: Connection refused. Confirms TCP failure before HTTP exists.
- 04
Understand Little’s Law and thread starvation
Analyze what happens when requests hang on downstream sockets without read timeouts under 200 Tomcat worker threads.
Check: You can explain why connect-timeout and read-timeout are mandatory for production resilience.
Command
bash observe-task-server.sh
Expected Output
--- 1. Operating System Socket Inspection ---
LISTEN 0 50 *:8080
--- 2. HTTP Request & Response Trace ---
HTTP/1.1 200 OK
Content-Type: application/json
[{"id":42,"title":"Read Module 0","status":"OPEN"}]
--- 3. Stopping Server & Testing Failure Boundary ---
[VERIFIED] TCP Connection Refused[INFO] Scanning for projects... [INFO] -------------------< com.example:order-service >------------------- [INFO] Building order-service 0.0.1-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-surefire-plugin:3.2.5:test (default-test) @ order-service --- [INFO] Running com.example.OrderServiceTest [ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.284 s <<< FAILURE! -- in com.example.OrderServiceTest [ERROR] com.example.OrderServiceTest.testOrderCreation -- Time elapsed: 0.041 s <<< ERROR! java.lang.NullPointerException: Cannot invoke "com.example.OrderRepository.save(Object)" because "this.repository" is null at com.example.OrderService.createOrder(OrderService.java:18) at com.example.OrderServiceTest.testOrderCreation(OrderServiceTest.java:24) [INFO] Results: [INFO] [ERROR] Errors: [ERROR] OrderServiceTest.testOrderCreation:24 » NullPointer Cannot invoke "com.example... [INFO] [ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.2.5:test on project order-service: There are test failures. [ERROR] -> [Help 1] [ERROR] BUILD FAILURE
Synthesis & Production Verification
Production Acceptance Criteria
- Describe a server as a long-running process listening on a port
- Trace one request from URL to response and back
- Decide which side owns state, validation, and presentation
Key Takeaway
A backend is a long-running process that listens on a port, applies rules to each incoming request independently, and answers over the network. Naming each hop — client, DNS, TCP, server process — turns mystery failures into isolated ones.
Architectural & Technical Review
Core Interview Questions
Q01What happens between entering a URL and receiving an HTTP response?Show answer
The client parses the URL, resolves the host through DNS, opens a TCP connection to the selected IP address and port, negotiates TLS for HTTPS, sends an HTTP request, and reads the server response. Each stage has different failure evidence, so this sequence is also a debugging map.
Q02What is the difference between a host, a port, and a URL path?Show answer
The host identifies the machine or network endpoint, the port selects a listening service on that host, and the path identifies a resource handled by that service. Reaching the correct host on the wrong port never reaches the intended process.
Q03How do connection refused, a timeout, and HTTP 500 narrow a failure?Show answer
Connection refused usually means the host was reached but nothing accepted that port. A timeout points to an unreachable path, filtering, or a service that did not answer. HTTP 500 proves the request reached an HTTP server and failed during application processing.
Production Pitfalls
Critical Anti-Patterns
- Pitfall: Restarting the server for problems that were never server-side — Without naming each hop, every failure looks like “the server”. Check whether the request even arrived before restarting anything.
- Pitfall: Trusting client-side validation — Bypassing the browser with curl exposes any rule only enforced in JavaScript. The server must own every business rule.
- Pitfall: Confusing localhost, ports, and domains when debugging — A service reachable on localhost:8080 is not reachable from another machine by that name. Distinguish machine address, service port, and DNS name before concluding anything is “down”.
Primary Documentation & Specifications
Lesson Primary References
Supplementary Module Resources