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:
- Install the required packages: Starlette, HTTPX, and python-dotenv
- Create a
.envfile to store the environment variable for the target server - Write the main application script
Here's a step-by-step guide:
- Install the required packages:
Create a virtual environment (optional, but recommended) and then run:
pip install starlette httpx python-dotenv uvicorn aiohttp- Create a
.envfile 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.
- 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 --reloadThis 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.comFor Windows:
set TARGET_SERVER_HOST=http://your-target-server.comReplace http://your-target-server.com with the actual target server host. Then run the app with:
uvicorn main:app --reloadThe 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.