Λατρεύω και χρησιμοποιώ Τζάνγκο σε πολλά από τα προσωπικά μου έργα και τα προγράμματα-πελάτες μου, κυρίως για πιο κλασικές εφαρμογές Ιστού και σε εκείνες που αφορούν σχεσιακές βάσεις δεδομένων. Ωστόσο, το Django δεν είναι ασημένια σφαίρα.
Σχετικά με το σχεδιασμό, το Django συνδυάζεται πολύ καλά με το αντικείμενο ORM, Template Engine System και Settings. Επιπλέον, δεν είναι ένα νέο έργο: μεταφέρει πολλές αποσκευές για να παραμείνει συμβατή προς τα πίσω.
Ορισμένοι προγραμματιστές της Python το βλέπουν ως σημαντικό πρόβλημα. Λένε ότι το Django δεν είναι αρκετά ευέλικτο και το αποφεύγει αν είναι δυνατόν και, αντ 'αυτού, χρησιμοποιήστε ένα μικροπλαίσιο Python όπως το Flask.
Δεν συμμερίζομαι αυτήν την άποψη. Το Django είναι υπέροχο όταν χρησιμοποιείται στο κατάλληλο μέρος και ώρα , ακόμη και αν δεν ταιριάζει κάθε προδιαγραφή έργου Καθώς το μάντρα πηγαίνει: «Χρησιμοποιήστε το σωστό εργαλείο για την εργασία».
(Ακόμα και όταν δεν είναι το σωστό μέρος και ώρα, μερικές φορές ο προγραμματισμός με το Django μπορεί να έχει μοναδικά οφέλη.)
μερίδιο αγοράς uber vs lift
Σε ορισμένες περιπτώσεις, μπορεί πράγματι να είναι ωραίο να χρησιμοποιήσετε ένα πιο ελαφρύ πλαίσιο (όπως Φλάσκα ). Συχνά, αυτά τα μικροπλαίσια αρχίζουν να λάμπουν όταν συνειδητοποιείτε πόσο εύκολο είναι να χακάρουν.
Σε μερικά από τα έργα των πελατών μου, συζητήσαμε να εγκαταλείψουμε το Django και να μετακινηθούμε σε ένα μικροπλαίσιο, συνήθως όταν οι πελάτες θέλουν να κάνουν κάποια ενδιαφέροντα πράγματα (σε μία περίπτωση, για παράδειγμα, ενσωμάτωση ZeroMQ στο αντικείμενο της εφαρμογής) και οι στόχοι του έργου φαίνεται πιο δύσκολο να επιτευχθούν με τον Django.
Γενικότερα, θεωρώ ότι η φιάλη είναι χρήσιμη για:
Ταυτόχρονα, η εφαρμογή μας απαιτούσε εγγραφή χρηστών και άλλες κοινές εργασίες που η Django έλυσε πριν από χρόνια. Λόγω του μικρού βάρους του, το Flask δεν συνοδεύεται από την ίδια εργαλειοθήκη.
Το ερώτημα προέκυψε: είναι το Django μια συμφωνία με όλα ή τίποτα;Το ερώτημα προέκυψε: είναι το Django μια συμφωνία με όλα ή τίποτα; Πρέπει να το αφήσουμε εντελώς από το έργο ή μπορούμε να μάθουμε να το συνδυάζουμε με την ευελιξία άλλων μικροπλαισίων ή παραδοσιακών πλαισίων; Μπορούμε να διαλέξουμε και να διαλέξουμε τα κομμάτια που θέλουμε να χρησιμοποιήσουμε και να αποφύγουμε τους άλλους;
Μπορούμε να έχουμε το καλύτερο και των δύο κόσμων; Λέω ναι, ειδικά όταν πρόκειται για διαχείριση συνεδριών.
(Για να μην αναφέρουμε, υπάρχουν πολλά έργα εκεί έξω για τους ελεύθερους επαγγελματίες του Django.)
Ποια από τις παρακάτω εκφράσεις μπορεί να χρησιμοποιηθεί για τον υπολογισμό της ελαστικότητας της ζήτησης ως προς την τιμή;
Ο στόχος αυτής της ανάρτησης είναι να μεταβιβάσει τα καθήκοντα ελέγχου ταυτότητας χρήστη και εγγραφής στο Django, αλλά να χρησιμοποιήσει το Redis για να μοιραστεί τις συνεδρίες χρηστών με άλλα πλαίσια. Μπορώ να σκεφτώ μερικά σενάρια στα οποία κάτι τέτοιο θα ήταν χρήσιμο:
Για αυτό το σεμινάριο, θα το χρησιμοποιήσω Redis για κοινή χρήση συνεδριών μεταξύ δύο πλαισίων (σε αυτήν την περίπτωση, Django και Flask). Στην τρέχουσα ρύθμιση, θα το χρησιμοποιήσω SQLite για να αποθηκεύσετε πληροφορίες χρήστη, αλλά μπορείτε να συνδέσετε το back-end σας σε μια βάση δεδομένων NoSQL (ή μια εναλλακτική λύση που βασίζεται σε SQL), εάν χρειάζεται.
Για να μοιραστούμε τις συνεδρίες μεταξύ Django και Flask, πρέπει να μάθουμε λίγο για τον τρόπο με τον οποίο το Django αποθηκεύει τις πληροφορίες συνεδρίας του. ο Έγγραφα Django είναι αρκετά καλά, αλλά θα δώσω κάποιο υπόβαθρο για την πληρότητα.
Γενικά, μπορείτε να επιλέξετε να διαχειριστείτε τα δεδομένα περιόδου σύνδεσης της εφαρμογής Python με έναν από τους δύο τρόπους:
Συνεδρίες βασισμένες σε cookie : Σε αυτό το σενάριο, τα δεδομένα περιόδου λειτουργίας δεν αποθηκεύονται σε μια αποθήκευση δεδομένων στο παρασκήνιο. Αντ 'αυτού, είναι σειριακό, υπογράφεται (με SECRET_KEY) και αποστέλλεται στον πελάτη. Όταν ο πελάτης αποστέλλει αυτά τα δεδομένα πίσω, ελέγχεται η ακεραιότητά του για παραβίαση και αποεπιτυγχάνεται ξανά στον διακομιστή.
Συνεδρίες βασισμένες στο χώρο αποθήκευσης : Σε αυτό το σενάριο, τα ίδια τα δεδομένα περιόδου σύνδεσης είναι δεν αποστέλλεται στον πελάτη. Αντ 'αυτού, αποστέλλεται μόνο ένα μικρό τμήμα (ένα κλειδί) για να υποδείξει την ταυτότητα του τρέχοντος χρήστη, αποθηκευμένο στο χώρο αποθήκευσης συνεδρίας.
Στο παράδειγμά μας, ενδιαφερόμαστε περισσότερο για το τελευταίο σενάριο: θέλουμε τα δεδομένα περιόδου σύνδεσης να αποθηκεύονται στο παρασκήνιο και στη συνέχεια να ελέγχονται στη Φιάλη. Το ίδιο θα μπορούσε να γίνει και στο πρώτο, αλλά όπως αναφέρει η τεκμηρίωση του Django, υπάρχουν μερικά ανησυχίες για την ασφάλεια της πρώτης μεθόδου.
Η γενική ροή εργασιών χειρισμού και διαχείρισης συνεδρίας θα είναι παρόμοια με αυτό το διάγραμμα:
πώς να χακάρετε την πιστωτική κάρτα
Ας ρίξουμε μια ματιά στην κοινή χρήση συνεδρίας με λίγο περισσότερες λεπτομέρειες:
Όταν εισέλθει ένα νέο αίτημα, το πρώτο βήμα είναι να το στείλετε μέσω του εγγεγραμμένου μεσαίο λογισμικό στη στοίβα του Django. Ενδιαφερόμαστε εδώ στο SessionMiddleware
τάξη που, όπως θα περίμενε κανείς, σχετίζεται με τη διαχείριση και τη διαχείριση συνεδριών:
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)
Σε αυτό το απόσπασμα, ο Django αρπάζει το εγγεγραμμένο SessionEngine
(θα το φτάσουμε σύντομα), εξάγει το SESSION_COOKIE_NAME
από request
(sessionid
, από προεπιλογή) και δημιουργεί μια νέα παρουσία του επιλεγμένου SessionEngine
για χειρισμό χώρου αποθήκευσης συνεδρίας.
Αργότερα (μετά την επεξεργασία της προβολής χρήστη, αλλά ακόμα στη στοίβα middleware), η μηχανή συνεδρίας καλεί τη μέθοδο αποθήκευσης για να αποθηκεύσει τυχόν αλλαγές στο χώρο αποθήκευσης δεδομένων. (Κατά τη διάρκεια του χειρισμού προβολών, ο χρήστης μπορεί να έχει αλλάξει μερικά πράγματα εντός της περιόδου σύνδεσης, π.χ., προσθέτοντας μια νέα τιμή στο αντικείμενο περιόδου σύνδεσης με request.session
.) Στη συνέχεια, το SESSION_COOKIE_NAME
αποστέλλεται στον πελάτη. Εδώ είναι η απλοποιημένη έκδοση:
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
Ενδιαφερόμαστε ιδιαίτερα για το SessionEngine
τάξη, την οποία θα αντικαταστήσουμε με κάτι για να αποθηκεύσουμε και να φορτώσουμε δεδομένα από και προς ένα backis.
Ευτυχώς, υπάρχουν μερικά έργα που το χειρίζονται ήδη για εμάς. Ακολουθεί ένα παράδειγμα από redis_sessions_fork . Δώστε ιδιαίτερη προσοχή στο save
και load
μεθόδους, οι οποίες είναι γραμμένες έτσι ώστε (αντίστοιχα) να αποθηκεύουν και να φορτώνουν την περίοδο λειτουργίας από και προς το 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)
Είναι σημαντικό να κατανοήσουμε πώς λειτουργεί αυτή η τάξη, καθώς θα χρειαστεί να εφαρμόσουμε κάτι παρόμοιο στο Flask για τη φόρτωση δεδομένων συνεδρίας. Ας ρίξουμε μια πιο προσεκτική ματιά με ένα παράδειγμα 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}
Η διεπαφή του καταστήματος συνεδριών είναι αρκετά εύκολη στην κατανόηση, αλλά υπάρχουν πολλά πράγματα κάτω από την κουκούλα. Πρέπει να σκάψουμε λίγο βαθύτερα, ώστε να μπορούμε να εφαρμόσουμε κάτι παρόμοιο στο Flask.
Σημείωση: Ίσως να ρωτήσετε, 'Γιατί όχι απλώς να αντιγράψετε το SessionEngine στη φιάλη;' Ευκολότερο να το λες παρά να το κάνεις. Όπως συζητήσαμε στην αρχή, το Django συνδέεται στενά με το αντικείμενο Ρυθμίσεις, οπότε δεν μπορείτε απλώς να εισαγάγετε κάποια ενότητα Django και να το χρησιμοποιήσετε χωρίς καμία επιπλέον εργασία.
Όπως είπα, το Django κάνει πολλή δουλειά για να καλύψει την πολυπλοκότητα της αποθήκευσης συνεδρίας. Ας δούμε το πλήκτρο Redis που είναι αποθηκευμένο στα παραπάνω αποσπάσματα:
>>> store.session_key u'ery3j462ezmmgebbpwjajlxjxmvt5adu'
Τώρα, ας ζητήσουμε αυτό το κλειδί στο redis-cli:
redis 127.0.0.1:6379> get 'django_sessions:ery3j462ezmmgebbpwjajlxjxmvt5adu' 'ZmUxOTY0ZTFkMmNmODA2OWQ5ZjE4MjNhZmQxNDM0MDBiNmQzNzM2Zjp7ImNvdW50IjoxfQ=='
Αυτό που βλέπουμε εδώ είναι πολύ μεγάλο, Base64-κωδικοποιημένο σειρά. Για να κατανοήσουμε τον σκοπό του, πρέπει να κοιτάξουμε το Django's SessionBase
τάξη για να δείτε πώς χειρίζεται:
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 {}
Η μέθοδος κωδικοποίησης πρώτα σειριοποιεί τα δεδομένα με τον τρέχοντα καταχωρημένο σειριοποιητή. Με άλλα λόγια, μετατρέπει την περίοδο λειτουργίας σε μια συμβολοσειρά, την οποία μπορεί αργότερα να μετατρέψει ξανά σε μια συνεδρία (ανατρέξτε στην τεκμηρίωση SESSION_SERIALIZER για περισσότερα). Στη συνέχεια, κατακερματίζει τα σειριακά δεδομένα και χρησιμοποιεί αυτόν τον κατακερματισμό αργότερα ως υπογραφή για να ελέγξει την ακεραιότητα των δεδομένων συνεδρίας. Τέλος, επιστρέφει αυτό το ζεύγος δεδομένων στο χρήστη ως συμβολοσειρά με κωδικοποίηση Base64.
Παρεμπιπτόντως: πριν από την έκδοση 1.6, ο Django είχε προεπιλογή να χρησιμοποιεί pickle για σειριοποίηση των δεδομένων συνεδρίας. Εξαιτίας ΘΕΜΑΤΑ ΑΣΦΑΛΕΙΑΣ , η προεπιλεγμένη μέθοδος σειριοποίησης είναι τώρα django.contrib.sessions.serializers.JSONSerializer
.
utf πίνακα σετ 8 χαρακτήρων
Ας δούμε τη διαδικασία διαχείρισης συνεδρίας σε δράση. Εδώ, το λεξικό συνεδρίας μας θα είναι απλώς ένα πλήθος και ένας ακέραιος αριθμός, αλλά μπορείτε να φανταστείτε πώς θα γενικευόταν σε πιο περίπλοκες συνεδρίες χρηστών.
>>> store.encode({'count': 1}) u'ZmUxOTY0ZTFkMmNmODA2OWQ5ZjE4MjNhZmQxNDM0MDBiNmQzNzM2Zjp7ImNvdW50IjoxfQ==' >>> base64.b64decode(encoded) 'fe1964e1d2cf8069d9f1823afd143400b6d3736f:{'count':1}'
Το αποτέλεσμα της μεθόδου αποθήκευσης (u'ZmUxOTY… == ') είναι μια κωδικοποιημένη συμβολοσειρά που περιέχει τη σειριακή συνεδρία χρήστη και το κατακερματισμό του. Όταν το αποκωδικοποιούμε, πράγματι επιστρέφουμε τόσο τον κατακερματισμό («fe1964e…») όσο και τη συνεδρία ({'count':1}
).
σε ποια γλώσσα είναι γραμμένα τα windows 7
Σημειώστε ότι η μέθοδος αποκωδικοποίησης ελέγχει για να διασφαλίσει ότι ο κατακερματισμός είναι σωστός για αυτήν την περίοδο λειτουργίας, διασφαλίζοντας την ακεραιότητα των δεδομένων όταν πηγαίνουμε να τα χρησιμοποιήσουμε στη Φιάλη. Στην περίπτωσή μας, δεν ανησυχούμε πολύ για την παραβίαση της συνεδρίας μας από την πλευρά του πελάτη επειδή:
Δεν χρησιμοποιούμε περιόδους σύνδεσης βάσει cookie, δηλαδή δεν στέλνουμε όλα δεδομένα χρήστη στον πελάτη.
Στο Flask, θα χρειαζόμαστε μόνο για ανάγνωση SessionStore
το οποίο θα μας πει εάν υπάρχει δεδομένο κλειδί ή όχι και θα επιστρέψει τα αποθηκευμένα δεδομένα.
Στη συνέχεια, ας δημιουργήσουμε μια απλοποιημένη έκδοση της μηχανής συνεδρίας Redis (βάση δεδομένων) για να συνεργαστούμε με το Flask. Θα χρησιμοποιήσουμε το ίδιο SessionStore
(ορίστηκε παραπάνω) ως βασική τάξη, αλλά θα πρέπει να καταργήσουμε ορισμένες από τις λειτουργίες της, π.χ., έλεγχο κακών υπογραφών ή τροποποίηση περιόδων σύνδεσης. Ενδιαφερόμαστε περισσότερο για μια μόνο ανάγνωση SessionStore
που θα φορτώσει τα δεδομένα περιόδου σύνδεσης που έχουν αποθηκευτεί από το Django. Ας δούμε πώς συνδυάζεται:
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 {}
Χρειαζόμαστε μόνο το load
μέθοδος επειδή είναι μια εφαρμογή για αποθήκευση μόνο για ανάγνωση. Αυτό σημαίνει ότι δεν μπορείτε να αποσυνδεθείτε απευθείας από το Flask. Αντ 'αυτού, ίσως θέλετε να ανακατευθύνετε αυτήν την εργασία στο Django. Θυμηθείτε, ο στόχος εδώ είναι να διαχειριστείτε συνεδρίες μεταξύ αυτών των δύο πλαισίων Python για να έχετε μεγαλύτερη ευελιξία.
Το μικροπλαίσιο Flask υποστηρίζει συνεδρίες βασισμένες σε cookie, πράγμα που σημαίνει ότι όλα τα δεδομένα περιόδου λειτουργίας αποστέλλονται στον πελάτη, με κωδικοποίηση Base64 και υπογραφή κρυπτογραφικά. Στην πραγματικότητα, δεν μας ενδιαφέρει πολύ η υποστήριξη συνεδρίας του Flask.
Αυτό που χρειαζόμαστε είναι να αποκτήσουμε το αναγνωριστικό περιόδου σύνδεσης που δημιουργήθηκε από τον Django και να το ελέγξουμε στο backis του Redis, ώστε να είμαστε σίγουροι ότι το αίτημα ανήκει σε έναν προ-υπογεγραμμένο χρήστη. Συνοπτικά, η ιδανική διαδικασία θα ήταν (αυτό συγχρονίζεται με το παραπάνω διάγραμμα):
Θα ήταν βολικό να έχετε έναν διακοσμητή για να ελέγξετε αυτές τις πληροφορίες και να ορίσετε την τρέχουσα user_id
στο g
μεταβλητή στη φιάλη:
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
Στο παραπάνω παράδειγμα, εξακολουθούμε να χρησιμοποιούμε το SessionStore
ορίσαμε προηγουμένως για τη λήψη των δεδομένων Django από το Redis. Εάν η περίοδος λειτουργίας έχει _auth_user_id
, επιστρέφουμε το περιεχόμενο από τη λειτουργία προβολής. Διαφορετικά, ο χρήστης ανακατευθύνεται σε μια σελίδα σύνδεσης, όπως ακριβώς θέλαμε.
Για να μοιραστώ τα cookies, θεωρώ βολικό να ξεκινήσω το Django και το Flask μέσω ενός WSGI διακομιστή και κολλήστε τα μαζί. Σε αυτό το παράδειγμα, έχω χρησιμοποιήσει 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)
Με αυτό, το Django θα εξυπηρετεί στο “/” και το Flask θα σερβίρει σε “/ backend” τελικά σημεία.
Αντί να εξετάσω το Django εναντίον του Flask ή να σας ενθαρρύνω να μάθετε μόνο το μικροπλαίσιο του Flask, έχω συγκολλήσει μαζί το Django και το Flask, κάνοντάς τους να μοιραστούν τα ίδια δεδομένα συνεδρίας για έλεγχο ταυτότητας, αναθέτοντας την εργασία στο Django. Καθώς το Django αποστέλλει πολλές λειτουργικές μονάδες για την επίλυση της εγγραφής χρήστη, της σύνδεσης και της αποσύνδεσης (για να αναφέρουμε μόνο μερικά), ο συνδυασμός αυτών των δύο πλαισίων θα σας εξοικονομήσει πολύτιμο χρόνο, ενώ σας δίνει την ευκαιρία να κάνετε hack σε ένα εύχρηστο μικροπλαίσιο όπως το Flask.