Compare commits

..

No commits in common. "5f4e1b7207f2741cb586a38f11323b3f7fd1bfad" and "189ba1d18c73730539961274428b17b92331e289" have entirely different histories.

2 changed files with 29 additions and 50 deletions

66
app.py
View File

@ -5,7 +5,6 @@ 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
@ -1791,39 +1790,25 @@ 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_query: if target_server_header:
try: try:
parsed_url = urlparse(target_server_query) parsed_url = urlparse(target_server_header)
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 proxy-target query parameter") raise ValueError("Invalid URL format in X-Target-FHIR-Server header")
final_base_url = target_server_query.rstrip('/') final_base_url = target_server_header.rstrip('/')
is_custom_target = True is_custom_target = True
logger.info(f"Proxy target identified from query parameter: {final_base_url}") logger.info(f"Proxy target identified from header: {final_base_url}")
except ValueError as e: except ValueError as e:
logger.warning(f"Invalid URL in proxy-target query parameter: '{target_server_query}'. Falling back. Error: {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('/') final_base_url = current_app.config['HAPI_FHIR_URL'].rstrip('/')
logger.debug(f"Falling back to default local HAPI due to invalid query: {final_base_url}") logger.debug(f"Falling back to default local HAPI due to invalid header: {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 info found, proxying to default local HAPI: {final_base_url}") logger.debug(f"No target header 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
@ -1905,6 +1890,7 @@ 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 ---
@ -2416,21 +2402,19 @@ 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():
# Push the application context manually for the generator's lifetime try:
with app.app_context(): yield from services.retrieve_bundles(
try: fhir_server_url=fhir_server_url,
yield from services.retrieve_bundles( resources=resources,
fhir_server_url=fhir_server_url, output_zip=output_zip,
resources=resources, validate_references=validate_references,
output_zip=output_zip, fetch_reference_bundles=fetch_reference_bundles,
validate_references=validate_references, auth_type=auth_type,
fetch_reference_bundles=fetch_reference_bundles, auth_token=auth_token
auth_type=auth_type, )
auth_token=auth_token except Exception as e:
) logger.error(f"Error in retrieve_bundles: {e}", exc_info=True)
except Exception as e: yield json.dumps({"type": "error", "message": f"Unexpected error: {str(e)}"}) + "\n"
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, urljoin from urllib.parse import quote, urlparse
from types import SimpleNamespace from types import SimpleNamespace
import datetime import datetime
import subprocess import subprocess
@ -4586,20 +4586,16 @@ 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:
# NEW: Add the custom URL as a query parameter for the proxy to use. headers['X-Target-FHIR-Server'] = fhir_server_url.rstrip('/')
# 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 proxy-target: {fhir_server_url}") logger.debug(f"Will use proxy with X-Target-FHIR-Server: {headers['X-Target-FHIR-Server']}")
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")
@ -4607,8 +4603,7 @@ 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: