2025, Oct 20 17:16
Исправляем 404 статических файлов во Flask за IIS: URL Rewrite и виртуальный каталог
Статика во Flask за IIS исчезает? Разбираем 404 из‑за URL Rewrite и виртуального каталога, показываем настройку reverse proxy и DispatcherMiddleware.
Когда вы разворачиваете приложение Flask за IIS с URL Rewrite, статические ресурсы могут внезапно исчезнуть. HTML отрисовывается, но css, js и изображения возвращают 404. Локально всё работает, приложение без проблем запускается с waitress, однако как только его проксируют под виртуальным каталогом вроде https://myhost/MyApp, пути к ресурсам перестают разрешаться.
Как воспроизвести проблему
Структура проекта хранит шаблоны и статические файлы в каталоге frontend, а бэкенд Flask — в src/backend. Приложение настроено на пользовательскую папку статических файлов и static_url_path.
import os as osmod
import flask as fk
from werkzeug.middleware.proxy_fix import ProxyFix as ProxyAdaptor
APP_ROOT = '/MYAPP'
tpl_path = osmod.path.abspath(osmod.path.join(osmod.path.dirname(__file__), '../frontend/templates'))
asset_base = osmod.path.abspath(osmod.path.join(osmod.path.dirname(__file__), '../frontend'))
svc = fk.Flask(
__name__,
template_folder=tpl_path,
static_folder=asset_base,
static_url_path=f'{APP_ROOT}/src/frontend'
)
svc.config['APPLICATION_ROOT'] = APP_ROOT
svc.wsgi_app = ProxyAdaptor(
svc.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
)
@svc.route('/')
def index():
return fk.render_template('page.html')
@svc.route('/toMYAPP')
def go_myapp():
return fk.render_template('page.html')
В шаблоне статические файлы запрашиваются через url_for, поэтому Flask должен отдавать их из настроенной папки static.
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<script src="{{ url_for('static', filename='js/my_app_js.js') }}"></script>
<img src="{{ url_for('static', filename='images/image1.png') }}">
<img src="{{ url_for('static', filename='images/image2.png') }}">
В IIS виртуальный каталог использует правило обратного прокси для переадресации запросов на сервер waitress.
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="ReverseProxyToFlask" stopProcessing="true">
<match url="^(.*)$" />
<action type="Rewrite" url="http://localhost:8081/{R:1}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Что на самом деле ломается
Обратный прокси опускает сегмент /MyApp при пересылке. Бэкенд получает пути без префикса виртуального каталога, поэтому все URL ресурсов, которые генерируются для статической конечной точки, не совпадают с тем, что IIS фактически передаёт. Отсюда 404 для css, js и изображений, хотя сам шаблон отрисовывается.
Как аккуратно устранить несоответствие путей
Рабочее решение — заставить обратный прокси сохранять виртуальный каталог в пересылаемом URL и смонтировать приложение Flask под тем же подпутём на уровне WSGI. Так то, что отправляет IIS, будет согласовано с ожиданиями приложения.
Смонтируйте приложение под /MyApp с помощью DispatcherMiddleware.
from werkzeug.middleware.dispatcher import DispatcherMiddleware as Mux
# оставьте экземпляр Flask, определённый ранее, под именем `svc`
application = Mux(None, {
'/MyApp': svc
})
После этого приложение явно обслуживается по пути /MyApp. Промежуточное ПО ProxyFix больше не требуется.
Обновите правило переписывания IIS так, чтобы подпуть сохранялся, и убедитесь, что IIS не перехватывает расширения статических файлов, которые должен обслуживать Flask.
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="ReverseProxyToFlask" stopProcessing="true">
<match url=".*" />
<action type="Rewrite" url="http://127.0.0.1:8081/MyApp/{R:0}" />
</rule>
</rules>
</rewrite>
<staticContent>
<remove fileExtension=".css" />
<remove fileExtension=".js" />
<remove fileExtension=".png" />
</staticContent>
</system.webServer>
</configuration>
Наконец, запустите waitress и укажите WSGI-точку входа, в которой подключено смонтированное приложение.
> cd MYAPP/src/backend
> waitress-serve --host 127.0.0.1 --port=8081 app:application
Почему это важно
Когда приложение разворачивается под подпутём за обратным прокси, прокси и приложение должны договориться о префиксе. Если прокси удаляет префикс, а приложение генерирует URL с ним, разрешение путей к ресурсам ломается. Согласование префикса на стороне прокси и на уровне WSGI предотвращает 404 для статики и делает маршрутизацию предсказуемой.
Выводы
Если локально статика работает, а за IIS — нет, проверьте, не отбрасывается ли сегмент виртуального каталога. Смонтируйте приложение Flask под тем же подпутём через DispatcherMiddleware и скорректируйте правило URL Rewrite, чтобы этот сегмент проходил дальше. Если IIS перехватывает расширения статических файлов, удалите эти сопоставления — тогда бэкенд сможет отдавать ресурсы. После этих правок шаблоны, css, js и изображения стабильно разрешаются внутри виртуального каталога.