-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implementation of reverse proxies for `varfish-docker-compose-n…
- Loading branch information
Showing
8 changed files
with
728 additions
and
314 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,92 @@ | ||
import os | ||
import sys | ||
|
||
from fastapi import FastAPI | ||
import httpx | ||
from dotenv import load_dotenv | ||
from fastapi import FastAPI, Query | ||
from fastapi.middleware.cors import CORSMiddleware | ||
from fastapi.staticfiles import StaticFiles | ||
from starlette.responses import RedirectResponse | ||
from starlette.background import BackgroundTask | ||
from starlette.requests import Request | ||
from starlette.responses import JSONResponse, RedirectResponse, Response, StreamingResponse | ||
|
||
# Load environment | ||
env = os.environ | ||
load_dotenv() | ||
|
||
#: Path to frontend build, if any. | ||
SERVE_FRONTEND = os.environ.get("REEV_SERVE_FRONTEND") | ||
SERVE_FRONTEND = env.get("REEV_SERVE_FRONTEND") | ||
#: Debug mode | ||
DEBUG = env.get("REEV_DEBUG", "false").lower() in ("true", "1") | ||
#: Prefix for the backend of annonars service | ||
BACKEND_PREFIX_ANNONARS = env.get("REEV_BACKEND_PREFIX_ANNONARS", "http://annonars") | ||
#: Prefix for the backend of mehari service | ||
BACKEND_PREFIX_MEHARI = env.get("REEV_BACKEND_PREFIX_MEHARI", "http://mehari") | ||
#: Prefix for the backend of viguno service | ||
BACKEND_PREFIX_VIGUNO = env.get("REEV_BACKEND_PREFIX_VIGUNO", "http://viguno") | ||
|
||
|
||
app = FastAPI() | ||
|
||
# Configure CORS settings | ||
origins = [ | ||
"http://localhost", # Update with the actual frontend URL | ||
"http://localhost:8081", # Update with the actual frontend URL | ||
] | ||
|
||
app.add_middleware( | ||
CORSMiddleware, | ||
allow_origins=origins, | ||
allow_credentials=True, | ||
allow_methods=["*"], | ||
allow_headers=["*"], | ||
) | ||
|
||
# Reverse proxy implementation | ||
client = httpx.AsyncClient() | ||
|
||
|
||
async def reverse_proxy(request: Request) -> Response: | ||
"""Implement reverse proxy for backend services.""" | ||
url = request.url | ||
backend_url = None | ||
|
||
if url.path.startswith("/proxy/annonars"): | ||
backend_url = BACKEND_PREFIX_ANNONARS + url.path.replace("/proxy/annonars", "/annos") | ||
elif url.path.startswith("/proxy/mehari"): | ||
backend_url = BACKEND_PREFIX_MEHARI + url.path.replace("/proxy/mehari", "") | ||
elif url.path.startswith("/proxy/viguno"): | ||
backend_url = BACKEND_PREFIX_VIGUNO + url.path.replace("/proxy/viguno", "") | ||
|
||
if backend_url: | ||
backend_url = backend_url + (f"?{url.query}" if url.query else "") | ||
backend_req = client.build_request( | ||
method=request.method, | ||
url=backend_url, | ||
headers=request.headers.raw, | ||
content=await request.body(), | ||
) | ||
backend_resp = await client.send(backend_req, stream=True) | ||
return StreamingResponse( | ||
backend_resp.aiter_raw(), | ||
status_code=backend_resp.status_code, | ||
headers=backend_resp.headers, | ||
background=BackgroundTask(backend_resp.aclose), | ||
) | ||
else: | ||
return Response(status_code=404, content="Reverse proxy route not found") | ||
|
||
|
||
@app.get("/api/hello") | ||
def read_root(): | ||
return {"Hello": "World"} | ||
# Register reverse proxy route | ||
app.add_route("/proxy/{path:path}", reverse_proxy, methods=["GET", "POST"]) | ||
|
||
|
||
if SERVE_FRONTEND: | ||
# Routes | ||
if SERVE_FRONTEND: # pragma: no cover | ||
print(f"SERVE_FRONTEND = {SERVE_FRONTEND}", file=sys.stderr) | ||
app.mount("/ui", StaticFiles(directory=SERVE_FRONTEND), name="app") | ||
|
||
@app.get("/") | ||
async def redirect(): | ||
response = RedirectResponse(url=f"/ui/index.html") | ||
response = RedirectResponse(url="/ui/index.html") | ||
return response |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import typing | ||
|
||
import pytest | ||
from app import main | ||
from requests_mock import Mocker | ||
from starlette.testclient import TestClient | ||
|
||
#: Host name to use for the mocked backend. | ||
MOCKED_BACKEND_HOST = "mocked-backend" | ||
|
||
#: A "token" to be used in test URLs, does not carry a meaning as it is mocked. | ||
MOCKED_URL_TOKEN = "xXTeStXxx" | ||
|
||
#: FastAPI/startlette test client. | ||
client = TestClient(main.app) | ||
|
||
|
||
@pytest.fixture | ||
def non_mocked_hosts() -> typing.List[str]: | ||
"""List of hosts that should not be mocked. | ||
We read the host from ``client``. | ||
""" | ||
return [client._base_url.host] | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_proxy_annonars(monkeypatch, httpx_mock): | ||
"""Test proxying to annonars backend.""" | ||
monkeypatch.setattr(main, "BACKEND_PREFIX_ANNONARS", f"http://{MOCKED_BACKEND_HOST}") | ||
httpx_mock.add_response( | ||
url=f"http://{MOCKED_BACKEND_HOST}/annos/{MOCKED_URL_TOKEN}", | ||
method="GET", | ||
text="Mocked response", | ||
) | ||
|
||
response = client.get(f"/proxy/annonars/{MOCKED_URL_TOKEN}") | ||
assert response.status_code == 200 | ||
assert response.text == "Mocked response" | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_proxy_mehari(monkeypatch, httpx_mock): | ||
"""Test proxying to mehari backend.""" | ||
monkeypatch.setattr(main, "BACKEND_PREFIX_MEHARI", f"http://{MOCKED_BACKEND_HOST}") | ||
httpx_mock.add_response( | ||
url=f"http://{MOCKED_BACKEND_HOST}/{MOCKED_URL_TOKEN}", | ||
method="GET", | ||
text="Mocked response", | ||
) | ||
|
||
response = client.get(f"/proxy/mehari/{MOCKED_URL_TOKEN}") | ||
assert response.status_code == 200 | ||
assert response.text == "Mocked response" | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_proxy_viguno(monkeypatch, httpx_mock): | ||
"""Test proxying to viguno backend.""" | ||
monkeypatch.setattr(main, "BACKEND_PREFIX_VIGUNO", f"http://{MOCKED_BACKEND_HOST}") | ||
httpx_mock.add_response( | ||
url=f"http://{MOCKED_BACKEND_HOST}/{MOCKED_URL_TOKEN}", | ||
method="GET", | ||
text="Mocked response", | ||
) | ||
|
||
response = client.get(f"/proxy/viguno/{MOCKED_URL_TOKEN}") | ||
assert response.status_code == 200 | ||
assert response.text == "Mocked response" | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_invalid_proxy_route(monkeypatch, httpx_mock): | ||
"""Test invalid proxy route.""" | ||
response = client.get("/proxy/some-other-path") | ||
assert response.status_code == 404 | ||
assert response.text == "Reverse proxy route not found" |