A lightweight Flask-like framework with DNS interception and traffic proxy capabilities
Rewroute is a powerful Python framework that combines the simplicity of Flask with advanced traffic interception capabilities. It can intercept all HTTP traffic, handle registered domains locally, and forward everything else to external servers.
- Quick Start
- Installation
- Core Concepts
- API Reference
- Advanced Features
- Examples
- Configuration
- Security Considerations
from rewroute import create_app, render_template, redirect
# Create a new application
app = create_app('myapp')
# Define routes
@app.get('/')
def home(request):
return render_template('index.html', title="Welcome to Rewroute")
@app.get('/user/<user_id>')
def user_profile(request):
user_id = request.url_params['user_id']
return {'user_id': user_id, 'name': f'User {user_id}'}
@app.post('/api/data')
def handle_data(request):
if request.json_data:
return {'received': request.json_data, 'status': 'success'}
return {'error': 'No JSON data'}, 400
# Run the application on a domain
# This will intercept all traffic to 'mysite.local'
app.run('mysite.local')Required:
pip install requestsOptional (for enhanced features):
pip install rich jinja2rich: Beautiful console output and loggingjinja2: Template engine support
- Python 3.6+
- Administrator/root privileges (for DNS interception and port 80/443 binding)
- Supported platforms: Windows, macOS, Linux
Rewroute works by intercepting all HTTP traffic on your system:
- DNS Management: Modifies system DNS or hosts file to route traffic
- Proxy Server: Runs local HTTP/HTTPS servers on ports 80/443
- Smart Routing: Registered domains β local apps, everything else β internet
# Create application
app = create_app('app_name', template_folder='templates', static_folder='static')
# Register routes
@app.route('/path', methods=['GET', 'POST'])
def handler(request):
return response
# Bind to domain and start
app.run('domain.local')- HTTP request comes in
- Rewroute checks if domain is registered
- If registered: routes to local app
- If not registered: proxies to external server
- Response sent back to client
Creates a new Rewroute application.
Parameters:
name(str): Application nametemplate_folder(str): Directory for templatesstatic_folder(str): Directory for static files
Returns: RewrouteFlask instance
Register a route handler.
Parameters:
path(str): URL path pattern (supports parameters like/user/<id>)methods(list): HTTP methods (default:['GET'])
@app.get(path) # GET only
@app.post(path) # POST only
@app.put(path) # PUT only
@app.delete(path) # DELETE only
@app.patch(path) # PATCH onlyThe request object passed to route handlers contains:
class RewrouteRequest:
method: str # HTTP method
path: str # URL path
headers: Dict[str, str] # HTTP headers
query_params: Dict[str, list] # Query parameters
body: bytes # Request body
host: str # Host header
json_data: dict # Parsed JSON (if applicable)
url_params: Dict[str, str] # URL parameters from pathExample:
@app.get('/user/<user_id>')
def user_handler(request):
user_id = request.url_params['user_id'] # From URL
name = request.query_params.get('name', [''])[0] # From ?name=value
return f"User {user_id}, Name: {name}"Explicit response object.
Parameters:
data: Response data (str, dict, list, bytes)status_code(int): HTTP status codeheaders(dict): HTTP headersmimetype(str): Content type
# String response
return "Hello World"
# JSON response
return {"key": "value"}
# Tuple (data, status_code)
return "Not Found", 404
# RewrouteResponse object
return RewrouteResponse("Custom", 201, {"X-Custom": "header"})Render Jinja2 templates.
Requirements: pip install jinja2
@app.get('/')
def home(request):
return render_template('index.html',
title="My Site",
users=['Alice', 'Bob'])Template (templates/index.html):
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ title }}</h1>
<ul>
{% for user in users %}
<li>{{ user }}</li>
{% endfor %}
</ul>
</body>
</html>Send a file as response.
@app.get('/download/<filename>')
def download(request):
filename = request.url_params['filename']
return send_file(f'files/{filename}', as_attachment=True)Safely serve files from a directory.
@app.get('/static/<filename>')
def static_files(request):
filename = request.url_params['filename']
return send_from_directory('static', filename)Create redirect responses.
@app.get('/old-page')
def redirect_old(request):
return redirect('/new-page', 301) # Permanent redirectStart the application on a domain.
Parameters:
domain(str): Domain to intercept (e.g., 'mysite.local')start_interceptor(bool): Start traffic interceptorhttp_port(int): HTTP port (default: 80)setup_dns(bool): Modify DNS/hosts file
Manually start the traffic interceptor.
Stop traffic interception and restore DNS.
Get list of registered domains.
Add domain to hosts file.
Automatically configure domain interception.
Capture dynamic parts of URLs:
@app.get('/user/<user_id>')
@app.get('/post/<int:post_id>') # Note: int converter not implemented
@app.get('/file/<path:filename>') # Note: path converter not implemented
def handler(request):
# Access via request.url_params
user_id = request.url_params['user_id']
return f"User: {user_id}"# Blog app
blog = create_app('blog')
@blog.get('/')
def blog_home(request):
return "Welcome to the blog!"
blog.run('blog.local', start_interceptor=False)
# Admin app
admin = create_app('admin')
@admin.get('/')
def admin_home(request):
return render_template('admin.html')
admin.run('admin.local', start_interceptor=False)
# Start interceptor once for all apps
start_interceptor()Static files are automatically served from the static_folder:
project/
βββ app.py
βββ static/
β βββ css/
β β βββ style.css
β βββ js/
β β βββ app.js
β βββ images/
β βββ logo.png
βββ templates/
βββ index.html
Access via: http://domain.local/static/css/style.css
@app.post('/api/users')
def create_user(request):
if not request.json_data:
return {'error': 'JSON required'}, 400
user_data = request.json_data
# Process user creation
return {
'id': 123,
'name': user_data.get('name'),
'status': 'created'
}, 201
@app.get('/api/users/<user_id>')
def get_user(request):
user_id = request.url_params['user_id']
# Fetch user data
return {
'id': user_id,
'name': 'John Doe',
'email': 'john@example.com'
}@app.get('/error-test')
def error_handler(request):
try:
# Some operation that might fail
result = risky_operation()
return {'result': result}
except Exception as e:
return {'error': str(e)}, 500@app.get('/api/data')
def api_data(request):
data = {'message': 'Hello API'}
headers = {
'X-API-Version': '1.0',
'Access-Control-Allow-Origin': '*'
}
return RewrouteResponse(data, 200, headers)from rewroute import create_app, render_template, redirect, send_from_directory
# Create blog application
blog = create_app('blog', template_folder='templates', static_folder='static')
# Sample data
posts = [
{'id': 1, 'title': 'First Post', 'content': 'Hello World!'},
{'id': 2, 'title': 'Second Post', 'content': 'More content here.'}
]
@blog.get('/')
def home(request):
return render_template('index.html', posts=posts)
@blog.get('/post/<post_id>')
def view_post(request):
post_id = int(request.url_params['post_id'])
post = next((p for p in posts if p['id'] == post_id), None)
if not post:
return render_template('404.html'), 404
return render_template('post.html', post=post)
@blog.post('/post/<post_id>/comment')
def add_comment(request):
post_id = int(request.url_params['post_id'])
comment = request.json_data.get('comment', '')
# Save comment logic here
return {'success': True, 'message': 'Comment added'}
@blog.get('/admin')
def admin(request):
return render_template('admin.html', posts=posts)
# Start the blog
blog.run('myblog.local')from rewroute import create_app
api = create_app('api')
# In-memory data store
users = {}
next_id = 1
@api.get('/api/users')
def list_users(request):
return {'users': list(users.values())}
@api.post('/api/users')
def create_user(request):
global next_id
if not request.json_data:
return {'error': 'JSON data required'}, 400
user = {
'id': next_id,
'name': request.json_data.get('name'),
'email': request.json_data.get('email')
}
users[next_id] = user
next_id += 1
return user, 201
@api.get('/api/users/<user_id>')
def get_user(request):
user_id = int(request.url_params['user_id'])
user = users.get(user_id)
if not user:
return {'error': 'User not found'}, 404
return user
@api.put('/api/users/<user_id>')
def update_user(request):
user_id = int(request.url_params['user_id'])
if user_id not in users:
return {'error': 'User not found'}, 404
if request.json_data:
users[user_id].update(request.json_data)
return users[user_id]
@api.delete('/api/users/<user_id>')
def delete_user(request):
user_id = int(request.url_params['user_id'])
if user_id not in users:
return {'error': 'User not found'}, 404
del users[user_id]
return {'message': 'User deleted'}
# Start API server
api.run('api.local')from rewroute import create_app, send_file
import os
app = create_app('fileserver')
UPLOAD_DIR = 'uploads'
os.makedirs(UPLOAD_DIR, exist_ok=True)
@app.post('/upload')
def upload_file(request):
# Note: This is simplified - you'd need proper multipart handling
filename = request.headers.get('X-Filename', 'uploaded_file')
filepath = os.path.join(UPLOAD_DIR, filename)
with open(filepath, 'wb') as f:
f.write(request.body)
return {'message': 'File uploaded', 'filename': filename}
@app.get('/download/<filename>')
def download_file(request):
filename = request.url_params['filename']
filepath = os.path.join(UPLOAD_DIR, filename)
if not os.path.exists(filepath):
return {'error': 'File not found'}, 404
return send_file(filepath, as_attachment=True)
@app.get('/files')
def list_files(request):
files = os.listdir(UPLOAD_DIR)
return {'files': files}
app.run('files.local')import os
# Configuration
DEBUG = os.getenv('DEBUG', 'false').lower() == 'true'
HOST = os.getenv('HOST', 'localhost')
PORT = int(os.getenv('PORT', 80))
app = create_app('myapp')
if DEBUG:
# Add debug routes
@app.get('/debug')
def debug_info(request):
return {
'headers': dict(request.headers),
'query_params': request.query_params,
'method': request.method
}
app.run(HOST, http_port=PORT)import logging
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s | %(name)s | %(levelname)s | %(message)s'
)
logger = logging.getLogger('myapp')
@app.get('/log-test')
def log_test(request):
logger.info(f"Request from {request.headers.get('User-Agent')}")
return "Check the logs!"- Path Traversal Protection:
send_from_directory()prevents access outside designated directories - Request Validation: Built-in JSON parsing with error handling
- Header Sanitization: Automatic hop-by-hop header removal in proxy mode
- Input Validation:
@app.post('/api/user')
def create_user(request):
data = request.json_data
# Validate input
if not data or 'name' not in data:
return {'error': 'Name required'}, 400
name = data['name'].strip()
if len(name) < 2:
return {'error': 'Name too short'}, 400
# Process validated data
return {'user': name}-
HTTPS in Production:
- Configure SSL certificates for HTTPS support
- Use reverse proxy (nginx) for production deployments
-
Access Control:
def require_auth(handler):
def wrapper(request):
token = request.headers.get('Authorization')
if not token or not validate_token(token):
return {'error': 'Unauthorized'}, 401
return handler(request)
return wrapper
@app.get('/admin/users')
@require_auth
def admin_users(request):
return {'users': get_all_users()}- Rate Limiting: Implement custom rate limiting logic in handlers
Rewroute modifies system DNS settings and requires administrator privileges:
- Hosts File: Adds entries to redirect domains
- DNS Settings: May modify system DNS configuration
- Port Binding: Requires root/admin for ports 80/443
Always run stop_interceptor() to restore original settings.
When proxying external traffic:
- SSL verification is disabled for simplicity
- Headers are forwarded (review for sensitive data)
- Timeouts prevent hanging connections
-
Permission Denied on Port 80/443
# Linux/macOS sudo python app.py # Windows (Run as Administrator) python app.py
-
Rich Console Not Working
pip install rich
-
Templates Not Rendering
pip install jinja2 # Ensure templates/ directory exists -
Domain Not Intercepting
- Check hosts file was modified
- Verify no other services on port 80
- Try
add_hosts_entry('domain.local')manually
-
Static Files 404
- Ensure static/ directory exists
- Check file permissions
- Verify path in browser
# Enable verbose logging
import logging
logging.getLogger().setLevel(logging.DEBUG)
# Add debug route
@app.get('/debug/<path:info>')
def debug_handler(request):
return {
'path': request.path,
'url_params': request.url_params,
'method': request.method,
'headers': dict(request.headers),
'query': request.query_params
}This framework is extensible and welcomes contributions. Key areas for enhancement:
- HTTPS/SSL certificate management
- WebSocket support
- Database integration helpers
- Authentication middleware
- Caching mechanisms
- Advanced routing (regex patterns)
- Request/response middleware
- Session management
Rewroute Framework - Powerful local development with global traffic interception capabilities.