Compare commits

...

3 Commits

Author SHA1 Message Date
5f4e1b7207 Update services.py 2025-08-12 21:29:13 +10:00
9729004982 Hotfix - proxy pathing 2025-08-12 20:48:52 +10:00
5a6f2072d7 Hotifx
fix context relative path.
2025-08-12 19:28:47 +10:00
2 changed files with 50 additions and 29 deletions

66
app.py
View File

@ -5,6 +5,7 @@ CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
# Introduce app_dir variable that can be overridden by environment # Introduce app_dir variable that can be overridden by environment
app_dir = os.environ.get('APP_DIR', CURRENT_DIR) app_dir = os.environ.get('APP_DIR', CURRENT_DIR)
sys.path.append(CURRENT_DIR) sys.path.append(CURRENT_DIR)
#sys.path.append(os.path.abspath(os.path.dirname(__file__)))
import datetime import datetime
import shutil import shutil
import queue import queue
@ -1790,25 +1791,39 @@ def proxy_hapi(subpath):
logger.debug(f"Proxy received request for path: '/fhir/{subpath}', cleaned subpath: '{clean_subpath}'") logger.debug(f"Proxy received request for path: '/fhir/{subpath}', cleaned subpath: '{clean_subpath}'")
# Determine the target FHIR server base URL # Determine the target FHIR server base URL
# NEW: Check for proxy-target query parameter first
target_server_query = request.args.get('proxy-target')
target_server_header = request.headers.get('X-Target-FHIR-Server') target_server_header = request.headers.get('X-Target-FHIR-Server')
final_base_url = None final_base_url = None
is_custom_target = False is_custom_target = False
if target_server_header: if target_server_query:
try: try:
parsed_url = urlparse(target_server_header) parsed_url = urlparse(target_server_query)
if not parsed_url.scheme or not parsed_url.netloc: if not parsed_url.scheme or not parsed_url.netloc:
raise ValueError("Invalid URL format in X-Target-FHIR-Server header") raise ValueError("Invalid URL format in proxy-target query parameter")
final_base_url = target_server_header.rstrip('/') final_base_url = target_server_query.rstrip('/')
is_custom_target = True is_custom_target = True
logger.info(f"Proxy target identified from header: {final_base_url}") logger.info(f"Proxy target identified from query parameter: {final_base_url}")
except ValueError as e: except ValueError as e:
logger.warning(f"Invalid URL in X-Target-FHIR-Server header: '{target_server_header}'. Falling back. Error: {e}") logger.warning(f"Invalid URL in proxy-target query parameter: '{target_server_query}'. Falling back. Error: {e}")
final_base_url = current_app.config['HAPI_FHIR_URL'].rstrip('/') final_base_url = current_app.config['HAPI_FHIR_URL'].rstrip('/')
logger.debug(f"Falling back to default local HAPI due to invalid header: {final_base_url}") logger.debug(f"Falling back to default local HAPI due to invalid query: {final_base_url}")
elif target_server_header:
try:
parsed_url = urlparse(target_server_header)
if not parsed_url.scheme or not parsed_url.netloc:
raise ValueError("Invalid URL format in X-Target-FHIR-Server header")
final_base_url = target_server_header.rstrip('/')
is_custom_target = True
logger.info(f"Proxy target identified from header: {final_base_url}")
except ValueError as e:
logger.warning(f"Invalid URL in X-Target-FHIR-Server header: '{target_server_header}'. Falling back. Error: {e}")
final_base_url = current_app.config['HAPI_FHIR_URL'].rstrip('/')
logger.debug(f"Falling back to default local HAPI due to invalid header: {final_base_url}")
else: else:
final_base_url = current_app.config['HAPI_FHIR_URL'].rstrip('/') final_base_url = current_app.config['HAPI_FHIR_URL'].rstrip('/')
logger.debug(f"No target header found, proxying to default local HAPI: {final_base_url}") logger.debug(f"No target info found, proxying to default local HAPI: {final_base_url}")
# Construct the final URL for the target server request # Construct the final URL for the target server request
# Append the cleaned subpath only if it's not empty # Append the cleaned subpath only if it's not empty
@ -1890,7 +1905,6 @@ def proxy_hapi(subpath):
except Exception as e: except Exception as e:
logger.error(f"Unexpected proxy error for {final_url}: {str(e)}", exc_info=True) logger.error(f"Unexpected proxy error for {final_url}: {str(e)}", exc_info=True)
return jsonify({'resourceType': 'OperationOutcome', 'issue': [{'severity': 'error', 'code': 'exception', 'diagnostics': 'An unexpected error occurred within the FHIR proxy.', 'details': {'text': str(e)}}]}), 500 return jsonify({'resourceType': 'OperationOutcome', 'issue': [{'severity': 'error', 'code': 'exception', 'diagnostics': 'An unexpected error occurred within the FHIR proxy.', 'details': {'text': str(e)}}]}), 500
# --- End of corrected proxy_hapi function --- # --- End of corrected proxy_hapi function ---
@ -2402,19 +2416,21 @@ def api_retrieve_bundles():
output_zip = os.path.join(temp_dir, zip_filename) output_zip = os.path.join(temp_dir, zip_filename)
def generate(): def generate():
try: # Push the application context manually for the generator's lifetime
yield from services.retrieve_bundles( with app.app_context():
fhir_server_url=fhir_server_url, try:
resources=resources, yield from services.retrieve_bundles(
output_zip=output_zip, fhir_server_url=fhir_server_url,
validate_references=validate_references, resources=resources,
fetch_reference_bundles=fetch_reference_bundles, output_zip=output_zip,
auth_type=auth_type, validate_references=validate_references,
auth_token=auth_token fetch_reference_bundles=fetch_reference_bundles,
) auth_type=auth_type,
except Exception as e: auth_token=auth_token
logger.error(f"Error in retrieve_bundles: {e}", exc_info=True) )
yield json.dumps({"type": "error", "message": f"Unexpected error: {str(e)}"}) + "\n" except Exception as e:
logger.error(f"Error in retrieve_bundles: {e}", exc_info=True)
yield json.dumps({"type": "error", "message": f"Unexpected error: {str(e)}"}) + "\n"
response = Response(generate(), mimetype='application/x-ndjson') response = Response(generate(), mimetype='application/x-ndjson')
response.headers['X-Zip-Path'] = os.path.join('/tmp', zip_filename) response.headers['X-Zip-Path'] = os.path.join('/tmp', zip_filename)

View File

@ -11,7 +11,7 @@ from flask import current_app, Blueprint, request, jsonify
from fhirpathpy import evaluate from fhirpathpy import evaluate
from collections import defaultdict, deque from collections import defaultdict, deque
from pathlib import Path from pathlib import Path
from urllib.parse import quote, urlparse from urllib.parse import quote, urlparse, urljoin
from types import SimpleNamespace from types import SimpleNamespace
import datetime import datetime
import subprocess import subprocess
@ -4586,16 +4586,20 @@ def retrieve_bundles(fhir_server_url, resources, output_zip, validate_references
# Determine Base URL and Headers for Proxy # Determine Base URL and Headers for Proxy
base_proxy_url = f"{current_app.config['APP_BASE_URL'].rstrip('/')}/fhir" base_proxy_url = f"{current_app.config['APP_BASE_URL'].rstrip('/')}/fhir"
headers = {'Accept': 'application/fhir+json, application/fhir+xml;q=0.9, */*;q=0.8'} headers = {'Accept': 'application/fhir+json, application/fhir+xml;q=0.9, */*;q=0.8'}
is_custom_url = fhir_server_url != '/fhir' and fhir_server_url is not None and fhir_server_url.startswith('http') is_custom_url = fhir_server_url != '/fhir' and fhir_server_url is not None and fhir_server_url.startswith('http')
if is_custom_url: if is_custom_url:
headers['X-Target-FHIR-Server'] = fhir_server_url.rstrip('/') # NEW: Add the custom URL as a query parameter for the proxy to use.
# This bypasses issues where reverse proxies might strip custom headers.
base_proxy_url = f"{base_proxy_url}?proxy-target={fhir_server_url.rstrip('/')}"
if auth_type in ['bearer', 'basic'] and auth_token: if auth_type in ['bearer', 'basic'] and auth_token:
auth_display = 'Basic <redacted>' if auth_type == 'basic' else (auth_token[:10] + '...' if len(auth_token) > 10 else auth_token) auth_display = 'Basic <redacted>' if auth_type == 'basic' else (auth_token[:10] + '...' if len(auth_token) > 10 else auth_token)
yield json.dumps({"type": "info", "message": f"Using {auth_type} auth with header: Authorization: {auth_display}"}) + "\n" yield json.dumps({"type": "info", "message": f"Using {auth_type} auth with header: Authorization: {auth_display}"}) + "\n"
headers['Authorization'] = auth_token headers['Authorization'] = auth_token
else: else:
yield json.dumps({"type": "info", "message": "Using no authentication for custom URL"}) + "\n" yield json.dumps({"type": "info", "message": "Using no authentication for custom URL"}) + "\n"
logger.debug(f"Will use proxy with X-Target-FHIR-Server: {headers['X-Target-FHIR-Server']}") logger.debug(f"Will use proxy with proxy-target: {fhir_server_url}")
else: else:
yield json.dumps({"type": "info", "message": "Using no authentication for local HAPI server"}) + "\n" yield json.dumps({"type": "info", "message": "Using no authentication for local HAPI server"}) + "\n"
logger.debug("Will use proxy targeting local HAPI server") logger.debug("Will use proxy targeting local HAPI server")
@ -4603,7 +4607,8 @@ def retrieve_bundles(fhir_server_url, resources, output_zip, validate_references
# Fetch Initial Bundles # Fetch Initial Bundles
initial_bundle_files = [] initial_bundle_files = []
for resource_type in resources: for resource_type in resources:
url = f"{base_proxy_url}/{quote(resource_type)}" #url = f"{base_proxy_url}/{quote(resource_type)}"
url = urljoin(base_proxy_url, quote(resource_type))
yield json.dumps({"type": "progress", "message": f"Fetching bundle for {resource_type} via proxy..."}) + "\n" yield json.dumps({"type": "progress", "message": f"Fetching bundle for {resource_type} via proxy..."}) + "\n"
logger.debug(f"Sending GET request to proxy {url} with headers: {json.dumps(headers)}") logger.debug(f"Sending GET request to proxy {url} with headers: {json.dumps(headers)}")
try: try: