š¦ 2.6 Identify the Parts of an HTTP Response
What is an HTTP Response?
An HTTP response is the message sent by a server back to a client after receiving and processing an HTTP request. Every HTTP response consists of three main parts: Status Code, Headers, and Body.
Part 1: Status Code (Status Line)
The status line is the first line of an HTTP response and contains three elements:
Status Line Format
HTTP/1.1 200 OK
Components:
- HTTP/1.1 - HTTP protocol version
- 200 - Status code (3-digit number)
- OK - Status message (human-readable description)
| Status Code Category | Range | Meaning | Examples |
|---|---|---|---|
| Informational | 1xx | Request received, processing continues | 100 Continue, 101 Switching Protocols |
| Success | 2xx | Request successfully processed | 200 OK, 201 Created, 204 No Content |
| Redirection | 3xx | Further action needed | 301 Moved Permanently, 302 Found |
| Client Error | 4xx | Client-side error in request | 400 Bad Request, 401 Unauthorized, 404 Not Found |
| Server Error | 5xx | Server failed to fulfill request | 500 Internal Server Error, 503 Service Unavailable |
Part 2: Response Headers
Response headers provide metadata about the response. They contain key-value pairs that give additional information about the server, the response data, and how the client should handle it.
Common Response Headers
| Header Name | Purpose | Example Value |
|---|---|---|
Content-Type |
Specifies format of response body | application/json text/html application/xml |
Content-Length |
Size of response body in bytes | 1234 |
Content-Encoding |
Compression method used | gzip, deflate, br |
Cache-Control |
Caching directives | no-cache max-age=3600 public |
Set-Cookie |
Set cookies on client | sessionId=abc123; Path=/ |
Server |
Information about server software | Apache/2.4.1 nginx/1.18.0 |
Date |
Timestamp when response was sent | Wed, 21 Oct 2024 07:28:00 GMT |
Location |
URL for redirect or new resource | https://api.example.com/users/123 |
X-RateLimit-Limit |
Maximum requests allowed | 1000 |
X-RateLimit-Remaining |
Requests remaining in time window | 999 |
Part 3: Response Body
The response body contains the actual data being sent back to the client. The format depends on the Content-Type header.
Common Response Body Formats
1. JSON (JavaScript Object Notation)
Most common format for REST APIs
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"active": true,
"roles": ["user", "admin"],
"created_at": "2024-01-15T10:30:00Z"
}Content-Type: application/json
2. XML (Extensible Markup Language)
Structured format, common in SOAP APIs
<?xml version="1.0" encoding="UTF-8"?>
<user>
<id>123</id>
<name>John Doe</name>
<email>john@example.com</email>
<active>true</active>
</user>Content-Type: application/xml or text/xml
3. HTML (HyperText Markup Language)
Used for web pages
<!DOCTYPE html>
<html>
<head>
<title>Welcome</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>Content-Type: text/html
4. Plain Text
Simple text data
Hello, World! This is plain text response.
Content-Type: text/plain
Complete HTTP Response Example
HTTP/1.1 200 OK ā Status Line
Date: Wed, 21 Oct 2024 07:28:00 GMT ā Headers start
Server: nginx/1.18.0
Content-Type: application/json
Content-Length: 154
Cache-Control: max-age=3600
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
Connection: keep-alive ā Headers end
ā Blank line separates headers from body
{ ā Body starts
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"status": "active"
} ā Body endsPython Example: Parsing HTTP Response Parts
#!/usr/bin/env python3
"""
HTTP Response Parts Analysis
Demonstrates how to access and parse each part of an HTTP response
"""
import requests
import json
def analyze_http_response(url):
"""
Make HTTP request and analyze all parts of the response
"""
print("=" * 70)
print(f"Analyzing HTTP Response from: {url}")
print("=" * 70)
# Make the request
response = requests.get(url)
# ========================================================================
# PART 1: STATUS CODE (STATUS LINE)
# ========================================================================
print("\nš PART 1: STATUS LINE")
print("-" * 70)
print(f"HTTP Version: HTTP/1.1 (default)")
print(f"Status Code: {response.status_code}")
print(f"Status Message: {response.reason}")
print(f"Success: {response.ok}") # True if status < 400
# ========================================================================
# PART 2: RESPONSE HEADERS
# ========================================================================
print("\nš PART 2: RESPONSE HEADERS")
print("-" * 70)
# Display all headers
print("All Headers:")
for header, value in response.headers.items():
print(f" {header}: {value}")
# Access specific important headers
print("\nKey Headers:")
print(f" Content-Type: {response.headers.get('Content-Type', 'N/A')}")
print(f" Content-Length: {response.headers.get('Content-Length', 'N/A')}")
print(f" Server: {response.headers.get('Server', 'N/A')}")
print(f" Date: {response.headers.get('Date', 'N/A')}")
print(f" Cache-Control: {response.headers.get('Cache-Control', 'N/A')}")
# Check for rate limiting headers
if 'X-RateLimit-Limit' in response.headers:
print(f"\nRate Limiting Info:")
print(f" Limit: {response.headers.get('X-RateLimit-Limit')}")
print(f" Remaining: {response.headers.get('X-RateLimit-Remaining')}")
print(f" Reset: {response.headers.get('X-RateLimit-Reset')}")
# ========================================================================
# PART 3: RESPONSE BODY
# ========================================================================
print("\nš¦ PART 3: RESPONSE BODY")
print("-" * 70)
# Determine content type
content_type = response.headers.get('Content-Type', '')
print(f"Content-Type: {content_type}")
# Parse based on content type
if 'application/json' in content_type:
print("\nBody Format: JSON")
try:
data = response.json()
print("Parsed JSON:")
print(json.dumps(data, indent=2))
# Access JSON data
print("\nAccessing JSON Fields:")
if isinstance(data, dict):
for key in list(data.keys())[:5]: # Show first 5 keys
print(f" {key}: {data[key]}")
except json.JSONDecodeError:
print("Failed to parse JSON")
print(response.text[:500])
elif 'text/html' in content_type:
print("\nBody Format: HTML")
print("HTML Content (first 500 chars):")
print(response.text[:500])
elif 'text/plain' in content_type:
print("\nBody Format: Plain Text")
print(response.text)
else:
print("\nBody Format: Other/Binary")
print(f"Body Size: {len(response.content)} bytes")
print("Raw content (first 200 chars):")
print(response.text[:200])
# ========================================================================
# ADDITIONAL RESPONSE PROPERTIES
# ========================================================================
print("\nā” ADDITIONAL PROPERTIES")
print("-" * 70)
print(f"Encoding: {response.encoding}")
print(f"URL: {response.url}")
print(f"Elapsed Time: {response.elapsed.total_seconds():.3f} seconds")
print(f"Cookies: {dict(response.cookies)}")
return response
# =============================================================================
# EXAMPLE USAGE
# =============================================================================
if __name__ == "__main__":
# Example 1: JSON API Response
print("\n" + "="*70)
print("EXAMPLE 1: Analyzing JSON API Response")
print("="*70)
analyze_http_response("https://jsonplaceholder.typicode.com/users/1")
input("\n\nPress Enter to see another example...")
# Example 2: Different endpoint
print("\n" + "="*70)
print("EXAMPLE 2: Analyzing List Response")
print("="*70)
analyze_http_response("https://jsonplaceholder.typicode.com/posts?_limit=3")
- Status Code: Tells you if request succeeded (2xx), failed (4xx/5xx), or needs action (3xx)
- Headers: Provide metadata - content type, encoding, caching, rate limits, etc.
- Body: Contains actual data - format specified by Content-Type header
- Always check all three parts when debugging API issues
š 2.7 Utilize Common API Authentication Mechanisms
What is API Authentication?
API authentication is the process of verifying the identity of a client making requests to an API. It ensures that only authorized users or applications can access protected resources.
1. Basic Authentication
Basic Auth Username and Password
Basic Authentication is the simplest form of HTTP authentication. It sends a username and password encoded in Base64 in the Authorization header.
- Client combines username and password with a colon:
username:password - Encodes the string in Base64
- Sends in Authorization header:
Authorization: Basic <base64-string> - Server decodes and verifies credentials
Format
# Header Format Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ= # Breakdown: # "username:password" -> Base64 encode -> "dXNlcm5hbWU6cGFzc3dvcmQ="
Python Implementation
import requests
from requests.auth import HTTPBasicAuth
import base64
# Method 1: Using HTTPBasicAuth (Recommended)
response = requests.get(
'https://api.example.com/data',
auth=HTTPBasicAuth('username', 'password')
)
# Method 2: Using auth tuple (shorthand)
response = requests.get(
'https://api.example.com/data',
auth=('username', 'password')
)
# Method 3: Manual header construction
username = 'username'
password = 'password'
credentials = f"{username}:{password}"
encoded = base64.b64encode(credentials.encode()).decode()
headers = {
'Authorization': f'Basic {encoded}'
}
response = requests.get('https://api.example.com/data', headers=headers)
print(f"Status: {response.status_code}")
print(f"Data: {response.json()}")- Not Encrypted: Base64 encoding is NOT encryption - credentials are easily decoded
- Must use HTTPS: Always use Basic Auth over HTTPS/TLS to encrypt transmission
- Not Recommended: Use only for simple applications or internal APIs
- No Expiration: Credentials remain valid until changed
| Pros | Cons |
|---|---|
| ā
Simple to implement ā Built into HTTP standard ā Widely supported |
ā Less secure (if not over HTTPS) ā Credentials sent with every request ā No token expiration ā Hard to revoke access |
2. Custom Token Authentication (Bearer Token)
Bearer Token Token-Based Authentication
Token authentication uses a unique token (usually a JSON Web Token - JWT) that is issued after successful login. The token is sent in the Authorization header with the Bearer prefix.
- Client sends login credentials to authentication endpoint
- Server validates credentials and issues a token (often JWT)
- Client stores token securely
- Client includes token in Authorization header for subsequent requests
- Server validates token and grants access if valid
Format
# Header Format Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjN9.xyzABC123 # JWT Token Structure (3 parts separated by dots): # 1. Header (algorithm, token type) # 2. Payload (user data, expiration) # 3. Signature (verifies token integrity)
Python Implementation
import requests
# Step 1: Login to get token
login_url = "https://api.example.com/auth/login"
credentials = {
"username": "user@example.com",
"password": "secretpassword"
}
# Get token from login endpoint
login_response = requests.post(login_url, json=credentials)
token = login_response.json()['access_token']
print(f"Received token: {token[:50]}...")
# Step 2: Use token for API requests
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
# Make authenticated request
response = requests.get(
'https://api.example.com/api/user/profile',
headers=headers
)
if response.status_code == 200:
print("ā
Authentication successful!")
print(f"User data: {response.json()}")
elif response.status_code == 401:
print("ā Token expired or invalid")
else:
print(f"ā Error: {response.status_code}")
# =============================================================================
# TOKEN REFRESH EXAMPLE
# =============================================================================
def make_authenticated_request(url, token):
"""Make request with token, handle refresh if needed"""
headers = {'Authorization': f'Bearer {token}'}
response = requests.get(url, headers=headers)
if response.status_code == 401:
# Token expired, refresh it
print("Token expired, refreshing...")
new_token = refresh_token(token)
headers['Authorization'] = f'Bearer {new_token}'
response = requests.get(url, headers=headers)
return response
def refresh_token(old_token):
"""Request new token using refresh token"""
refresh_url = "https://api.example.com/auth/refresh"
response = requests.post(
refresh_url,
headers={'Authorization': f'Bearer {old_token}'}
)
return response.json()['access_token']- Stateless: Server doesn't need to store tokens - validates using signature
- Self-contained: Token includes user information and expiration
- Expiration: Tokens expire automatically for security
- Revocable: Can be invalidated if compromised
- Cross-domain: Works across different domains/services
| Pros | Cons |
|---|---|
| ā
More secure than Basic Auth ā Tokens expire automatically ā Can be revoked ā Stateless (scalable) ā Can store user data in token |
ā More complex to implement ā Requires token management ā Token refresh logic needed ā Larger header size |
3. API Key Authentication
API Key Key-Based Authentication
API Key authentication uses a unique key assigned to each client. The key is sent in a custom header or as a query parameter with each request.
- Developer registers application and receives API key
- API key is stored securely by client
- Client includes API key in header or query parameter
- Server validates key and grants access if valid
- Server may track usage, rate limits per key
Common Formats
# Method 1: Custom Header (Most Common) X-API-Key: abc123def456ghi789jkl Api-Key: abc123def456ghi789jkl apikey: abc123def456ghi789jkl # Method 2: Authorization Header Authorization: ApiKey abc123def456ghi789jkl # Method 3: Query Parameter (Less Secure) https://api.example.com/data?api_key=abc123def456ghi789jkl https://api.example.com/data?apikey=abc123def456ghi789jkl
Python Implementation
import requests
import os
# Best practice: Store API key in environment variable
API_KEY = os.getenv('API_KEY', 'your-api-key-here')
# Method 1: Using custom header (Most Common)
headers = {
'X-API-Key': API_KEY,
'Content-Type': 'application/json'
}
response = requests.get(
'https://api.example.com/data',
headers=headers
)
# Method 2: Using Authorization header
headers = {
'Authorization': f'ApiKey {API_KEY}'
}
response = requests.get(
'https://api.example.com/data',
headers=headers
)
# Method 3: Using query parameter (Not recommended - visible in logs)
params = {
'api_key': API_KEY
}
response = requests.get(
'https://api.example.com/data',
params=params
)
print(f"Status: {response.status_code}")
print(f"Data: {response.json()}")
# =============================================================================
# REAL-WORLD EXAMPLES
# =============================================================================
# Example 1: OpenWeatherMap API
def get_weather(city):
"""Get weather data using API key"""
api_key = os.getenv('OPENWEATHER_API_KEY')
url = "https://api.openweathermap.org/data/2.5/weather"
params = {
'q': city,
'appid': api_key,
'units': 'metric'
}
response = requests.get(url, params=params)
if response.status_code == 200:
data = response.json()
return {
'city': data['name'],
'temperature': data['main']['temp'],
'description': data['weather'][0]['description']
}
else:
return {'error': f'Status {response.status_code}'}
# Example 2: GitHub API
def get_github_user(username):
"""Get GitHub user info using API key"""
token = os.getenv('GITHUB_TOKEN')
url = f"https://api.github.com/users/{username}"
headers = {
'Authorization': f'token {token}',
'Accept': 'application/vnd.github.v3+json'
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
return {
'name': data['name'],
'followers': data['followers'],
'public_repos': data['public_repos']
}
else:
return {'error': f'Status {response.status_code}'}
# Example 3: Rate limiting with API key
def make_api_request_with_limits(url, api_key):
"""Make API request and check rate limits"""
headers = {'X-API-Key': api_key}
response = requests.get(url, headers=headers)
# Check rate limit headers
limit = response.headers.get('X-RateLimit-Limit')
remaining = response.headers.get('X-RateLimit-Remaining')
reset = response.headers.get('X-RateLimit-Reset')
print(f"Rate Limit: {remaining}/{limit} requests remaining")
print(f"Resets at: {reset}")
if response.status_code == 429:
print("ā Rate limit exceeded!")
retry_after = response.headers.get('Retry-After')
print(f"Retry after: {retry_after} seconds")
return response- Never hardcode: Store API keys in environment variables or secure vaults
- Never commit to Git: Add to .gitignore, use .env files
- Use headers, not URLs: Query parameters are logged and cached
- Rotate regularly: Change API keys periodically
- Use different keys: Separate keys for development, staging, production
- Monitor usage: Track API key usage to detect abuse
| Pros | Cons |
|---|---|
| ā
Simple to implement ā Easy to track usage per client ā Good for rate limiting ā Can revoke individual keys ā No login required |
ā No user-level authentication ā Keys don't expire automatically ā If leaked, harder to detect ā Less secure than OAuth |
Comparison of Authentication Methods
| Feature | Basic Auth | Bearer Token (JWT) | API Key |
|---|---|---|---|
| Security | ā ļø Low (if not HTTPS) | ā High | ā ļø Medium |
| Complexity | ā Very Simple | ā ļø Complex | ā Simple |
| Expiration | ā No | ā Yes (auto) | ā No (manual) |
| User Identity | ā Yes | ā Yes | ā No (app-level) |
| Rate Limiting | ā ļø By user | ā ļø By user | ā By key |
| Revocation | ā ļø Change password | ā Easy | ā Easy |
| Use Case | Internal APIs, simple apps | User authentication, mobile apps | Third-party integrations, public APIs |
- Basic Auth: Internal tools, development/testing, simple applications
- Bearer Token (JWT): User-facing applications, mobile apps, single-page apps (SPAs)
- API Key: Third-party integrations, public APIs, service-to-service communication
š Interactive Python Interpreter
- Write Python code in the editor below
- Click "ā¶ Run Code" to execute
- Output appears in the console below
- Use sample codes for quick testing
- Experiment with response parsing and authentication examples