From 4feee8aa70060588f63b1576410bebc7e03ac9cb Mon Sep 17 00:00:00 2001 From: Alex Ioannidis Date: Tue, 24 Mar 2026 11:06:24 +0100 Subject: [PATCH] fix(previewer): bypass IIPImage server for simple image preview * When we don't have tiles (e.g. for restricted records), we currently rely on nginx to interpret the 404/500 from IIPImage to fall back to the Flask application. This didn't work well in cases where IIPImage fails with a segfault or non-standard response. * We now explicitly add to the IIIF URL for the simple image preview (used for restricted records), a `__skip_image_server=1` querystring parameter, so that nginx can interpret it directly and immediately skip/fallback to the Flask IIIF serving. * We also enable buffering on nginx, so we can properly handle upstream errors and fallback properly to Flask. This is fine since tile payloads from IIPImage are relatively samll. --- docker/nginx/conf.d/default.conf | 13 +++++++++++-- site/zenodo_rdm/previewer/image_previewer.py | 10 +++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/docker/nginx/conf.d/default.conf b/docker/nginx/conf.d/default.conf index 3e397e5c9..eb2fc215a 100644 --- a/docker/nginx/conf.d/default.conf +++ b/docker/nginx/conf.d/default.conf @@ -181,6 +181,15 @@ server { # Keep the original URI to pass to the fallback location in case of error set $original_uri $request_uri; + + # Skip IIPImage, route directly to Flask (strip the param before fallback). + # NOTE: $uri strips the entire querystring. This is fine since IIIF Image + # API uses path segments, not query params. If future query params are + # needed, this will need a more targeted stripping approach. + if ($arg___skip_image_server = "1") { + set $original_uri $uri; + return 418; + } # Construct the final URL. This converts: # # - /api/iiif/record:1:image.png/... -> /api/iiif/1_/__/_/image.png.ptif/... @@ -191,7 +200,7 @@ server { fastcgi_pass image_server; include fastcgi_params; - fastcgi_buffering off; + fastcgi_buffering on; fastcgi_param PATH_INFO $fastcgi_script_name; fastcgi_param REQUEST_METHOD $request_method; @@ -204,7 +213,7 @@ server { # In case of an error, fallback to the Flask/Invenio application fastcgi_intercept_errors on; - error_page 404 500 502 503 504 = @iiif_fallback; + error_page 404 418 500 502 503 504 = @iiif_fallback; } } diff --git a/site/zenodo_rdm/previewer/image_previewer.py b/site/zenodo_rdm/previewer/image_previewer.py index 964317d7b..5b8a6f975 100644 --- a/site/zenodo_rdm/previewer/image_previewer.py +++ b/site/zenodo_rdm/previewer/image_previewer.py @@ -5,6 +5,7 @@ from copy import deepcopy from datetime import datetime, timedelta, timezone from os.path import splitext +from urllib.parse import urlencode, urlparse, urlunparse from flask import current_app, render_template @@ -149,9 +150,16 @@ def preview(file): else "jpg" ) size = current_app.config["IIIF_SIMPLE_PREVIEWER_SIZE"] - tpl_ctx["iiif_simple_url"] = ( + iiif_simple_url = ( f"{file.data['links']['iiif_base']}/full/{size}/0/default.{format}" ) + unprocessed = tile_status is None + if unprocessed: + parsed = urlparse(iiif_simple_url) + qs = urlencode({"__skip_image_server": "1"}) + existing_qs = f"{parsed.query}&{qs}" if parsed.query else qs + iiif_simple_url = urlunparse(parsed._replace(query=existing_qs)) + tpl_ctx["iiif_simple_url"] = iiif_simple_url # Add banner message if needed if record.is_draft: