Python HTTP Deep Dive Practice Problems & Exercises
Practice: HTTP Deep Dive
← Back to lessonWrite a function that classifies an HTTP status code into its category string.Solution
def classify_status(code):
"""Return the category of an HTTP status code as a string.
Categories: 'informational', 'success', 'redirection',
'client error', 'server error', 'unknown'
"""
pass
# Test cases
for code in [100, 200, 201, 204, 301, 400, 401, 403, 404, 422, 500, 503]:
print(f"{code}: {classify_status(code)}")
Expected Output
100: informational
200: success
201: success
204: success
301: redirection
400: client error
401: client error
403: client error
404: client error
422: client error
500: server error
503: server errorHints
Hint 1: HTTP status codes are grouped by their hundreds digit: 1xx, 2xx, 3xx, 4xx, 5xx.
Hint 2: Use integer division: code // 100 gives 1 for 1xx, 2 for 2xx, etc.
Hint 3: Return 'unknown' for anything outside the 100-599 range.
Use Python's urllib.parse to decompose a URL into its scheme, host, path, and query parameters.Solution
from urllib.parse import urlparse, parse_qs
def parse_url(url):
"""Return a dict with keys: scheme, host, path, query_params.
query_params should be a dict of {key: list_of_values}.
"""
pass
url = "https://api.example.com/v1/users?role=admin&active=true&active=1"
result = parse_url(url)
print(result['scheme'])
print(result['host'])
print(result['path'])
print(result['query_params'])
Expected Output
https
api.example.com
/v1/users
{'role': ['admin'], 'active': ['true', '1']}Hints
Hint 1: urllib.parse.urlparse() returns a ParseResult with .scheme, .netloc, .path, .query.
Hint 2: parse_qs() converts a query string like 'a=1&b=2' into a dict of lists.
Hint 3: .netloc contains the host (and port if present).
Write a function that maps HTTP methods to their CRUD equivalents.Solution
def http_to_crud(method):
"""Map an HTTP method string to its primary CRUD operation.
Returns one of: 'create', 'read', 'update', 'delete', 'partial update', 'unknown'
Treat method as case-insensitive.
"""
pass
for method in ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'get', 'post']:
print(f"{method} -> {http_to_crud(method)}")
Expected Output
GET -> read
POST -> create
PUT -> update
DELETE -> delete
PATCH -> partial update
HEAD -> read
get -> read
post -> createHints
Hint 1: Normalize the method with .upper() before mapping.
Hint 2: HEAD is similar to GET but returns no body — it is still a 'read' operation.
Hint 3: Use a dictionary mapping for clean, direct lookups.
Build a URL-encoded query string from a Python dict, handling list values as repeated keys.Solution
from urllib.parse import urlencode
def build_query_string(params):
"""Given a dict of params, return a URL-encoded query string.
Multi-value keys (list values) should produce repeated keys.
Result should NOT include the leading '?'.
"""
pass
print(build_query_string({'search': 'hello world', 'page': 1, 'tags': ['python', 'api']}))
print(build_query_string({'sort': 'asc', 'limit': 10}))
Expected Output
search=hello+world&page=1&tags=python&tags=api
sort=asc&limit=10Hints
Hint 1: urllib.parse.urlencode() handles basic dicts.
Hint 2: Pass doseq=True to urlencode() to expand list values into repeated keys.
Hint 3: Convert non-string values to strings or let urlencode handle them.
Parse a raw HTTP header block into a normalized dict, then extract a Bearer token from the Authorization header.Solution
def parse_headers(raw_headers):
"""Parse a raw HTTP header string into a dict.
Header names should be normalized to Title-Case.
Strip whitespace from names and values.
Return dict of {header_name: header_value}.
"""
pass
def extract_bearer_token(headers):
"""Extract the token from an Authorization: Bearer <token> header.
Return None if header is missing or not a Bearer token.
"""
pass
raw = """Content-Type: application/json
authorization: Bearer eyJhbGciOiJSUzI1NiJ9
cache-control: no-cache, no-store
X-Request-Id: abc-123"""
headers = parse_headers(raw)
print(headers)
print(extract_bearer_token(headers))
Expected Output
{'Content-Type': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJSUzI1NiJ9', 'Cache-Control': 'no-cache, no-store', 'X-Request-Id': 'abc-123'}
eyJhbGciOiJSUzI1NiJ9Hints
Hint 1: Split on the first ':' only to handle values that contain colons (like timestamps).
Hint 2: str.title() converts 'content-type' to 'Content-Type'.
Hint 3: For bearer token extraction, split the Authorization value on a space and check the scheme.
Implement a function that returns the safety and idempotency properties of any HTTP method.Solution
def method_properties(method):
"""Return a dict with keys 'safe' and 'idempotent' (both bool).
Safe: does not modify server state.
Idempotent: multiple identical requests have same effect as one.
GET, HEAD, OPTIONS, TRACE are safe (and idempotent).
PUT, DELETE are idempotent but not safe.
POST, PATCH are neither safe nor idempotent.
"""
pass
for m in ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']:
props = method_properties(m)
print(f"{m}: safe={props['safe']}, idempotent={props['idempotent']}")
Expected Output
GET: safe=True, idempotent=True
POST: safe=False, idempotent=False
PUT: safe=False, idempotent=True
DELETE: safe=False, idempotent=True
PATCH: safe=False, idempotent=False
HEAD: safe=True, idempotent=True
OPTIONS: safe=True, idempotent=TrueHints
Hint 1: Safe methods are a subset of idempotent methods — if a method is safe, it is also idempotent.
Hint 2: PUT is idempotent: PUT /users/1 with the same body always produces the same resource state.
Hint 3: PATCH is NOT guaranteed idempotent: PATCH that does 'increment counter' produces different results each call.
Implement a fetch_user function using urllib.request and verify it works against a mock HTTP response.Solution
import json
from unittest.mock import patch, MagicMock
import urllib.request
def fetch_user(base_url, user_id):
"""Fetch a user by ID from base_url/users/{user_id}.
Return the parsed JSON as a dict.
Raise ValueError if the response status is not 200.
"""
pass
# Test with mock
mock_response_data = {'id': 42, 'name': 'Alice', 'email': '[email protected]'}
with patch('urllib.request.urlopen') as mock_open:
mock_cm = MagicMock()
mock_cm.__enter__ = MagicMock(return_value=mock_cm)
mock_cm.__exit__ = MagicMock(return_value=False)
mock_cm.status = 200
mock_cm.read.return_value = json.dumps(mock_response_data).encode()
mock_open.return_value = mock_cm
user = fetch_user("https://api.example.com", 42)
print(user)
Expected Output
{'id': 42, 'name': 'Alice', 'email': '[email protected]'}Hints
Hint 1: urllib.request.urlopen() returns a context manager; use 'with urlopen(url) as resp:'.
Hint 2: resp.status gives the HTTP status code; resp.read() returns bytes.
Hint 3: json.loads(resp.read().decode()) parses the response body.
Write a parser for the Cache-Control HTTP header that extracts directives into a structured dict.Solution
def parse_cache_control(header_value):
"""Parse a Cache-Control header value into a dict.
Directives with values: max-age=3600 -> {'max-age': 3600}
Flag directives: no-cache -> {'no-cache': True}
Return numeric values as ints where applicable.
"""
pass
examples = [
"no-cache, no-store, must-revalidate",
"public, max-age=86400, s-maxage=3600",
"private, max-age=0, no-cache",
"max-age=31536000, immutable",
]
for cc in examples:
print(parse_cache_control(cc))
Expected Output
{'no-cache': True, 'no-store': True, 'must-revalidate': True}
{'public': True, 'max-age': 86400, 's-maxage': 3600}
{'private': True, 'max-age': 0, 'no-cache': True}
{'max-age': 31536000, 'immutable': True}Hints
Hint 1: Split the header value on ',' to get individual directives.
Hint 2: For each directive, split on '=' to check if it has a value.
Hint 3: Cast numeric values with int() — max-age values are always integers.
Build a raw HTTP/1.1 request string from method, URL, headers, and body components.Solution
def build_http_request(method, url, headers=None, body=None):
"""Build a raw HTTP/1.1 request string.
Format:
METHOD /path?query HTTP/1.1\r\n
Host: host\r\n
Header-Name: value\r\n
\r\n
body (if any)
Auto-add Content-Length header if body is provided.
Auto-add Host header from URL if not in headers.
Return the complete request as a string.
"""
pass
import json
payload = json.dumps({'name': 'Alice', 'role': 'admin'})
req = build_http_request(
'POST',
'https://api.example.com/users?notify=true',
headers={'Content-Type': 'application/json', 'Authorization': 'Bearer token123'},
body=payload
)
print(req)
Expected Output
POST /users?notify=true HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token123
Content-Length: 34
{"name": "Alice", "role": "admin"}Hints
Hint 1: Use urllib.parse.urlparse() to extract host, path, and query from the URL.
Hint 2: Reconstruct the request line as 'METHOD /path?query HTTP/1.1'.
Hint 3: Each header line ends with \r\n; the blank line separating headers from body is also \r\n.
Implement HTTP GET with exponential backoff retry, sleeping for base_delay * 2^attempt between retries.Solution
import time
from unittest.mock import patch, call
def http_get_with_retry(url, max_retries=3, base_delay=1.0, retryable_codes=(429, 500, 502, 503)):
"""Perform a GET request with exponential backoff retry.
Retry on retryable_codes: wait base_delay * (2 ** attempt) seconds between retries.
Return (status_code, body) on success (2xx).
Raise RuntimeError after exhausting retries.
Use the provided _make_request(url) stub for the actual call.
"""
pass
def _make_request(url):
"""Stub — replaced in tests."""
raise NotImplementedError
# Simulate: first two calls return 503, third returns 200
call_results = [(503, ''), (503, ''), (200, '{"ok": true}')]
call_iter = iter(call_results)
with patch('__main__._make_request', side_effect=lambda u: next(call_iter)), patch('time.sleep') as mock_sleep:
status, body = http_get_with_retry('https://api.example.com/data', base_delay=1.0)
print(f"status={status}, body={body}")
print(f"sleep calls: {[c.args[0] for c in mock_sleep.call_args_list]}")
Expected Output
status=200, body={"ok": true}
sleep calls: [1.0, 2.0]Hints
Hint 1: Loop from attempt=0 to max_retries (inclusive). On each failure, sleep base_delay * (2 ** attempt).
Hint 2: Do not sleep after the final failed attempt — raise RuntimeError instead.
Hint 3: Check if status // 100 == 2 to detect a successful response.
Simulate HTTP/1.1 sequential vs HTTP/2 multiplexed request loading and measure the speedup.Solution
import time
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
def simulate_http1_sequential(requests_ms):
"""Simulate HTTP/1.1: requests execute one at a time.
requests_ms: list of (name, duration_ms) tuples.
Return (total_ms, results) where results is list of names in completion order.
Use time.sleep(duration_ms / 1000).
"""
pass
def simulate_http2_multiplexed(requests_ms, max_workers=6):
"""Simulate HTTP/2 multiplexing: all requests run concurrently.
Return (total_ms, results) where results is list of names in completion order.
"""
pass
requests = [
('CSS', 50),
('JS bundle', 200),
('API call', 150),
('Font', 80),
('Image', 300),
]
h1_time, h1_order = simulate_http1_sequential(requests)
h2_time, h2_order = simulate_http2_multiplexed(requests)
print(f"HTTP/1.1 total: {h1_time}ms")
print(f"HTTP/2 total: {h2_time}ms")
print(f"HTTP/1.1 order: {h1_order}")
print(f"HTTP/2 order: {h2_order}")
print(f"Speedup: {h1_time / h2_time:.1f}x")
Expected Output
HTTP/1.1 total: 780ms
HTTP/2 total: 300ms
HTTP/1.1 order: ['CSS', 'JS bundle', 'API call', 'Font', 'Image']
HTTP/2 order: ['CSS', 'Font', 'API call', 'JS bundle', 'Image']
Speedup: 2.6xHints
Hint 1: HTTP/1.1 sequential: sum all durations, process in order.
Hint 2: HTTP/2 multiplexed: use ThreadPoolExecutor, record completions via as_completed().
Hint 3: For total time in HTTP/2, take the max duration (the longest request determines wall time).
