Understanding HTTP Responses

Understanding HTTP Responses

After your browser sends a request, the server sends back an HTTP Response. The response tells the client whether the operation succeeded, provides metadata about the data, and carries the actual content in its body.


1. The Structure of an HTTP Response

┌────────────────────────────────────────────────────────────┐
│  STATUS LINE                                               │
│  HTTP/1.1 200 OK                                           │
├────────────────────────────────────────────────────────────┤
│  HEADERS                                                   │
│  Date: Mon, 31 Mar 2025 07:38:00 GMT                       │
│  Content-Type: application/json; charset=utf-8             │
│  Content-Length: 284                                       │
│  Cache-Control: public, max-age=300                        │
│  ETag: "a1b2c3d4"                                          │
│  X-Request-Id: 7e4c9f21-ab12-4d3e-bcd1-9e5f2c3a8d71       │
├────────────────────────────────────────────────────────────┤
│  BLANK LINE                                                │
│                                                            │
├────────────────────────────────────────────────────────────┤
│  BODY                                                      │
│  {"id":42,"username":"Nandhoo","level":"intermediate"}    │
└────────────────────────────────────────────────────────────┘

2. The Status Line

The status line has three components:

HTTP/1.1  200  OK
   │       │    │
   │       │    └── Reason phrase (human-readable, informational only)
   │       └─────── Status code (the machine-readable part that matters)
   └─────────────── HTTP version

The status code is a 3-digit number grouped by its first digit:

RangeCategoryMeaning
1xxInformationalRequest received, continuing
2xxSuccessRequest was understood and processed
3xxRedirectionClient needs to take further action
4xxClient ErrorThe request had a problem
5xxServer ErrorThe server failed

3. Common Status Codes in Depth

2xx — Success

CodeNameWhen to use
200OKStandard success for GET, PUT, PATCH
201CreatedSomething was created (after POST) — should include a Location header pointing to the new resource
202AcceptedRequest received and queued (async processing — the work isn't done yet)
204No ContentSuccess but no body (after DELETE, or PATCH with no response body)
206Partial ContentStreaming — part of a file (used with Range requests for video scrubbing)

3xx — Redirection

CodeNameWhen to use
301Moved PermanentlyResource moved forever; browser and search engines update their records
302FoundTemporary redirect; browser follows it but keeps the original URL cached
304Not ModifiedResponse is in the browser's cache; don't re-download it
307Temporary RedirectSame as 302 but the method must NOT change (a POST stays POST)
308Permanent RedirectSame as 301 but the method must NOT change

4xx — Client Errors

CodeNameWhen to use
400Bad RequestMalformed request, missing required body field
401UnauthorizedNo auth or invalid credentials (misleadingly named — really means "unauthenticated")
403ForbiddenAuthenticated but not authorised for this resource
404Not FoundResource doesn't exist at this URL
405Method Not AllowedThe URL exists but doesn't support this HTTP method
409ConflictDuplicate resource (e.g. email already registered)
422Unprocessable EntityRequest is valid JSON but fails validation (wrong field values)
429Too Many RequestsRate limit exceeded

5xx — Server Errors

CodeNameWhen to use
500Internal Server ErrorUnhandled exception on the server
502Bad GatewayThe upstream server the proxy was talking to failed
503Service UnavailableServer is overloaded or down for maintenance
504Gateway TimeoutUpstream server didn't respond in time

4. Response Headers in Depth

Content Headers

HeaderExamplePurpose
Content-Typeapplication/json; charset=utf-8Format of the body
Content-Length284Exact body size in bytes
Content-EncodinggzipCompression applied to the body
Content-Languageen-GBLanguage of the content

Caching Headers

HeaderExamplePurpose
Cache-Controlpublic, max-age=86400How and for how long the response can be cached
ETag"a1b2c3"A fingerprint of the resource — for conditional requests
Last-ModifiedMon, 31 Mar 2025 00:00:00 GMTWhen the resource was last changed
ExpiresTue, 01 Apr 2025 00:00:00 GMTAbsolute expiry (older — prefer Cache-Control)
VaryAccept-EncodingWhich request headers the server considers when caching

Cache-Control Directives Explained

Cache-Control: public, max-age=86400, stale-while-revalidate=3600
     │           │         │             │
     │           │         │             └── Serve stale content for 1hr while revalidating
     │           │         └───────────────── Cache freshness: 24 hours
     │           └─────────────────────────── Any cache (CDN, browser) can store it
     └─────────────────────────────────────── (vs "private" = only the browser can cache it)

Cache-Control: no-store
     ↑ Don't cache this at all (for sensitive data like bank account pages)

Cache-Control: no-cache
     ↑ Cache it, but always check with the server before using it

Security Headers

HeaderExamplePurpose
Strict-Transport-Securitymax-age=31536000; includeSubDomainsForce HTTPS for 1 year
X-Content-Type-OptionsnosniffPrevent MIME type sniffing
X-Frame-OptionsDENYPrevent clickjacking via iframes
Content-Security-Policydefault-src 'self'Controls which resources can load
Referrer-Policystrict-origin-when-cross-originControls the Referer header

CORS Response Headers

HeaderExamplePurpose
Access-Control-Allow-Originhttps://nandhoo.comWhich origins can access the response
Access-Control-Allow-MethodsGET, POST, PUT, DELETEWhich HTTP methods are allowed
Access-Control-Allow-HeadersAuthorization, Content-TypeWhich request headers are allowed
Access-Control-Max-Age86400How long to cache the preflight response

Identity and Redirect Headers

HeaderPurpose
LocationURL to redirect to (3xx) or URL of newly created resource (201)
Set-CookieInstructs browser to store a cookie
X-Request-IdUnique ID for this specific request (for server-side debugging)
Retry-AfterWhen to retry after a 429 or 503 (seconds or date)

5. The Response Body

The body contains the actual content. Its format is defined by Content-Type.

JSON Response (API)

{
  "id": 42,
  "username": "Nandhoo",
  "email": "hi@nandhoo.com",
  "courses": [
    { "id": 1, "title": "HTML5", "progress": 85 },
    { "id": 2, "title": "HTTP", "progress": 40 }
  ],
  "createdAt": "2024-09-01T10:30:00Z"
}

HTML Response (Web Page)

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html lang="en">
  <head><title>Nandhoo Dashboard</title></head>
  <body>
    <h1>Welcome back, Nandhoo!</h1>
  </body>
</html>

Plain Text Response

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8

Server is healthy.

No Body Response (204)

HTTP/1.1 204 No Content
Date: Mon, 31 Mar 2025 07:38:00 GMT

[No body]

6. Conditional Requests and Caching in Action

ETags allow the client to ask "has this changed since last time?"

First request (cache empty):
  Client → GET /api/courses/1
  Server → 200 OK, ETag: "v2025-03-31", [body]

Second request (using cache):
  Client → GET /api/courses/1, If-None-Match: "v2025-03-31"
  Server → 304 Not Modified, [no body]  ← no data downloaded!

After content changes:
  Client → GET /api/courses/1, If-None-Match: "v2025-03-31"
  Server → 200 OK, ETag: "v2025-04-01", [new body]

This saves significant bandwidth and speeds up apps that work with frequently-read but rarely-changed data.


7. Streaming Responses

For large or real-time data, the server can stream the response instead of sending it all at once.

const response = await fetch('https://api.nandhoo.com/large-dataset');
const reader   = response.body.getReader();
const decoder  = new TextDecoder();
let   received = 0;

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  received += value.length;
  const chunk = decoder.decode(value, { stream: true });
  console.log(`Received ${received} bytes so far...`);
  process(chunk); // handle each chunk as it arrives
}
console.log('Stream complete!');

Streaming is used for:

  • Video and audio players
  • Large file downloads with progress bars
  • Server-Sent Events for real-time data feeds
  • AI chatbot token-by-token streaming

8. Reading Responses in JavaScript

async function callAPI(url) {
  let response;

  try {
    response = await fetch(url, {
      signal: AbortSignal.timeout(8000) // 8-second timeout
    });
  } catch (err) {
    // Network error (offline, DNS failure, timeout)
    throw new Error('Network error: ' + err.message);
  }

  // Check Content-Type before parsing
  const contentType = response.headers.get('content-type') ?? '';

  if (!response.ok) {
    // Try to get a structured error message from the body
    const body = contentType.includes('application/json')
      ? await response.json()
      : await response.text();

    throw new Error(`HTTP ${response.status}: ${JSON.stringify(body)}`);
  }

  // Parse body based on content type
  if (contentType.includes('application/json')) return response.json();
  if (contentType.includes('text/'))            return response.text();
  if (contentType.includes('image/'))           return response.blob();

  return response.arrayBuffer(); // fallback for binary data
}

9. Inspecting Responses in DevTools

  1. Open Chrome DevToolsNetwork tab
  2. Click any request in the list
  3. Explore the sections:
TabWhat you see
HeadersStatus line, request headers, response headers
PreviewRendered view of the response body (great for JSON)
ResponseRaw response body text
TimingBreakdown: DNS, TCP, TLS, TTFB (Time to First Byte), content download
CookiesAny cookies set by this response

TTFB (Time to First Byte) shows how long the server took to start sending data. A high TTFB means a slow server, not a slow network.


10. Visualising the Response Journey

Server processes request
        │
        ▼
Compose status line:  HTTP/1.1 200 OK
        │
        ▼
Add response headers:
  Content-Type: application/json
  Cache-Control: max-age=300
  ETag: "a1b2c3"
        │
        ▼
Send blank line (headers ended)
        │
        ▼
Send body:  {"id":42,...}
        │
        ▼ (arrives at browser)
Browser checks status code (200 → success)
Browser reads Content-Type → parses as JSON
Browser checks Cache-Control → stores in cache for 5 min
Browser checks ETag → saves for future conditional requests

11. Mini Exercises

  1. Open DevTools → Network tab and visit any website. Find a request with a 304 response. Why did the server send 304?
  2. Find a response that includes an ETag header. What does its value look like?
  3. Look at the Timing tab for any request. How long was the TTFB (Time to First Byte)?
  4. In the browser console, write fetch code that reads the status code and Content-Type header before printing the body.
  5. Find a request that sets a cookie (look for Set-Cookie in the response headers). What flags does it have (HttpOnly, Secure, SameSite)?

12. Key Terms

TermMeaning
Status code3-digit code summarising the result of the request
Response headersMetadata describing how to process the body
Response bodyThe actual content returned (HTML, JSON, image, etc.)
ETagA version fingerprint of a resource for caching
Cache-ControlDirectives that control how the response is cached
304 Not ModifiedTells the client to use its cached copy
TTFBTime To First Byte — how long before the server starts sending data
Content-TypeThe MIME type of the body (tells the client how to parse it)
CORSCross-Origin Resource Sharing — browser security mechanism
Set-CookieInstructs the browser to store a cookie