"""HTTP server module for serving exported images from memory."""
import io
import json
import socketserver
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
class InMemoryImageHandler(BaseHTTPRequestHandler):
"""HTTP request handler for serving images stored in memory."""
# Class variable to store images
images = {}
def do_GET(self):
"""Handle GET requests."""
# Remove query parameters if any
path = self.path.split('?', 1)[0]
path = path.split('#', 1)[0]
# Extract image ID from path
# Expected path format: /images/{image_id}.{format}
parts = path.split('/')
if len(parts) == 3 and parts[1] == 'images':
# Extract image_id by removing the file extension if present
image_id_with_ext = parts[2]
image_id = image_id_with_ext.split('.')[0]
if image_id in self.images:
img_data = self.images[image_id]['data']
img_format = self.images[image_id]['format']
# Set content type based on format
content_type = f"image/{img_format}"
if img_format == 'svg':
content_type = 'image/svg+xml'
self.send_response(200)
self.send_header('Content-type', content_type)
self.send_header('Content-length', len(img_data))
self.end_headers()
self.wfile.write(img_data)
return
# Return 404 if image not found
self.send_response(404)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {'error': 'Image not found'}
self.wfile.write(json.dumps(response).encode())
class ImageServer:
"""Server for in-memory images."""
def __init__(self, host='localhost', port=0):
"""Initialize the HTTP server.
Args:
host: Host address to listen on
port: Port to listen on (0 means use a random available port)
"""
self.host = host
self.port = port
self.server = None
self.server_thread = None
self.is_running = False
self.base_url = None
def start(self):
"""Start the HTTP server in a background thread.
Returns:
Base URL of the server with actual port used
"""
if self.is_running:
return self.base_url
# Create TCP server with address reuse enabled
class ReuseAddressTCPServer(socketserver.TCPServer):
allow_reuse_address = True
self.server = ReuseAddressTCPServer((self.host, self.port), InMemoryImageHandler)
# Get the actual port that was assigned
self.port = self.server.socket.getsockname()[1]
self.base_url = f"http://{self.host}:{self.port}"
# Start server in a separate thread
self.server_thread = threading.Thread(target=self.server.serve_forever)
self.server_thread.daemon = True # Don't keep process running if main thread exits
self.server_thread.start()
self.is_running = True
print(f"Image server started at {self.base_url}")
return self.base_url
def stop(self):
"""Stop the HTTP server."""
if not self.is_running:
return
self.server.shutdown()
self.server.server_close()
self.is_running = False
print("Image server stopped")
def add_image(self, image_id, image_data, image_format='png'):
"""Add image to in-memory storage.
Args:
image_id: Unique identifier for the image
image_data: Binary image data
image_format: Image format (png, jpg, etc.)
Returns:
URL to access the image
"""
InMemoryImageHandler.images[image_id] = {
'data': image_data,
'format': image_format
}
return f"{self.base_url}/images/{image_id}.{image_format}"
def remove_image(self, image_id):
"""Remove image from in-memory storage."""
if image_id in InMemoryImageHandler.images:
del InMemoryImageHandler.images[image_id]