J'aime et j'utilise Django dans beaucoup de mes projets personnels et clients, principalement pour des applications web plus classiques et celles impliquant des bases de données relationnelles. Cependant, Django n'est pas une solution miracle.
De par sa conception, Django est très étroitement associé à son objet ORM, Template Engine System et Settings. De plus, ce n’est pas un nouveau projet: il transporte beaucoup de bagages pour rester rétrocompatible.
Certains développeurs Python voient cela comme un problème majeur. Ils disent que Django n’est pas assez flexible et l’évitent si possible et utilisent à la place un microframework Python comme Flask.
Je ne partage pas cette opinion. Django est génial lorsqu'il est utilisé dans le lieu et heure appropriés , même s'il ne rentre pas dans chaque spécification du projet. Comme le dit le mantra: «Utilisez le bon outil pour le travail».
(Même si ce n'est pas le bon endroit et le bon moment, la programmation avec Django peut parfois avoir des avantages uniques.)
quels sont les 5 principes de la gestalt ?
Dans certains cas, il peut en effet être agréable d'utiliser un framework plus léger (comme Ballon ). Souvent, ces microframeworks commencent à briller lorsque vous réalisez à quel point ils sont faciles à pirater.
Dans quelques-uns de mes projets clients, nous avons discuté d'abandonner Django et de passer à un microframework, généralement lorsque les clients veulent faire des choses intéressantes (dans un cas, par exemple, intégrer ZeroMQ dans l'objet application) et les objectifs du projet semblent plus difficiles à atteindre avec Django.
Plus généralement, je trouve Flask utile pour:
Dans le même temps, notre application nécessitait l'enregistrement des utilisateurs et d'autres tâches courantes que Django avait résolues il y a des années. Compte tenu de son faible poids, Flask n’est pas livré avec la même boîte à outils.
La question s'est posée: Django est-il un accord tout ou rien?La question s'est posée: Django est-il un accord tout ou rien? Devrions-nous le laisser tomber complètement du projet, ou pouvons-nous apprendre à le combiner avec la flexibilité d'autres microframeworks ou frameworks traditionnels? Pouvons-nous choisir les pièces que nous voulons utiliser et en éviter les autres?
Pouvons-nous avoir le meilleur des deux mondes? Je dis oui, surtout en ce qui concerne la gestion de session.
(Sans oublier, il existe de nombreux projets pour les pigistes de Django.)
qu'est-ce qu'un marché adressable
Le but de cet article est de déléguer les tâches d'authentification et d'enregistrement des utilisateurs à Django, tout en utilisant Redis pour partager des sessions utilisateur avec d'autres frameworks. Je peux penser à quelques scénarios dans lesquels quelque chose comme ça serait utile:
Pour ce didacticiel, j'utiliserai Redis pour partager des sessions entre deux frameworks (dans ce cas, Django et Flask). Dans la configuration actuelle, j'utiliserai SQLite pour stocker les informations utilisateur, mais vous pouvez avoir votre back-end lié à une base de données NoSQL (ou à une alternative basée sur SQL) si nécessaire.
Pour partager des sessions entre Django et Flask, nous devons savoir comment Django stocke ses informations de session. La Documents Django sont plutôt bons, mais je vais vous fournir quelques informations d’exhaustivité.
En règle générale, vous pouvez choisir de gérer les données de session de votre application Python de l'une des deux manières suivantes:
Sessions basées sur les cookies : Dans ce scénario, les données de session ne sont pas stockées dans un magasin de données sur le back-end. Au lieu de cela, il est sérialisé, signé (avec une SECRET_KEY) et envoyé au client. Lorsque le client renvoie ces données, son intégrité est vérifiée pour la falsification et il est à nouveau désérialisé sur le serveur.
Sessions basées sur le stockage : Dans ce scénario, les données de session elles-mêmes sont ne pas envoyé au client. Au lieu de cela, seule une petite partie est envoyée (une clé) pour indiquer l'identité de l'utilisateur actuel, stockée sur le magasin de session.
Dans notre exemple, nous sommes plus intéressés par ce dernier scénario: nous voulons que nos données de session soient stockées sur le back-end, puis archivées dans Flask. La même chose pourrait être faite dans le premier, mais comme le mentionne la documentation de Django, il y a inquiétudes concernant la sécurité de la première méthode.
que fait un concepteur visuel
Le flux de travail général de la gestion et de la gestion des sessions sera similaire à ce diagramme:
Voyons un peu plus en détail le partage de session:
Lorsqu'une nouvelle demande arrive, la première étape consiste à l'envoyer via le middleware dans la pile Django. Nous sommes intéressés ici par le SessionMiddleware
classe qui, comme vous pouvez vous y attendre, est liée à la gestion et à la gestion des sessions:
class SessionMiddleware(object): def process_request(self, request): engine = import_module(settings.SESSION_ENGINE) session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None) request.session = engine.SessionStore(session_key)
Dans cet extrait de code, Django saisit les SessionEngine
enregistrés | (nous y reviendrons bientôt), extrait le SESSION_COOKIE_NAME
de request
(sessionid
, par défaut) et crée une nouvelle instance du SessionEngine
sélectionné | pour gérer le stockage de session.
Plus tard (après le traitement de la vue utilisateur, mais toujours dans la pile middleware), le moteur de session appelle sa méthode de sauvegarde pour enregistrer toutes les modifications apportées au magasin de données. (Lors de la gestion de la vue, l'utilisateur peut avoir changé certaines choses dans la session, par exemple en ajoutant une nouvelle valeur à l'objet de session avec request.session
.) Ensuite, le SESSION_COOKIE_NAME
est envoyé au client. Voici la version simplifiée:
def process_response(self, request, response): .... if response.status_code != 500: request.session.save() response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key, max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, path=settings.SESSION_COOKIE_PATH, secure=settings.SESSION_COOKIE_SECURE or None, httponly=settings.SESSION_COOKIE_HTTPONLY or None) return response
Nous sommes particulièrement intéressés par le SessionEngine
classe, que nous remplacerons par quelque chose pour stocker et charger des données depuis et vers un back-end Redis.
Heureusement, il existe quelques projets qui gèrent déjà cela pour nous. Voici un exemple de redis_sessions_fork . Faites très attention au save
et load
méthodes, qui sont écrites de manière à (respectivement) stocker et charger la session dans et depuis Redis:
class SessionStore(SessionBase): ''' Redis session back-end for Django ''' def __init__(self, session_key=None): super(SessionStore, self).__init__(session_key) def _get_or_create_session_key(self): if self._session_key is None: self._session_key = self._get_new_session_key() return self._session_key def load(self): session_data = backend.get(self.session_key) if not session_data is None: return self.decode(session_data) else: self.create() return {} def exists(self, session_key): return backend.exists(session_key) def create(self): while True: self._session_key = self._get_new_session_key() try: self.save(must_create=True) except CreateError: continue self.modified = True self._session_cache = {} return def save(self, must_create=False): session_key = self._get_or_create_session_key() expire_in = self.get_expiry_age() session_data = self.encode(self._get_session(no_load=must_create)) backend.save(session_key, expire_in, session_data, must_create) def delete(self, session_key=None): if session_key is None: if self.session_key is None: return session_key = self.session_key backend.delete(session_key)
Il est important de comprendre comment cette classe fonctionne, car nous devrons implémenter quelque chose de similaire sur Flask pour charger les données de session. Regardons de plus près avec un exemple de REPL:
>>> from django.conf import settings >>> from django.utils.importlib import import_module >>> engine = import_module(settings.SESSION_ENGINE) >>> engine.SessionStore() >>> store['count'] = 1 >>> store.save() >>> store.load() {u'count': 1}
L'interface du magasin de sessions est assez facile à comprendre, mais il se passe beaucoup de choses sous le capot. Nous devrions creuser un peu plus pour pouvoir implémenter quelque chose de similaire sur Flask.
Remarque: vous pourriez demander: «Pourquoi ne pas simplement copier le SessionEngine dans Flask?» Plus facile à dire qu'à faire. Comme nous l'avons vu au début, Django est étroitement associé à son objet Settings, vous ne pouvez donc pas simplement importer un module Django et l'utiliser sans aucun travail supplémentaire.
Comme je l'ai dit, Django fait beaucoup de travail pour masquer la complexité de son stockage de session. Vérifions la clé Redis stockée dans les extraits ci-dessus:
>>> store.session_key u'ery3j462ezmmgebbpwjajlxjxmvt5adu'
Maintenant, interrogeons cette clé sur le redis-cli:
redis 127.0.0.1:6379> get 'django_sessions:ery3j462ezmmgebbpwjajlxjxmvt5adu' 'ZmUxOTY0ZTFkMmNmODA2OWQ5ZjE4MjNhZmQxNDM0MDBiNmQzNzM2Zjp7ImNvdW50IjoxfQ=='
Ce que nous voyons ici est un très long, Encodé en base64 chaîne. Pour comprendre son objectif, nous devons examiner les SessionBase
de Django | classe pour voir comment il est géré:
class SessionBase(object): ''' Base class for all Session classes. ''' def encode(self, session_dict): 'Returns the given session dictionary serialized and encoded as a string.' serialized = self.serializer().dumps(session_dict) hash = self._hash(serialized) return base64.b64encode(hash.encode() + b':' + serialized).decode('ascii') def decode(self, session_data): encoded_data = base64.b64decode(force_bytes(session_data)) try: hash, serialized = encoded_data.split(b':', 1) expected_hash = self._hash(serialized) if not constant_time_compare(hash.decode(), expected_hash): raise SuspiciousSession('Session data corrupted') else: return self.serializer().loads(serialized) except Exception as e: # ValueError, SuspiciousOperation, unpickling exceptions if isinstance(e, SuspiciousOperation): logger = logging.getLogger('django.security.%s' % e.__class__.__name__) logger.warning(force_text(e)) return {}
La méthode d'encodage sérialise d'abord les données avec le sérialiseur enregistré actuel. En d'autres termes, il convertit la session en une chaîne, qu'il peut ultérieurement reconvertir en session (consultez la documentation SESSION_SERIALIZER pour en savoir plus). Ensuite, il hache les données sérialisées et utilise ce hachage plus tard comme signature pour vérifier l'intégrité des données de session. Enfin, il renvoie cette paire de données à l'utilisateur sous la forme d'une chaîne encodée en Base64.
Au fait: avant la version 1.6, Django utilisait par défaut pickle pour la sérialisation des données de session. En raison de problèmes de sécurité , la méthode de sérialisation par défaut est désormais django.contrib.sessions.serializers.JSONSerializer
.
meilleur cours de programmation c en ligne
Voyons le processus de gestion de session en action. Ici, notre dictionnaire de session sera simplement un nombre et un nombre entier, mais vous pouvez imaginer comment cela se généraliserait à des sessions utilisateur plus compliquées.
>>> store.encode({'count': 1}) u'ZmUxOTY0ZTFkMmNmODA2OWQ5ZjE4MjNhZmQxNDM0MDBiNmQzNzM2Zjp7ImNvdW50IjoxfQ==' >>> base64.b64decode(encoded) 'fe1964e1d2cf8069d9f1823afd143400b6d3736f:{'count':1}'
Le résultat de la méthode de stockage (u’ZmUxOTY… == ’) est une chaîne codée contenant la session utilisateur sérialisée et son hachage. Lorsque nous le décodons, nous récupérons en effet à la fois le hachage (‘fe1964e…’) et la session ({'count':1}
).
Notez que la méthode de décodage vérifie que le hachage est correct pour cette session, garantissant l'intégrité des données lorsque nous allons les utiliser dans Flask. Dans notre cas, nous ne sommes pas trop inquiets que notre session soit falsifiée côté client car:
Nous n'utilisons pas de sessions basées sur les cookies, c'est-à-dire que nous n'envoyons pas tout données utilisateur au client.
Sur Flask, nous aurons besoin d'un fichier en lecture seule SessionStore
qui nous dira si la clé donnée existe ou non et renverra les données stockées.
Ensuite, créons une version simplifiée du moteur de session Redis (base de données) pour fonctionner avec Flask. Nous utiliserons le même SessionStore
(définie ci-dessus) en tant que classe de base, mais nous devrons supprimer certaines de ses fonctionnalités, par exemple, vérifier les mauvaises signatures ou modifier les sessions. Nous sommes plus intéressés par une lecture seule SessionStore
qui chargera les données de session enregistrées depuis Django. Voyons comment cela s’associe:
class SessionStore(object): # The default serializer, for now def __init__(self, conn, session_key, secret, serializer=None): self._conn = conn self.session_key = session_key self._secret = secret self.serializer = serializer or JSONSerializer def load(self): session_data = self._conn.get(self.session_key) if not session_data is None: return self._decode(session_data) else: return {} def exists(self, session_key): return self._conn.exists(session_key) def _decode(self, session_data): ''' Decodes the Django session :param session_data: :return: decoded data ''' encoded_data = base64.b64decode(force_bytes(session_data)) try: # Could produce ValueError if there is no ':' hash, serialized = encoded_data.split(b':', 1) # In the Django version of that they check for corrupted data # I don't find it useful, so I'm removing it return self.serializer().loads(serialized) except Exception as e: # ValueError, SuspiciousOperation, unpickling exceptions. If any of # these happen, return an empty dictionary (i.e., empty session). return {}
Nous n'avons besoin que du load
car il s’agit d’une implémentation en lecture seule du stockage. Cela signifie que vous ne pouvez pas vous déconnecter directement de Flask; à la place, vous souhaiterez peut-être rediriger cette tâche vers Django. N'oubliez pas que l'objectif ici est de gérer les sessions entre ces deux frameworks Python pour vous donner plus de flexibilité.
Le microframework Flask prend en charge les sessions basées sur les cookies, ce qui signifie que toutes les données de session sont envoyées au client, encodées en Base64 et signées cryptographiquement. Mais en fait, nous ne sommes pas très intéressés par le support de session de Flask.
g.co/express/bonjour
Ce dont nous avons besoin, c'est d'obtenir l'ID de session créé par Django et de le vérifier par rapport au back-end Redis afin que nous puissions être sûrs que la demande appartient à un utilisateur pré-signé. En résumé, le processus idéal serait (cela se synchronise avec le diagramme ci-dessus):
Il sera pratique de demander à un décorateur de vérifier ces informations et de définir le user_id
actuel | dans le g
variable dans Flask:
from functools import wraps from flask import g, request, redirect, url_for def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): djsession_id = request.cookies.get('sessionid') if djsession_id is None: return redirect('/') key = get_session_prefixed(djsession_id) session_store = SessionStore(redis_conn, key) auth = session_store.load() if not auth: return redirect('/') g.user_id = str(auth.get('_auth_user_id')) return f(*args, **kwargs) return decorated_function
Dans l'exemple ci-dessus, nous utilisons toujours le SessionStore
nous avons défini précédemment pour récupérer les données Django de Redis. Si la session a un _auth_user_id
, nous renvoyons le contenu de la fonction d'affichage; sinon, l'utilisateur est redirigé vers une page de connexion, comme nous le voulions.
Pour partager des cookies, je trouve pratique de démarrer Django et Flask via un WSGI serveur et collez-les ensemble. Dans cet exemple, j'ai utilisé CherryPy :
from app import app from django.core.wsgi import get_wsgi_application application = get_wsgi_application() d = wsgiserver.WSGIPathInfoDispatcher({ '/':application, '/backend':app }) server = wsgiserver.CherryPyWSGIServer(('127.0.0.1', 8080), d)
Avec cela, Django servira sur «/» et Flask servira sur les points de terminaison «/ backend».
Plutôt que d’examiner Django par rapport à Flask ou de vous encourager uniquement à apprendre le microframework Flask, j’ai soudé Django et Flask, leur faisant partager les mêmes données de session pour l’authentification en déléguant la tâche à Django. Comme Django est livré avec de nombreux modules pour résoudre l'enregistrement, la connexion et la déconnexion des utilisateurs (pour n'en nommer que quelques-uns), la combinaison de ces deux frameworks vous fera gagner un temps précieux tout en vous offrant la possibilité de pirater un microframework gérable comme Flask.