Request Transformation at the API Gateway
In this tutorial, you'll learn about Request Transformation. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Request transformation modifies incoming requests before forwarding them to backend services, enabling protocol adaptation, header injection, and payload normalization.
What You'll Learn
By the end of this lesson, you will implement header modifications, body transformations, query parameter manipulation, and request enrichment patterns.
Why It Matters
Backend services cannot always be modified. Request transformation bridges the gap between client expectations and service contracts without changing either.
Real-World Use
A legacy SOAP service expects XML, but clients send JSON. The gateway transforms JSON to XML, converts the content-type header, and forwards to the legacy service.
Transformation Pipeline
flowchart LR
Client -->|JSON Payload| GW[Gateway]
GW -->|Add Auth Header| GW
GW -->|Convert to XML| GW
GW -->|Forward| Backend[Backend]
style GW fill:#22c55e,color:#fff
Header Transformation
# header_transform.py
from typing import Dict, Optional
class HeaderTransformer:
def __init__(self):
self.add_headers: Dict[str, str] = {}
self.remove_headers: set = set()
self.rename_headers: Dict[str, str] = {}
def add(self, name: str, value: str):
self.add_headers[name] = value
def remove(self, name: str):
self.remove_headers.add(name.lower())
def rename(self, old_name: str, new_name: str):
self.rename_headers[old_name.lower()] = new_name
def transform(self, headers: Dict[str, str]) -> Dict[str, str]:
result = {}
for key, value in headers.items():
key_lower = key.lower()
if key_lower in self.remove_headers:
continue
if key_lower in self.rename_headers:
result[self.rename_headers[key_lower]] = value
else:
result[key] = value
result.update(self.add_headers)
return result
transformer = HeaderTransformer()
transformer.add("X-Gateway", "gateway-v1")
transformer.add("X-Request-ID", "req-12345")
transformer.remove("x-internal-token")
transformer.rename("user-agent", "X-Original-UA")
incoming = {
"Content-Type": "application/json",
"Authorization": "Bearer token123",
"X-Internal-Token": "supersecret",
"User-Agent": "curl/8.0",
}
transformed = transformer.transform(incoming)
for key, value in sorted(transformed.items()):
print(f"{key}: {value}")
Expected output:
Authorization: Bearer token123
Content-Type: application/json
X-Gateway: gateway-v1
X-Original-UA: curl/8.0
X-Request-ID: req-12345
Body Transformation
# body_transform.py
import json
from typing import Any, Dict, Optional
class BodyTransformer:
def add_field(self, body: Dict, key: str, value: Any) -> Dict:
body[key] = value
return body
def remove_field(self, body: Dict, key: str) -> Dict:
body.pop(key, None)
return body
def rename_field(self, body: Dict, old: str, new: str) -> Dict:
if old in body:
body[new] = body.pop(old)
return body
def wrap_in_envelope(self, body: Dict) -> Dict:
return {"data": body, "meta": {"transformed": True, "version": "1.0"}}
def json_to_xml(self, body: Dict) -> str:
xml_parts = ["<request>"]
for key, value in body.items():
xml_parts.append(f" <{key}>{value}</{key}>")
xml_parts.append("</request>")
return "\n".join(xml_parts)
transformer = BodyTransformer()
payload = {"username": "alice", "password": "secret123", "internal_id": "12345"}
payload = transformer.remove_field(payload, "internal_id")
payload = transformer.add_field(payload, "source", "web")
payload = transformer.wrap_in_envelope(payload)
print(json.dumps(payload, indent=2))
Expected output:
{
"data": {
"username": "alice",
"password": "secret123",
"source": "web"
},
"meta": {
"transformed": true,
"version": "1.0"
}
}
Query Parameter Transformation
# query_transform.py
from urllib.parse import urlencode, parse_qs
from typing import Dict, List, Optional
class QueryTransformer:
def __init__(self):
self.add_params: Dict[str, str] = {}
self.remove_params: set = set()
self.rename_params: Dict[str, str] = {}
def add(self, key: str, value: str):
self.add_params[key] = value
def remove(self, key: str):
self.remove_params.add(key)
def rename(self, old: str, new: str):
self.rename_params[old] = new
def transform(self, query_string: str) -> str:
params = parse_qs(query_string)
result = {}
for key, values in params.items():
if key in self.remove_params:
continue
new_key = self.rename_params.get(key, key)
result[new_key] = values[-1]
result.update(self.add_params)
return urlencode(result)
transformer = QueryTransformer()
transformer.add("api_key", "gw_key_123")
transformer.remove("debug")
transformer.rename("v", "version")
result = transformer.transform("page=1&limit=20&v=2&debug=true")
print(f"Original: page=1&limit=20&v=2&debug=true")
print(f"Transformed: {result}")
Expected output:
Original: page=1&limit=20&v=2&debug=true
Transformed: page=1&limit=20&version=2&api_key=gw_key_123
Common Mistakes
1. Modifying Body for GET Requests
GET requests should not have bodies. Only transform headers and query parameters for GET. Apply body transformations to POST/PUT/PATCH.
2. Breaking Content-Length
Transforming the body without updating Content-Length causes truncated or malformed requests. Always recalculate Content-Length.
3. Not Preserving Original Headers
Removing headers like Accept, Content-Type, or Authorization can break downstream services. Preserve or transform, never blindly delete.
4. Adding Sensitive Data to Headers
Injecting API keys or secrets into headers that get logged exposes credentials. Use secure header passing mechanisms.
5. Case Sensitivity Issues
HTTP headers are case-insensitive, but some frameworks treat them case-sensitively. Normalize header names to avoid conflicts.
Practice Questions
1. Why would you transform request headers at the gateway?
To inject authentication tokens, add tracing headers, remove internal headers before they reach clients, or rename headers for compatibility.
2. What is body enrichment?
Adding data to the request body that the client did not provide, such as client IP, timestamp, or user context from the authentication token.
3. How does request transformation support legacy systems?
It converts modern protocols (JSON/REST) to legacy formats (XML/SOAP) without modifying the legacy backend.
4. Why must Content-Length be recalculated after body transformation?
The body size changes after transformation, and an incorrect Content-Length causes the backend to misread the request.
Challenge
Design a transformation pipeline that converts a RESTful JSON request to a SOAP XML request, including header conversion, body restructuring, and content-type changes.
FAQ
Mini Project: Request Transformer
# full_transformer.py
import json
from typing import Any, Dict, Optional
class RequestTransformer:
def __init__(self):
self.header_rules = {}
self.body_rules = {}
self.query_rules = {}
def add_rule(self, stage: str, rule: Dict):
if stage == "header":
self.header_rules = rule
elif stage == "body":
self.body_rules = rule
elif stage == "query":
self.query_rules = rule
def transform(self, request: Dict) -> Dict:
result = dict(request)
if "headers" in result and self.header_rules:
for add_header in self.header_rules.get("add", []):
result["headers"][add_header["name"]] = add_header["value"]
if "body" in result and self.body_rules:
for remove_field in self.body_rules.get("remove", []):
result["body"].pop(remove_field, None)
for add_field in self.body_rules.get("add", []):
result["body"][add_field["name"]] = add_field["value"]
return result
xf = RequestTransformer()
xf.add_rule("header", {"add": [{"name": "X-Gateway", "value": "gw-v1"}]})
xf.add_rule("body", {
"remove": ["password", "ssn"],
"add": [{"name": "processed_by", "value": "gateway"}],
})
req = {
"method": "POST",
"path": "/users",
"headers": {"Content-Type": "application/json"},
"body": {"username": "alice", "password": "secret", "ssn": "123-45-6789"},
}
transformed = xf.transform(req)
print(json.dumps(transformed, indent=2))
Expected output shows body without password/ssn, with processed_by field, and with X-Gateway header added.
What's Next
You understand request transformation. Next, learn about response transformation, then explore rate limiting at the gateway.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro