add http test | fix ctrl+c shutdown

This commit is contained in:
mario
2025-07-11 15:48:34 +07:00
parent 853cc70edb
commit 0cdd52db94
6 changed files with 138 additions and 139 deletions

View File

@@ -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:

25
api-app/api_cleanup.py Normal file
View File

@@ -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

View File

@@ -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():
"""

View File

@@ -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",

12
api-app/test.http Normal file
View File

@@ -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

View File

@@ -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)
# 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)