FastAPI Request Objects
Introduction
When building APIs with FastAPI, understanding how to work with request objects is essential. The Request object provides access to the underlying HTTP request data, giving you control over how your API interacts with clients. In this guide, we'll explore FastAPI's Request objects in depth, learning how to access request data, headers, cookies, and more.
A Request object represents the HTTP request sent by the client to your API. It contains all the information about that request, including:
- The HTTP method (GET, POST, PUT, etc.)
- Headers sent by the client
- Query parameters
- Request body
- Client information (IP address, etc.)
- Cookies
Let's dive in and see how we can harness the power of Request objects in FastAPI!
Importing the Request Object
To work with Request objects in FastAPI, you need to import them from the fastapi
package:
from fastapi import FastAPI, Request
app = FastAPI()
Basic Request Object Usage
The simplest way to work with a Request object is to include it as a parameter in your path operation function:
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/")
async def root(request: Request):
return {"request_url": request.url.path}
In this example, we're accessing the path
attribute of the request URL and returning it. If you navigate to the root endpoint (/
), you'll get:
{
"request_url": "/"
}
Accessing Request Properties
URL and Query Parameters
FastAPI's Request object allows you to access various URL components:
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/items")
async def read_items(request: Request):
url_path = request.url.path
full_url = str(request.url)
query_params = dict(request.query_params)
return {
"url_path": url_path,
"full_url": full_url,
"query_params": query_params
}
If you visit /items?category=books&sort=price
, you'll get:
{
"url_path": "/items",
"full_url": "http://127.0.0.1:8000/items?category=books&sort=price",
"query_params": {
"category": "books",
"sort": "price"
}
}
Request Headers
Accessing headers is straightforward with the Request object:
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/headers")
async def read_headers(request: Request):
user_agent = request.headers.get("user-agent")
accept_language = request.headers.get("accept-language")
all_headers = dict(request.headers)
return {
"user_agent": user_agent,
"accept_language": accept_language,
"all_headers": all_headers
}
The output will include the headers sent in your request:
{
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...",
"accept_language": "en-US,en;q=0.9",
"all_headers": {
"host": "127.0.0.1:8000",
"connection": "keep-alive",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...",
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp...",
"accept-language": "en-US,en;q=0.9"
// ... other headers
}
}
Client Information
You can access client information such as IP address:
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/client")
async def client_info(request: Request):
client_host = request.client.host
client_port = request.client.port
return {
"client_host": client_host,
"client_port": client_port
}
Example response:
{
"client_host": "127.0.0.1",
"client_port": 52431
}
Working with Request Body
FastAPI provides multiple ways to access the request body. Here's how to access the raw request body:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/raw-body")
async def get_raw_body(request: Request):
body = await request.body()
return {
"body_size": len(body),
"body_content": body.decode()
}
If you send a POST request with a JSON body like {"name": "John", "age": 30}
, you'll receive:
{
"body_size": 28,
"body_content": "{\"name\": \"John\", \"age\": 30}"
}
For JSON content, you can use request.json()
:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/json-body")
async def get_json_body(request: Request):
json_data = await request.json()
return {
"received_data": json_data,
"name_from_json": json_data.get("name")
}
Response for the same JSON input:
{
"received_data": {
"name": "John",
"age": 30
},
"name_from_json": "John"
}
For form data, use request.form()
:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/form-data")
async def get_form_data(request: Request):
form_data = await request.form()
return {
"form_items": dict(form_data)
}
Accessing Cookies
You can access cookies from the request object:
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/cookies")
async def read_cookies(request: Request):
session_cookie = request.cookies.get("session")
all_cookies = request.cookies
return {
"session_cookie": session_cookie,
"all_cookies": all_cookies
}
Real-World Example: Request Logger Middleware
Let's implement a practical example of using Request objects in a middleware to log request details:
from fastapi import FastAPI, Request
import time
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger("request-logger")
app = FastAPI()
@app.middleware("http")
async def log_requests(request: Request, call_next):
# Get request details
request_id = id(request)
path = request.url.path
method = request.method
client_ip = request.client.host
# Log request start
logger.info(f"Request [{request_id}] {method} {path} started from {client_ip}")
# Process request and time it
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
# Log request completion
logger.info(
f"Request [{request_id}] {method} {path} completed in {process_time:.4f}s "
f"with status {response.status_code}"
)
return response
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
When you make requests to your API, you'll see log entries like:
2023-08-15 14:23:45,123 - request-logger - INFO - Request [140371825953232] GET / started from 127.0.0.1
2023-08-15 14:23:45,125 - request-logger - INFO - Request [140371825953232] GET / completed in 0.0021s with status 200
2023-08-15 14:23:50,789 - request-logger - INFO - Request [140371825954512] GET /items/42?q=test started from 127.0.0.1
2023-08-15 14:23:50,791 - request-logger - INFO - Request [140371825954512] GET /items/42?q=test completed in 0.0015s with status 200
This middleware logs each request's details, timing, and outcome—useful for debugging, monitoring, and performance analysis.
Real-World Example: API Key Validation
Here's another practical example showing how to validate API keys in request headers:
from fastapi import FastAPI, Request, HTTPException
from typing import Dict
app = FastAPI()
# In a real app, you'd store these in a secure database
API_KEYS: Dict[str, str] = {
"abc123": "user1",
"xyz789": "user2",
"def456": "admin"
}
@app.get("/secure-data")
async def get_secure_data(request: Request):
# Get API key from header
api_key = request.headers.get("X-API-Key")
if not api_key:
raise HTTPException(status_code=401, detail="API Key is missing")
if api_key not in API_KEYS:
raise HTTPException(status_code=403, detail="Invalid API Key")
# Get the user associated with the API key
user = API_KEYS[api_key]
return {
"message": f"Secure data for {user}",
"timestamp": time.time()
}
To test this endpoint, you'd make a request with an API key in the headers:
GET /secure-data
X-API-Key: abc123
Response:
{
"message": "Secure data for user1",
"timestamp": 1692106234.567
}
Without a valid API key, you'd receive an error response.
Handling Large Request Bodies
When dealing with large file uploads or request bodies, you can use request.stream()
to process the data in chunks:
from fastapi import FastAPI, Request
import aiofiles
import os
app = FastAPI()
@app.post("/upload-large-file")
async def upload_large_file(request: Request):
# Create a file to write to
file_path = "uploaded_file.dat"
total_bytes = 0
async with aiofiles.open(file_path, 'wb') as f:
# Process the request body in chunks
async for chunk in request.stream():
total_bytes += len(chunk)
await f.write(chunk)
file_size = os.path.getsize(file_path)
return {
"file_size": file_size,
"total_bytes_received": total_bytes,
"message": "File uploaded successfully"
}
This approach is memory-efficient because it doesn't load the entire file into memory at once.
Summary
FastAPI's Request objects provide a powerful interface for accessing all aspects of incoming HTTP requests. In this guide, we've covered:
- Accessing basic request properties like URL, path, and query parameters
- Reading request headers and client information
- Working with request bodies in various formats
- Accessing cookies
- Real-world examples including request logging middleware and API key validation
- Handling large request bodies efficiently
Understanding Request objects is crucial for building robust APIs that can properly process client data, validate inputs, and implement security features like authentication and authorization.
Additional Resources
- FastAPI Official Documentation on Request Objects
- Starlette Request Documentation (FastAPI is built on Starlette)
- HTTP Protocol Documentation
Exercises
- Create an endpoint that returns all information about the client's browser based on the User-Agent header.
- Build a request counter middleware that tracks how many requests each client IP makes.
- Implement an endpoint that accepts file uploads and validates file types based on content inspection.
- Create a rate limiter middleware that uses request information to limit how many requests a client can make per minute.
- Build an endpoint that accepts JSON data and form data in the same request and processes both.
By mastering FastAPI Request objects, you'll be able to build more sophisticated, secure, and user-friendly APIs!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)