URL Normalization Middleware
A lightweight, production‑ready URL normalization middleware for the Nexios ASGI framework.
It automatically handles trailing slashes, double slashes, and other common URL normalization issues to ensure consistent and clean URLs across your application.
Installation
bash
pip install nexios_contribQuick Start
python
from nexios import NexiosApp
import nexios_contrib.slashes as slashes
app = NexiosApp()
# Add URL normalization middleware
app.add_middleware(slashes.Slashes(
slash_action=slashes.SlashAction.REDIRECT_REMOVE, # Redirect to remove trailing slashes
auto_remove_double_slashes=True, # Clean up double slashes
redirect_status_code=301 # Use permanent redirects
))
@app.get("/users")
async def users(request, response):
return {"users": ["alice", "bob", "charlie"]}
@app.get("/posts")
async def posts(request, response):
return {"posts": ["post1", "post2", "post3"]}Requests to /users/ will redirect to /users, and URLs with double slashes like /users//123 will be cleaned to /users/123.
Configuration
Slash Action Options
The middleware supports several modes for handling trailing slashes:
SlashAction.REDIRECT_REMOVE(default): Redirect to remove trailing slashesSlashAction.REDIRECT_ADD: Redirect to add trailing slashesSlashAction.REMOVE: Remove trailing slashes without redirectSlashAction.ADD: Add trailing slashes without redirectSlashAction.IGNORE: Leave trailing slashes as-is (only clean double slashes)
Parameters
slash_action: SlashAction- How to handle trailing slashes (default: REDIRECT_REMOVE)auto_remove_double_slashes: bool- Remove double slashes automatically (default: True)redirect_status_code: int- HTTP status code for redirects (default: 301)
Examples
SEO-Friendly Setup (Remove Trailing Slashes)
python
app.add_middleware(slashes.Slashes(
slash_action=slashes.SlashAction.REDIRECT_REMOVE,
redirect_status_code=301 # SEO-friendly permanent redirect
))Result:
/api/users/→ redirects to/api/users/blog/posts/→ redirects to/blog/posts
Directory-Style URLs (Add Trailing Slashes)
python
app.add_middleware(slashes.Slashes(
slash_action=slashes.SlashAction.REDIRECT_ADD,
redirect_status_code=301
))Result:
/api/users→ redirects to/api/users//blog/posts→ redirects to/blog/posts/
Silent Normalization (No Redirects)
python
app.add_middleware(slashes.Slashes(
slash_action=slashes.SlashAction.REMOVE, # Just remove, no redirect
auto_remove_double_slashes=True
))Result:
/api/users/→ internally processed as/api/users- No redirect sent to client
Minimal Processing
python
app.add_middleware(slashes.Slashes(
slash_action=slashes.SlashAction.IGNORE, # Don't touch slashes
auto_remove_double_slashes=True # Only clean double slashes
))Result:
/api//users→ cleaned to/api/users- Trailing slashes left unchanged
How It Works
- Path Analysis: Examines the request path for normalization issues
- Skip Logic: Intelligently skips processing for files, API paths, and query parameters
- Double Slash Removal: Cleans up double slashes (
//becomes/) - Trailing Slash Handling: Applies the configured slash action
- Redirect or Modify: Either redirects the client or modifies the request path
Skip Patterns
The middleware automatically skips processing for paths containing:
- File extensions (e.g.,
.css,.js,.jpg) - Query parameters (e.g.,
?param=value) - URL fragments (e.g.,
#section)
This ensures that:
- Static files are served correctly
- API endpoints with parameters work properly
- Client-side routing isn't interfered with
URL Normalization Features
- Double Slash Removal:
//api/users→/api/users - Trailing Slash Handling: Configurable add/remove/redirect behavior
- Smart Skipping: Preserves file extensions and query parameters
- SEO Optimization: Uses 301 redirects for permanent URL changes
- Proxy Compatible: Works correctly behind reverse proxies
- Standards Compliant: Follows web standards for URL normalization
Use Cases
SEO & Consistency
python
# Ensure all URLs have consistent trailing slash behavior
slashes.Slashes(slash_action=slashes.SlashAction.REDIRECT_REMOVE)Benefits:
- Prevents duplicate content issues
- Consistent URL structure
- Better search engine indexing
Clean URLs
python
# Remove double slashes and normalize paths
slashes.Slashes(auto_remove_double_slashes=True)Benefits:
- Cleaner logs and analytics
- Prevents routing issues
- Better user experience
API Design
python
# API routes without trailing slashes
slashes.Slashes(
slash_action=slashes.SlashAction.REMOVE,
auto_remove_double_slashes=True
)Benefits:
- Consistent API endpoints
- Cleaner API documentation
- Better client integration
Legacy URL Support
python
# Redirect old URLs to new format
slashes.Slashes(
slash_action=slashes.SlashAction.REDIRECT_REMOVE,
redirect_status_code=301
)Benefits:
- Maintains SEO value
- Smooth migration path
- User-friendly redirects
Advanced Usage
Custom Skip Logic
python
import nexios_contrib.slashes as slashes
class CustomSlashesMiddleware(slashes.SlashesMiddleware):
def _should_skip_processing(self, path: str) -> bool:
# Custom logic for skipping paths
if super()._should_skip_processing(path):
return True
# Skip specific paths
if path.startswith("/api/v2/"):
return True
# Skip webhook endpoints
if "/webhook" in path:
return True
return False
app.add_middleware(CustomSlashesMiddleware(
slash_action=slashes.SlashAction.REDIRECT_REMOVE
))Programmatic URL Normalization
python
from nexios_contrib.slashes.helpers import normalize_path, clean_url_path
# Normalize a path
clean_path = normalize_path("/api//users//123") # "/api/users/123"
# Clean a full URL
clean_url = clean_url_path("https://example.com/api//users") # "https://example.com/api/users"
# Use in your handlers
@app.get("/redirect")
async def redirect_handler(request, response):
target_path = normalize_path(request.query_params.get("url", "/"))
return response.redirect(target_path)Conditional Processing
python
from nexios_contrib.slashes import SlashesMiddleware, SlashAction
class ConditionalSlashesMiddleware(SlashesMiddleware):
def __init__(self, **kwargs):
super().__init__(**kwargs)
async def __call__(self, request, response, call_next):
# Different behavior for different domains
host = request.headers.get("host", "")
if host.startswith("api."):
# API subdomain: remove trailing slashes
self.slash_action = SlashAction.REDIRECT_REMOVE
elif host.startswith("blog."):
# Blog subdomain: add trailing slashes
self.slash_action = SlashAction.REDIRECT_ADD
else:
# Main domain: ignore slashes
self.slash_action = SlashAction.IGNORE
return await super().__call__(request, response, call_next)
app.add_middleware(ConditionalSlashesMiddleware())Best Practices
- Choose one slash behavior: Be consistent across your application
- Use 301 for permanent changes: Better for SEO than 302
- Test with your routing: Ensure compatibility with your route definitions
- Consider API endpoints: May need different behavior for API vs pages
- Monitor redirects: Use appropriate redirect status codes
- File serving: Static files are automatically skipped from processing
Migration Guide
If you're migrating from inconsistent URLs:
- Audit your URLs: Check current URL patterns
- Choose a strategy: Decide on trailing slash behavior
- Implement gradually: Add middleware and monitor
- Update internal links: Ensure all internal links follow the new pattern
- Update documentation: Document the new URL conventions
Example Migration
python
# Phase 1: Log what would be changed
class AuditSlashesMiddleware(slashes.SlashesMiddleware):
async def __call__(self, request, response, call_next):
original_path = request.url.path
# Process normally but log changes
result = await super().__call__(request, response, call_next)
if hasattr(request, '_normalized_path'):
print(f"Would normalize: {original_path} → {request._normalized_path}")
return result
# Phase 2: Implement with redirects
app.add_middleware(slashes.Slashes(
slash_action=slashes.SlashAction.REDIRECT_REMOVE,
redirect_status_code=301
))Performance Considerations
- Minimal overhead: Only processes paths that need normalization
- Smart skipping: Avoids processing static files and complex URLs
- Efficient redirects: Uses appropriate HTTP status codes
- Cache-friendly: Redirects are cacheable by browsers and CDNs
Troubleshooting
Common Issues
Infinite redirects
- Check that your route definitions match your slash action
- Ensure middleware is added before routing middleware
Static files not loading
- Verify file extensions are being skipped
- Check static file serving configuration
API endpoints broken
- Consider using
SlashAction.IGNOREfor API routes - Use custom skip logic for specific endpoints
Built with ❤️ by the @nexios-labs community.
