Skip to content

Instantly share code, notes, and snippets.

@simonw
Created March 26, 2023 16:17
Show Gist options
  • Select an option

  • Save simonw/d95809e5a5c1441281cd5585270ab834 to your computer and use it in GitHub Desktop.

Select an option

Save simonw/d95809e5a5c1441281cd5585270ab834 to your computer and use it in GitHub Desktop.

Starlette CORS Forwarding App

2023-03-26 04:10:49

user: Write a web app in python Starlette which has CORS enabled - including for the auth header - and forwards all incoming requests to any path to another server host specified in an environment variable


assistant: To build a web app in Python using the Starlette framework with CORS enabled, you need to follow these steps:

  1. Install the required packages: Starlette, HTTPX, and python-dotenv
  2. Create a .env file to store the environment variable for the target server
  3. Write the main application script

Here's a step-by-step guide:

  1. Install the required packages:

Create a virtual environment (optional, but recommended) and then run:

pip install starlette httpx python-dotenv uvicorn aiohttp
  1. Create a .env file to store the environment variable for the target server:

Create a .env file in the same directory as your main application script, and add the following line:

TARGET_SERVER_HOST=http://your-target-server.com

Replace http://your-target-server.com with the actual target server host.

  1. Write the main application script:

Create a new Python file, main.py, and add the following code:

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.routing import Route
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.config import Config
import httpx
from aiohttp.client_exceptions import ClientConnectorError
import json

config = Config(".env")
TARGET_SERVER_HOST = config("TARGET_SERVER_HOST")

middleware = [
    Middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )
]

app = Starlette(middleware=middleware)


async def forward_request(request: Request):
    try:
        async with httpx.AsyncClient() as client:
            target_url = f"{TARGET_SERVER_HOST}{request.url.path}"
            method = request.method
            headers = dict(request.headers)
            if "host" in headers:
                del headers["host"]
            if request.method in ["POST", "PUT", "PATCH"]:
                request_body = await request.json()
            else:
                request_body = None

            response = await client.request(
                method, target_url, headers=headers, json=request_body
            )
            return JSONResponse(response.json(), status_code=response.status_code)
    except ClientConnectorError:
        return JSONResponse({"error": "Target server connection error."}, status_code=503)
    except Exception as e:
        return JSONResponse({"error": str(e)}, status_code=500)


app.add_route("/{path:path}", forward_request, methods=["GET", "POST", "PUT", "PATCH", "DELETE"])

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8000)

To run the app, execute the following command:

uvicorn main:app --reload

This will start the Starlette server with CORS enabled, and it will forward all incoming requests to the server host specified in the .env file.


user: Rewrite that to use os.environ


assistant: Here's the updated version of main.py using os.environ instead of python-dotenv:

import os
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.routing import Route
from starlette.requests import Request
from starlette.responses import JSONResponse
import httpx
from aiohttp.client_exceptions import ClientConnectorError
import json

TARGET_SERVER_HOST = os.environ.get("TARGET_SERVER_HOST")

middleware = [
    Middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )
]

app = Starlette(middleware=middleware)

async def forward_request(request: Request):
    try:
        async with httpx.AsyncClient() as client:
            target_url = f"{TARGET_SERVER_HOST}{request.url.path}"
            method = request.method
            headers = dict(request.headers)
            if "host" in headers:
                del headers["host"]
            if request.method in ["POST", "PUT", "PATCH"]:
                request_body = await request.json()
            else:
                request_body = None

            response = await client.request(
                method, target_url, headers=headers, json=request_body
            )
            return JSONResponse(response.json(), status_code=response.status_code)
    except ClientConnectorError:
        return JSONResponse({"error": "Target server connection error."}, status_code=503)
    except Exception as e:
        return JSONResponse({"error": str(e)}, status_code=500)

app.add_route("/{path:path}", forward_request, methods=["GET", "POST", "PUT", "PATCH", "DELETE"])

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8000)

Before running the app, you need to set the environment variable TARGET_SERVER_HOST in your terminal:

For Linux/Mac:

export TARGET_SERVER_HOST=http://your-target-server.com

For Windows:

set TARGET_SERVER_HOST=http://your-target-server.com

Replace http://your-target-server.com with the actual target server host. Then run the app with:

uvicorn main:app --reload

The server will start with CORS enabled and forward all incoming requests to the server host specified in the environment variable.


user: Catch httpx.HTTPError as the only exception you care about


assistant: Here's the updated version of main.py with only httpx.HTTPError as the exception we care about:

import os
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.routing import Route
from starlette.requests import Request
from starlette.responses import JSONResponse
import httpx

TARGET_SERVER_HOST = os.environ.get("TARGET_SERVER_HOST")

middleware = [
    Middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )
]

app = Starlette(middleware=middleware)

async def forward_request(request: Request):
    try:
        async with httpx.AsyncClient() as client:
            target_url = f"{TARGET_SERVER_HOST}{request.url.path}"
            method = request.method
            headers = dict(request.headers)
            if "host" in headers:
                del headers["host"]
            if request.method in ["POST", "PUT", "PATCH"]:
                request_body = await request.json()
            else:
                request_body = None

            response = await client.request(
                method, target_url, headers=headers, json=request_body
            )
            return JSONResponse(response.json(), status_code=response.status_code)
    except httpx.HTTPError as e:
        return JSONResponse({"error": str(e)}, status_code=500)

app.add_route("/{path:path}", forward_request, methods=["GET", "POST", "PUT", "PATCH", "DELETE"])

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8000)

Now, the application will only catch httpx.HTTPError exceptions and return a JSON response with the error message and a 500 status code.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment