import re
from dataclasses import dataclass
from typing import Optional, Dict, List
from urllib.parse import urlparse, parse_qs
@dataclass
class RewriteRule:
"""URL rewriting rule configuration"""
name: str
pattern: str
replacement: str
is_regex: bool = False
conditions: Optional[Dict] = None
flags: Optional[Dict[str, any]] = None
class URLRewriter:
"""URL rewriting engine for API gateway"""
def __init__(self):
self.rules: List[RewriteRule] = []
self.compiled_patterns: Dict[str, re.Pattern] = {}
def add_rule(self, rule: RewriteRule):
"""Add rewriting rule to engine"""
self.rules.append(rule)
if rule.is_regex:
self.compiled_patterns[rule.name] = re.compile(rule.pattern)
print(f"Added rewrite rule: {rule.name}")
def rewrite(
self,
url: str,
context: Optional[Dict] = None
) -> tuple[str, bool]:
"""Apply rewriting rules to URL"""
context = context or {}
parsed = urlparse(url)
path = parsed.path
for rule in self.rules:
if not self._check_conditions(rule, context):
continue
if rule.is_regex:
new_path, matched = self._apply_regex(
rule, path, context
)
else:
new_path, matched = self._apply_pattern(
rule, path, context
)
if matched:
new_url = self._reconstruct_url(
parsed, new_path
)
if rule.flags and rule.flags.get("last"):
return new_url, True
path = urlparse(new_url).path
return url, False
def _apply_pattern(
self,
rule: RewriteRule,
path: str,
context: Dict
) -> tuple[str, bool]:
"""Apply pattern-based rewriting"""
pattern_parts = rule.pattern.split('/')
path_parts = path.split('/')
if len(pattern_parts) != len(path_parts):
return path, False
variables = {}
for pp, up in zip(pattern_parts, path_parts):
if pp.startswith('{') and pp.endswith('}'):
var_name = pp[1:-1]
variables[var_name] = up
elif pp != up and pp != '*':
return path, False
new_path = rule.replacement
for var_name, var_value in variables.items():
new_path = new_path.replace(
f"{{{var_name}}}",
var_value
)
return new_path, True
def _apply_regex(
self,
rule: RewriteRule,
path: str,
context: Dict
) -> tuple[str, bool]:
"""Apply regex-based rewriting"""
pattern = self.compiled_patterns[rule.name]
match = pattern.match(path)
if not match:
return path, False
new_path = pattern.sub(rule.replacement, path)
return new_path, True
def _check_conditions(
self,
rule: RewriteRule,
context: Dict
) -> bool:
"""Check if rule conditions are met"""
if not rule.conditions:
return True
for key, expected in rule.conditions.items():
actual = context.get(key)
if actual != expected:
return False
return True
def _reconstruct_url(
self,
parsed,
new_path: str
) -> str:
"""Reconstruct URL with new path"""
return parsed._replace(path=new_path).geturl()
rewriter = URLRewriter()
rewriter.add_rule(RewriteRule(
name="model_routing",
pattern="/models/{model}/{endpoint}",
replacement="/{model}/v1/{endpoint}",
is_regex=False
))
rewriter.add_rule(RewriteRule(
name="version_upgrade",
pattern=r"^/api/v1/(.+)$",
replacement=r"/api/v2/\1",
is_regex=True,
conditions={"method": "GET"}
))
original_url = "/models/gpt-4/completions"
new_url, matched = rewriter.rewrite(original_url)
print(f"{original_url} → {new_url}")