From 0cdd52db94205b7f6024c27ebe72d12ed9ec0c95 Mon Sep 17 00:00:00 2001 From: mario Date: Fri, 11 Jul 2025 15:48:34 +0700 Subject: [PATCH] add http test | fix ctrl+c shutdown --- api-app/README.md | 201 +++++++++++++---------------------------- api-app/api_cleanup.py | 25 +++++ api-app/main.py | 17 +++- api-app/run.py | 11 +++ api-app/test.http | 12 +++ utils/cleanup.py | 11 ++- 6 files changed, 138 insertions(+), 139 deletions(-) create mode 100644 api-app/api_cleanup.py create mode 100644 api-app/test.http diff --git a/api-app/README.md b/api-app/README.md index a539fcd..a1913dc 100644 --- a/api-app/README.md +++ b/api-app/README.md @@ -1,172 +1,101 @@ # DICOM Migration REST API -This REST API provides an endpoint to trigger DICOM study migration for a specific Accession Number. +A simple REST API that triggers DICOM study migration for a specific Accession Number. -## Features - -- Migrate DICOM studies by Accession Number -- Token-based authentication -- Detailed JSON responses with operation status -- Proper logging and error handling -- Automatic cleanup of DICOM files after each request - -## Requirements - -- Python 3.9 or higher -- FastAPI -- Uvicorn -- Dependencies from the main DICOM migration project - -## Installation - -1. Install the required dependencies: - -```bash -cd api-app -pip install -r requirements.txt -``` - -## Configuration - -The API uses the same configuration settings as the main DICOM migration project. - -### Authentication - -The API uses token-based authentication. Set your token using the environment variable: - -```bash -export API_TOKEN=your-secure-token -``` - -If no token is provided, a default token will be used (not recommended for production). - -## Usage - -### Starting the API Server +## How to Run ```bash # Start the server on default port 8000 python run.py -# Specify a custom port +# Custom port python run.py --port 9000 -# Specify a custom token +# Custom token python run.py --token your-secure-token -# Enable auto-reload for development +# Development mode with auto-reload python run.py --reload ``` -### API Endpoints +## How to Use -#### 1. Migrate Study by Accession Number - -``` -POST /migrate/{accession_number} -``` - -**Authentication:** -- Bearer token in Authorization header - -**Response:** - -Successful migration: -```json -{ - "success": true, - "message": "Successfully migrated study for accession number: ABC12345", - "details": { - "study_uid": "1.2.826.0.1.3680043.9.7307.1.123456", - "his_integration_success": true - } -} -``` - -Failed migration: -```json -{ - "success": false, - "message": "Failed to process study: Study not found", - "details": null -} -``` - -## Example API Calls - -### Using curl +### Make a Request ```bash -# Migrate a study by accession number -curl -X POST "http://localhost:8000/migrate/ABC12345" \ - -H "Authorization: Bearer your-token" \ +# Using curl +curl -X POST "http://localhost:8000/migrate/R.20240401.0138" \ + -H "Authorization: Bearer token-his2-untuk-hit-api-migrasi-clarity" \ -H "Content-Type: application/json" ``` -### Using Python requests +### Response Example -```python -import requests - -url = "http://localhost:8000/migrate/ABC12345" -headers = { - "Authorization": "Bearer your-token", - "Content-Type": "application/json" +```json +{ + "success":true, + "message":"Berhasil migrasi file DICOM Accession Number: R.20240401.0138", + "details": + { + "accession_number":"R.20240401.0138", + "process_start_time":"2025-07-11 15:27:26", + "steps_completed":[ + "study_found","study_retrieved","study_sent","log_created","his_api_success","cleanup_success" + ], + "study_uid":"1.2.826.1.3680043.9.5282.150415.544835.202404010138", + "patient_id":"00544835", + "study_description":"A103 - Thorax PA", + "instances_retrieved":4, + "files_sent":2, + "total_files":2, + "c_store_success":true, + "series_count":4, + "his_api_status_code":200, + "his_integration_success":true, + "cleanup_success":true, + "process_end_time":"2025-07-11 15:27:46" + } } - -response = requests.post(url, headers=headers) -print(response.json()) ``` ## Logs -The API logs are stored in the `logs/api.log` file. The log includes information about: +- Main API logs: `logs/api.log` +- Process details: `logs/api_process.log` -- API startup and shutdown -- Migration requests -- Errors during migration process -- Integration with HIS results +## Simple Workflow -## Troubleshooting +1. API receives request with accession number +2. Finds study UID using C-FIND +3. Downloads DICOM files using C-GET +4. Uploads files to destination PACS using C-STORE +5. Updates HIS system via API call +6. Cleans up all temporary DICOM files +7. Returns success/failure response -1. **Authentication Error**: - - Make sure you're providing the correct token in the Authorization header - - Check that the token format is `Bearer your-token` +## Troubleshooting Tips -2. **Study Not Found**: - - Verify that the accession number is correct - - Check if the PACS server is reachable - - Verify that the study exists in the source PACS +- Check PACS connectivity in `config/settings.py` +- Verify the accession number exists in source PACS +- Ensure permissions for log directories +- Check both log files for detailed error messages +- Verify HIS API connectivity for end-to-end success -3. **Connection Error**: - - Check PACS connectivity - - Verify network settings in `config/settings.py` +## Quick Test -## Integrating with External Systems +1. Start the server: + ```bash + python run.py + ``` -You can call this API from other systems to trigger DICOM migrations for specific studies: +2. In another terminal, test the root endpoint: + ```bash + curl http://localhost:8000/ + ``` -1. Obtain the API token -2. Make a POST request to `/migrate/{accession_number}` -3. Handle the JSON response based on the `success` field +3. Test migration with a valid accession number: + ```bash + curl -X POST "http://localhost:8000/migrate/R.20240401.0138" \ + -H "Authorization: Bearer token-his2-untuk-hit-api-migrasi-clarity" + ``` -## Technical Details - -### DICOM File Cleanup - -The API implements a robust cleanup mechanism to ensure that DICOM files are always removed after processing: - -1. **Exit Handlers**: The application registers cleanup handlers via Python's `atexit` module and signal handlers for graceful cleanup during server shutdown. - -2. **Per-Request Cleanup**: After each migration request completes (whether successful or not), the API explicitly calls the cleanup function to remove all DICOM files associated with the study. - -3. **Directory Registration**: All temporary directories created during the migration process are registered for cleanup, ensuring no files are left behind. - -This dual approach (exit handlers + explicit cleanup) ensures that: -- DICOM files are immediately removed after each API request completes -- No files are left behind even if the server crashes or is terminated abnormally -- Disk space is efficiently managed during high-volume operations - -### Authentication - -The API uses Bearer token authentication. The token is specified through: diff --git a/api-app/api_cleanup.py b/api-app/api_cleanup.py new file mode 100644 index 0000000..19f180b --- /dev/null +++ b/api-app/api_cleanup.py @@ -0,0 +1,25 @@ +""" +Custom cleanup utilities for the API application. +This module provides customized cleanup functions that are more compatible with FastAPI. +""" +import os +import sys +import shutil +import atexit +from config import settings +from utils.logger import setup_logger +from utils.cleanup import _cleanup_dirs, cleanup_dicom_files + +# Create API cleanup logger +cleanup_logger = setup_logger("api_cleanup", "logs/api_cleanup.log") + +def register_api_cleanup(): + """ + Register cleanup handlers for the API application. + This avoids registering signal handlers which can conflict with FastAPI. + """ + # Only register the atexit handler, not signal handlers + atexit.register(cleanup_dicom_files) + cleanup_logger.info("Registered API cleanup handler for exit") + + return True diff --git a/api-app/main.py b/api-app/main.py index fa421a5..7bab9a0 100644 --- a/api-app/main.py +++ b/api-app/main.py @@ -17,7 +17,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from process import process_study_by_accession from utils.logger import setup_logger from utils.dicom_utils import create_directory_if_not_exists -from utils.cleanup import register_exit_handlers +from api_cleanup import register_api_cleanup # Create API logger api_logger = setup_logger("api", "logs/api.log") @@ -71,10 +71,23 @@ def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)): @app.on_event("startup") async def startup_event(): """Initialize resources when the API starts.""" - register_exit_handlers() + # Create necessary directories + create_directory_if_not_exists("logs") + + # Use our custom API cleanup registration that avoids signal handlers + register_api_cleanup() api_logger.info("API started successfully") +@app.on_event("shutdown") +async def shutdown_event(): + """Clean up resources when the API shuts down.""" + api_logger.info("API shutting down, cleaning up resources...") + # Perform cleanup operations here + from utils.cleanup import cleanup_dicom_files + cleanup_dicom_files() + api_logger.info("Cleanup complete, API shutdown successful") + @app.get("/", response_model=MigrationResponse) async def root(): """ diff --git a/api-app/run.py b/api-app/run.py index ef491ad..7a14615 100644 --- a/api-app/run.py +++ b/api-app/run.py @@ -4,6 +4,7 @@ Script to run the FastAPI server for DICOM migration service. """ import os import sys +import signal import uvicorn import argparse @@ -16,6 +17,12 @@ def parse_args(): parser.add_argument('--reload', action='store_true', help='Enable auto-reload for development') return parser.parse_args() +def handle_exit(signum, frame): + """Handle exit signals gracefully.""" + print(f"Received signal {signum}, shutting down gracefully...") + # Let uvicorn handle the exit + sys.exit(0) + def main(): """Main function to run the API server.""" args = parse_args() @@ -29,6 +36,10 @@ def main(): print("WARNING: API_TOKEN not set. Using default token 'token-his2-untuk-hit-api-migrasi-clarity'.") print("Set environment variable API_TOKEN or use --token argument for better security.") + # Set up signal handlers for graceful shutdown + signal.signal(signal.SIGINT, handle_exit) + signal.signal(signal.SIGTERM, handle_exit) + print(f"Starting DICOM Migration API server on {args.host}:{args.port}") uvicorn.run( "main:app", diff --git a/api-app/test.http b/api-app/test.http new file mode 100644 index 0000000..4030e1a --- /dev/null +++ b/api-app/test.http @@ -0,0 +1,12 @@ +@host = http://localhost:8000 +@token = token-his2-untuk-hit-api-migrasi-clarity + + +### Healtcheck +GET {{host}}/ + + +### +POST {{host}}/migrate/R.20240401.0138 +Authorization: Bearer {{token}} +Content-Type: application/json \ No newline at end of file diff --git a/utils/cleanup.py b/utils/cleanup.py index edf1f01..feb9085 100644 --- a/utils/cleanup.py +++ b/utils/cleanup.py @@ -2,6 +2,7 @@ DICOM file cleanup utility to ensure temporary DICOM files are removed even on script termination. """ import os +import sys import shutil import atexit import signal @@ -100,4 +101,12 @@ def signal_handler(sig, frame): logger.info(f"Received {signal_name}, cleaning up...") cleanup_dicom_files() - exit(0) \ No newline at end of file + # Don't call exit() directly in a web application context + # Let the calling application handle the exit process + # This fix makes it compatible with FastAPI and other web frameworks + # Raise SystemExit instead of calling exit() directly + if hasattr(frame, '_is_web_app') and frame._is_web_app: + return + else: + # For command-line applications, we can exit directly + sys.exit(0) \ No newline at end of file