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

45 min

Lesson 5 of 7

1d streak

The 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.

NameExampleResponsibility
Schemehttp or httpsSelect protocol and security expectations
Hostlocalhost or api.example.comResolve to an address
Port8080 or an implied defaultReach the listening socket
Path/tasks/42Identify the target inside HTTP
Query?status=OPENRefine 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.

request-path.txt
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 interpretation

Connection refused is not an HTTP status

If no process is listening, the TCP connection fails before an HTTP response exists. A 404 or 500 proves that an HTTP server answered; it does not mean the same thing as an unreachable port.

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.

task-round-trip.http
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

ObservationLikely boundaryFirst check
Host cannot resolveDNS or host configurationResolve the host independently
Connection refusedProcess, address, or portInspect listeners and server startup
TLS certificate errorIdentity or trustInspect hostname, chain, and clock
404 responseHTTP route or resourceCompare method and path
400 responseRequest contractInspect payload and validation details
500 responseUnhandled server failureCorrelate response with server logs
Timed outNetwork or slow workMeasure 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.

simulate-socket-hang.sh
# 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

If your service has a worker pool of 200 Tomcat threads, and downstream calls hang for 60 seconds, it takes just 4 requests per second to completely exhaust every thread in 50 seconds. The entire server becomes completely unresponsive to health checks.

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.

observe-server.sh
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'
  1. 01Predict the response before sending the request.
  2. 02Stop the server and repeat the request; record how the failure changes.
  3. 03Start it on port 8081 and prove that the old URL still fails.
  4. 04Separate facts from guesses: listener, connection, HTTP status, headers, and body.

Windows and macOS

Use 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.

Request: GET /api/tasks
Response: 200 OK with JSON

Phase 1 of 5

Client creates the request

A browser, mobile app, or another service chooses the HTTP method, URL, headers, and optional body.

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.

observe-task-server.sh
#!/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"
fi
Uses mktemp -d with a trap handler to guarantee immediate cleanup of temporary files and background server processes on EXIT, INT, or TERM.
jwebserver creates a real listening process without introducing Spring, application rules, or a database.
curl separates connection evidence from the HTTP status, headers, and response content.
Stopping the server changes the observation from an HTTP response to a TCP connection failure, handled safely without breaking under set -euo pipefail.
Guided LabBuild & Verify Step by Step

A terminal with JDK 21 (jwebserver), curl, and netstat/ss/lsof tools installed.

  1. 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.

  2. 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.

  3. 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.

  4. 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
Simulated terminal replay — matched to lesson verificationJDK 21 / Maven 3.9
spring-boot-lab@localhost:~
$bash observe-task-server.sh#failingCondition
[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
Exit Code: 1
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

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