Skip to content

Python Server Proxy

For more idioms see ../integrations/python.md.

Flask

python
import os, time, hmac, hashlib
from flask import Flask, Response
import requests

app = Flask(__name__)
KEY    = os.environ['MFD_SRV_KEY']
SECRET = os.environ['MFD_SRV_SECRET']
BASE   = 'https://tiles.mapsfordevs.com'

def sign(method, path):
    ts = str(int(time.time()))
    sig = hmac.new(SECRET.encode(), f'{method}\n{path}\n{ts}'.encode(), hashlib.sha256).hexdigest()
    return {'Authorization': f'Bearer {KEY}', 'X-MFD-Timestamp': ts, 'X-MFD-Signature': sig}

@app.get('/tiles/<int:z>/<int:x>/<int:y>.pbf')
def tile(z, x, y):
    path = f'/tiles/{z}/{x}/{y}.pbf'
    r = requests.get(f'{BASE}{path}', headers=sign('GET', path), stream=True, timeout=10)
    return Response(
        r.iter_content(8192),
        status=r.status_code,
        content_type='application/x-protobuf',
        headers={'Cache-Control': 'public, max-age=86400'}
    )

FastAPI (async)

python
import os, time, hmac, hashlib
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import httpx

app   = FastAPI()
KEY    = os.environ['MFD_SRV_KEY']
SECRET = os.environ['MFD_SRV_SECRET']
client = httpx.AsyncClient(base_url='https://tiles.mapsfordevs.com', timeout=10)

def sign(method: str, path: str) -> dict[str, str]:
    ts = str(int(time.time()))
    sig = hmac.new(SECRET.encode(), f'{method}\n{path}\n{ts}'.encode(), hashlib.sha256).hexdigest()
    return {'Authorization': f'Bearer {KEY}', 'X-MFD-Timestamp': ts, 'X-MFD-Signature': sig}

@app.get('/tiles/{z}/{x}/{y}.pbf')
async def tile(z: int, x: int, y: int):
    path = f'/tiles/{z}/{x}/{y}.pbf'
    r = await client.get(path, headers=sign('GET', path))
    return StreamingResponse(
        r.aiter_bytes(),
        status_code=r.status_code,
        media_type='application/x-protobuf',
        headers={'Cache-Control': 'public, max-age=86400'}
    )

Django

python
# views.py
import os, time, hmac, hashlib
from django.http import HttpResponse
from django.views.decorators.cache import cache_page
import requests

KEY    = os.environ['MFD_SRV_KEY']
SECRET = os.environ['MFD_SRV_SECRET']

def sign(method, path):
    ts = str(int(time.time()))
    sig = hmac.new(SECRET.encode(), f'{method}\n{path}\n{ts}'.encode(), hashlib.sha256).hexdigest()
    return {'Authorization': f'Bearer {KEY}', 'X-MFD-Timestamp': ts, 'X-MFD-Signature': sig}

@cache_page(60 * 60 * 24)
def tile(request, z, x, y):
    path = f'/tiles/{z}/{x}/{y}.pbf'
    r = requests.get(f'https://tiles.mapsfordevs.com{path}', headers=sign('GET', path), timeout=10)
    return HttpResponse(r.content, status=r.status_code, content_type='application/x-protobuf')

# urls.py
# path('tiles/<int:z>/<int:x>/<int:y>.pbf', views.tile)

Redis-backed tile cache

python
import redis
r_cache = redis.Redis(host='localhost', port=6379, decode_responses=False)

@app.get('/tiles/{z}/{x}/{y}.pbf')
async def tile(z: int, x: int, y: int):
    path = f'/tiles/{z}/{x}/{y}.pbf'
    cached = r_cache.get(path)
    if cached:
        return Response(cached, media_type='application/x-protobuf', headers={'X-Cache': 'HIT'})
    r = await client.get(path, headers=sign('GET', path))
    if r.status_code == 200:
        r_cache.setex(path, 86400, r.content)
    return Response(r.content, status_code=r.status_code,
                    media_type='application/x-protobuf',
                    headers={'X-Cache': 'MISS'})

ASGI deployment

Use uvicorn with multiple workers + uvloop:

bash
uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 --loop uvloop