Make API encode/decode endpoints async with thread pool
- Added run_in_thread() helper using asyncio.to_thread() - /encode, /encode/file, /decode use thread pool for CPU-bound ops - /encode/multipart, /decode/multipart also updated - Server can now handle concurrent requests without blocking - Updated version header to v4.2.0 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,14 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Stegasoo REST API (v4.0.0)
|
Stegasoo REST API (v4.2.0)
|
||||||
|
|
||||||
FastAPI-based REST API for steganography operations.
|
FastAPI-based REST API for steganography operations.
|
||||||
Supports both text messages and file embedding.
|
Supports both text messages and file embedding.
|
||||||
|
|
||||||
|
CHANGES in v4.2.0:
|
||||||
|
- Async encode/decode operations (run in thread pool)
|
||||||
|
- Server can handle concurrent requests without blocking
|
||||||
|
|
||||||
CHANGES in v4.0.0:
|
CHANGES in v4.0.0:
|
||||||
- Added channel key support for deployment/group isolation
|
- Added channel key support for deployment/group isolation
|
||||||
- New /channel endpoints for key management
|
- New /channel endpoints for key management
|
||||||
@@ -21,8 +25,10 @@ NEW in v3.0: LSB and DCT embedding modes.
|
|||||||
NEW in v3.0.1: DCT color mode and JPEG output format.
|
NEW in v3.0.1: DCT color mode and JPEG output format.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
import sys
|
import sys
|
||||||
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
@@ -436,6 +442,27 @@ def _get_channel_info(channel_key: str | None) -> tuple[str, str | None]:
|
|||||||
return info["mode"], info.get("fingerprint")
|
return info["mode"], info.get("fingerprint")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# HELPER: ASYNC EXECUTION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
async def run_in_thread(func, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Run a CPU-bound function in a thread pool.
|
||||||
|
|
||||||
|
This allows the FastAPI server to handle other requests while
|
||||||
|
encode/decode operations are running. Essential for Pi deployments
|
||||||
|
where operations can take several seconds.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
result = await run_in_thread(encode, message=msg, carrier_image=carrier, ...)
|
||||||
|
"""
|
||||||
|
if kwargs:
|
||||||
|
func = partial(func, **kwargs)
|
||||||
|
return await asyncio.to_thread(func, *args)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# ROUTES - STATUS & INFO
|
# ROUTES - STATUS & INFO
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -874,8 +901,9 @@ async def api_encode(request: EncodeRequest):
|
|||||||
request.embed_mode, request.dct_output_format, request.dct_color_mode
|
request.embed_mode, request.dct_output_format, request.dct_color_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
# v4.0.0: Include channel_key
|
# v4.2.0: Run CPU-bound encode in thread pool
|
||||||
result = encode(
|
result = await run_in_thread(
|
||||||
|
encode,
|
||||||
message=request.message,
|
message=request.message,
|
||||||
reference_photo=ref_photo,
|
reference_photo=ref_photo,
|
||||||
carrier_image=carrier,
|
carrier_image=carrier,
|
||||||
@@ -950,8 +978,9 @@ async def api_encode_file(request: EncodeFileRequest):
|
|||||||
request.embed_mode, request.dct_output_format, request.dct_color_mode
|
request.embed_mode, request.dct_output_format, request.dct_color_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
# v4.0.0: Include channel_key
|
# v4.2.0: Run CPU-bound encode in thread pool
|
||||||
result = encode(
|
result = await run_in_thread(
|
||||||
|
encode,
|
||||||
message=payload,
|
message=payload,
|
||||||
reference_photo=ref_photo,
|
reference_photo=ref_photo,
|
||||||
carrier_image=carrier,
|
carrier_image=carrier,
|
||||||
@@ -1021,8 +1050,9 @@ async def api_decode(request: DecodeRequest):
|
|||||||
ref_photo = base64.b64decode(request.reference_photo_base64)
|
ref_photo = base64.b64decode(request.reference_photo_base64)
|
||||||
rsa_key = base64.b64decode(request.rsa_key_base64) if request.rsa_key_base64 else None
|
rsa_key = base64.b64decode(request.rsa_key_base64) if request.rsa_key_base64 else None
|
||||||
|
|
||||||
# v4.0.0: Include channel_key
|
# v4.2.0: Run CPU-bound decode in thread pool
|
||||||
result = decode(
|
result = await run_in_thread(
|
||||||
|
decode,
|
||||||
stego_image=stego,
|
stego_image=stego,
|
||||||
reference_photo=ref_photo,
|
reference_photo=ref_photo,
|
||||||
passphrase=request.passphrase,
|
passphrase=request.passphrase,
|
||||||
@@ -1150,8 +1180,9 @@ async def api_encode_multipart(
|
|||||||
# Get DCT parameters
|
# Get DCT parameters
|
||||||
dct_params = _get_dct_params(embed_mode, dct_output_format, dct_color_mode)
|
dct_params = _get_dct_params(embed_mode, dct_output_format, dct_color_mode)
|
||||||
|
|
||||||
# v4.0.0: Include channel_key
|
# v4.2.0: Run CPU-bound encode in thread pool
|
||||||
result = encode(
|
result = await run_in_thread(
|
||||||
|
encode,
|
||||||
message=payload,
|
message=payload,
|
||||||
reference_photo=ref_data,
|
reference_photo=ref_data,
|
||||||
carrier_image=carrier_data,
|
carrier_image=carrier_data,
|
||||||
@@ -1264,8 +1295,9 @@ async def api_decode_multipart(
|
|||||||
# QR code keys are never password-protected
|
# QR code keys are never password-protected
|
||||||
effective_password = None if rsa_key_from_qr else (rsa_password if rsa_password else None)
|
effective_password = None if rsa_key_from_qr else (rsa_password if rsa_password else None)
|
||||||
|
|
||||||
# v4.0.0: Include channel_key
|
# v4.2.0: Run CPU-bound decode in thread pool
|
||||||
result = decode(
|
result = await run_in_thread(
|
||||||
|
decode,
|
||||||
stego_image=stego_data,
|
stego_image=stego_data,
|
||||||
reference_photo=ref_data,
|
reference_photo=ref_data,
|
||||||
passphrase=passphrase,
|
passphrase=passphrase,
|
||||||
|
|||||||
Reference in New Issue
Block a user