Flask's SERVER_NAME, subdominios y 404 errores
La configuración de Flask's SERVER_NAME puede dar 404 errores si está usando subdominios.
Este es un corto post sobre Flask y la config variable SERVER_NAME. Como muchos desarrolladores me encontré con esto en un momento determinado, y pensé que compartía mi historia. Con suerte esto evitará dolores de cabeza para algunos.
Mis sitios web deben estar disponibles escribiendo las siguientes direcciones en el navegador:
- https://example.com = 'without-www', y,
- https://www.example.com = 'with-www'
Además, para este sitio web había decidido que los URL de las páginas generadas debían ser "without-www" o "with-www", dependiendo de cuál fuera el URL de la solicitud. Esto significa que si escribo:
https://example.com/contact
no hay redireccionamiento y todos los enlaces en la página generada (aparecerán) comenzarán con https://example.com. O, si escribo:
https://www.example.com/contact
no hay redireccionamiento y todos los enlaces en la página generada (aparecerán) comenzarán con https://www.example.com.
Debo añadir que esta no es la mejor práctica y que siempre debe utilizar un solo canonical name, lo que significa que su página web es o bien "with-www" o "without-www", ¡y nunca ambos! En un próximo post entraré en detalles sobre esto.
De todos modos, lo anterior funcionó bien para todos mis sitios web excepto uno, este sitio web de hecho. Hace meses esto no era un problema pero cambió de repente después de una importante actualización de código. Para prevenir los errores 404 de la versión 'with-www' lo solucioné rápidamente haciendo que Nginx redirigiera 'with-www' a 'without-www'. Y luego hubo otros proyectos más urgentes y pasaron meses.
Recientemente estuve actualizando mi servidor (ISPConfig) y pensé en intentarlo de nuevo. Por supuesto, todavía hay 404 errores en la versión "with-www".
Hice algo de tiempo y decidí crear la misma situación en mi PC de desarrollo. Esto significaba copiar los certificados Letsencrypt a mi PC y modificar ligeramente la configuración de desarrollo cambiando FLASK_ENV a 'production' y eliminando el 'with-www' a 'without-www' redirigir en Nginx. Dejé FLASK_DEBUG=True para poder poner puntos de ruptura (excepciones) en el código Python y ver qué estaba pasando.
Afortunadamente, experimenté los mismos errores 404 al cambiar de 'without-www' a 'with-www' en mi PC. Luego, después de algún tiempo reinicié Flask y ahora de repente la versión 'with-www' funcionó y la versión 'Q 4_959_TNEMECALPER_4Q' dio 404 errores. ¿Qué? ¿Al revés?
Buscando en internet por 'Flask ' reboté en páginas, ver los enlaces de abajo, que insinuaban que la versión config variable SERVER_NAME podría ser el problema.
Entonces recordé que añadí SERVER_NAME a mi config cuando estaba trabajando en las tareas de Celery . Hice esto porque pensé que podría ser útil para generar plantillas de Jinja con enlaces url_for() en algunas tareas. Más tarde descarté esta idea. Las tareas deberían ser sencillas y no contener campanas y silbidos. Pero no eliminé el código SERVER_NAME , porque todo iba bien, y después de establecer la redirección en Nginx había otros proyectos más urgentes, qué más hay de nuevo.
Descripción de lo que pasó
Mi factory.py (__init__.py) parecía:
def create_app():
...
@app.before_request
def before_request():
....
# get server_name from http_host
if current_app.config.get('SERVER_NAME') is None:
http_host = request.environ.get('HTTP_HOST')
current_app.config['SERVER_NAME'] = http_host
....
Aquí obtengo SERVER_NAME de la solicitud si no se ha establecido. Esto significa que una vez que config[SERVER_NAME] se establece, ya no se actualiza. Si empiezo con:
https://www.peterspython.com
entonces el SERVER_NAME se establece en: www.peterspython.com. Pero si empiezo con:
https://peterspython.com
entonces el SERVER_NAME se establece en: peterspython.com.
En ambos casos, config recuerda el SERVER_NAME entre las peticiones (de esta sesión), así que si cambio un URL de trabajo 'with-www' a un URL 'without-www', entonces obtenemos los errores 404 porque el enrutamiento Flask utiliza un SERVER_NAME erróneo.
El comportamiento de Flask de SERVER_NAME está bien documentado pero comprenderlo completamente es otra cosa. Mi problema también se describe en el artículo "Cosas que debes saber sobre Flask SERVER_NAME", ver enlaces abajo:
"Una vez que se establece SERVER_NAME, Flask sólo puede servir la solicitud de un solo dominio y devolver 404 para otros dominios. Si SERVER_NAME = midominio.com, no servirá la solicitud de www.mydomain.com ...'
Solución
La solución es, por supuesto, eliminar la línea:
....
if current_app.config.get('SERVER_NAME') is None:
....
Esto siempre establecerá la SERVER_NAME config variable en cada solicitud. O mejor aún (?), eliminar estas líneas todas juntas porque no uso SERVER_NAME en ninguna parte.
Resumen
En un momento dado añadí la variable config a mi código y establecí esta variable en before_request sólo cuando no estaba ya establecida. Olvidé que mi sitio web podía funcionar tanto con 'with-www' como con 'without-www' URLs y que la Flask config es persistente entre peticiones de la misma sesión. Una vez que llegué allí fue fácil de resolver.
Esto nunca hubiera pasado si hubiera elegido un solo canonical name para mi sitio web. Con este canonical name Flask sólo tendría que tratar con 'with-www' o 'without-www' pero nunca con ambos. En un próximo post explicaré por qué su canonical name debería ser siempre 'with-www'.
De todos modos, las lecciones aprendidas... otra vez.
Enlaces / créditos
SERVER_NAME configuration should not implicitly change routing behavior. #998
https://github.com/pallets/flask/issues/998
Things You Should Know About Flask SERVER_NAME
https://code.luasoftware.com/tutorials/flask/things-you-should-know-about-flask-server-name/
Unexplainable Flask 404 errors
https://stackoverflow.com/questions/24437248/unexplainable-flask-404-errors
Leer más
Flask
Deje un comentario
Comente de forma anónima o inicie sesión para comentar.
Comentarios (2)
Deje una respuesta.
Responda de forma anónima o inicie sesión para responder.
This has been clawing at me all afternoon. Thank you for sharing this.
A word of warning, changing the app config every before_request is sensitive to race conditions. So if you are using a threaded model of Flask execution, I would strongly advise against using the presented approach.
A small demo I tried to see if it would fit my needs in this gist: https://gist.github.com/eelkevdbos/14177eb9d72f5c96ed0f22ed64c30d19
Recientes
- Cómo ocultar las claves primarias de la base de datos UUID de su aplicación web
- Don't Repeat Yourself (DRY) con Jinja2
- SQLAlchemy, PostgreSQL, número máximo de filas por user
- Mostrar los valores en filtros dinámicos SQLAlchemy
- Transferencia de datos segura con cifrado de Public Key y pyNaCl
- rqlite: una alternativa de alta disponibilidad y dist distribuida SQLite
Más vistos
- Usando Python's pyOpenSSL para verificar los certificados SSL descargados de un host
- Usando UUIDs en lugar de Integer Autoincrement Primary Keys con SQLAlchemy y MariaDb
- Conectarse a un servicio en un host Docker desde un contenedor Docker
- Usando PyInstaller y Cython para crear un ejecutable de Python
- SQLAlchemy: Uso de Cascade Deletes para eliminar objetos relacionados
- Flask RESTful API validación de parámetros de solicitud con esquemas Marshmallow