Rakastan ja käytän Django monissa henkilökohtaisissa ja asiakasprojekteissani, enimmäkseen klassisemmille verkkosovelluksille ja relaatiotietokantoihin liittyville. Django ei kuitenkaan ole hopealuoti.
Suunnittelun mukaan Django on hyvin tiukasti yhdistetty sen ORM-, Template Engine System- ja Settings-objektiin. Lisäksi se ei ole uusi projekti: se kuljettaa paljon matkatavaroita pysyäkseen taaksepäin yhteensopivina.
Jotkut Python-kehittäjät pitävät tätä suurena ongelmana. He sanovat, että Django ei ole tarpeeksi joustava ja välttää sitä mahdollisuuksien mukaan ja käyttää sen sijaan Python-mikrokehystä, kuten Flask.
En ole samaa mieltä. Django on hieno, kun sitä käytetään sopiva paikka ja aika , vaikka se ei sovi joka projektin tekniset tiedot Kuten mantra menee: 'Käytä oikeaa työkalua työhön'.
(Jopa silloin, kun se ei ole oikea paikka ja aika, joskus ohjelmoinnilla Djangon kanssa voi olla ainutlaatuisia etuja.)
Joissakin tapauksissa voi olla mukavaa käyttää kevyempää kehystä (kuten Pullo ). Usein nämä mikrokehykset alkavat loistaa, kun huomaat kuinka helppoa niitä on hakata.
Muutamassa asiakasprojektissani olemme keskustelleet Djangosta luopumisesta ja siirtymisestä mikrokehykseen, tyypillisesti kun asiakkaat haluavat tehdä mielenkiintoisia juttuja (yhdessä tapauksessa esimerkiksi upottaa ZeroMQ sovellusobjektissa) ja projektin tavoitteet näyttävät olevan vaikeampia saavuttaa Djangolla.
Pullosta pidän yleisemmin hyödyllisenä:
Samanaikaisesti sovelluksemme vaati käyttäjien rekisteröintiä ja muita yleisiä tehtäviä, jotka Django ratkaisi vuosia sitten. Pienen painonsa vuoksi pullossa ei ole samaa työkalupakettia.
Esiin nousi kysymys: onko Django kaikki vai ei-sopimus?Esiin nousi kysymys: onko Django kaikki vai ei-sopimus? Pitäisikö meidän pudottaa se täysin vai voimmeko oppia yhdistämään sen muiden mikrokehysten tai perinteisten kehysten joustavuuteen? Voimmeko valita kappaleet, joita haluamme käyttää, ja välttää muita?
Voimmeko saada molempien maailmojen parhaat puolet? Sanon kyllä, varsinkin istunnonhallinnassa.
(Puhumattakaan, siellä on paljon projekteja Django-freelancereille.)
Tämän viestin tarkoituksena on delegoida käyttäjien todentamisen ja rekisteröinnin tehtävät Djangolle, mutta käytä Redistä käyttäjän istuntojen jakamiseen muiden kehysten kanssa. Voin ajatella muutamia tilanteita, joissa jotain tällaista olisi hyödyllistä:
Tässä opetusohjelmassa käytän Redis jakaa istuntoja kahden kehyksen välillä (tässä tapauksessa Django ja Flask). Käytän nykyisessä asennuksessa SQLite käyttäjätietojen tallentamiseksi, mutta voit liittää taustasi tarvittaessa NoSQL-tietokantaan (tai SQL-pohjaiseen vaihtoehtoon).
Jos haluat jakaa istuntoja Djangon ja Pullon välillä, meidän on tiedettävä vähän siitä, kuinka Django tallentaa istuntotietonsa. Django dos ovat melko hyviä, mutta annan taustan täydellisyydelle.
Yleensä voit hallita Python-sovelluksesi istuntotietoja kahdella tavalla:
Evästepohjaiset istunnot : Tässä tilanteessa istuntotietoja ei ole tallennettu tietokantaan. Sen sijaan se sarjoitetaan, allekirjoitetaan (SECRET_KEY-tunnuksella) ja lähetetään asiakkaalle. Kun asiakas lähettää kyseisen tiedon takaisin, sen eheys tarkistetaan väärentämisen varalta ja se poistetaan palvelimelta uudelleen.
Tallennuspohjaiset istunnot : Tässä tilanteessa istuntotiedot itse ovat ei lähetetään asiakkaalle. Sen sijaan lähetetään vain pieni osa (avain) nykyisen käyttäjän henkilöllisyyden osoittamiseksi istuntokauppaan.
Esimerkissämme meitä kiinnostaa enemmän jälkimmäinen skenaario: haluamme, että istuntotietomme tallennetaan taustapäähän ja tarkistetaan sitten pullossa. Sama voidaan tehdä entisessä, mutta kuten Djangon asiakirjoissa mainitaan, on joitain huoli turvallisuudesta ensimmäisen menetelmän.
Istunnon käsittelyn ja hallinnan yleinen työnkulku on samanlainen kuin tämä kaavio:
Käydään läpi istunnon jakaminen tarkemmin:
Kun uusi pyyntö tulee sisään, lähetä se ensin rekisteröidyn kautta väliohjelmisto Django-pinossa. Olemme kiinnostuneita tästä SessionMiddleware
luokka, joka, kuten voit odottaa, liittyy istunnon hallintaan ja käsittelyyn:
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)
Tässä katkelmassa Django nappaa rekisteröidyt SessionEngine
(pääsemme siihen pian), otteet SESSION_COOKIE_NAME
alkaen request
(sessionid
, oletusarvoisesti) ja luo uuden esiintymän valitusta SessionEngine
käsittelemään istunnon tallennustilaa.
Myöhemmin (kun käyttäjänäkymä on käsitelty, mutta silti väliohjelmistopinossa), istunto moottori kutsuu tallennusmenetelmänsä tallentaakseen muutokset tietovarastoon. (Näkymän käsittelyn aikana käyttäjä on saattanut muuttaa istunnon aikana joitain asioita, esimerkiksi lisäämällä uuden arvon istuntoobjektiin request.session
-näppäimellä.) Sitten SESSION_COOKIE_NAME
lähetetään asiakkaalle. Tässä on yksinkertaistettu versio:
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
Olemme erityisen kiinnostuneita SessionEngine
luokka, jonka korvataan jollakin tietojen tallentamiseen ja lataamiseen Redisin taustapäähän ja takaisin.
Onneksi on olemassa muutama projekti, jotka jo hoitavat tämän meille. Tässä esimerkki redis_sessions_fork . Kiinnitä huomiota save
ja load
menetelmät, jotka on kirjoitettu tallentamaan ja lataamaan istunto Redisiin ja sieltä:
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)
On tärkeää ymmärtää, miten tämä luokka toimii, koska meidän on toteutettava jotain vastaavaa pullossa istuntotietojen lataamiseksi. Katsotaanpa tarkemmin REPL-esimerkillä:
>>> 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}
Istuntokaupan käyttöliittymä on melko helppo ymmärtää, mutta konepellin alla tapahtuu paljon. Meidän pitäisi kaivaa hieman syvemmälle, jotta voimme toteuttaa jotain vastaavaa pullossa.
Huomaa: Voit kysyä: 'Miksi ei vain kopioida SessionEngine pulloon?' Helpommin sanottu kuin tehty. Kuten keskustelimme alussa, Django on tiukasti yhdistetty sen Asetukset-objektiin, joten et voi vain tuoda jotakin Django-moduulia ja käyttää sitä ilman ylimääräisiä töitä.
Kuten sanoin, Django tekee paljon työtä peittääkseen istuntotallennuksensa monimutkaisuuden. Tarkistetaan Redis-avain, joka on tallennettu yllä oleviin katkelmiin:
>>> store.session_key u'ery3j462ezmmgebbpwjajlxjxmvt5adu'
Antaa nyt kysyä kyseisen avaimen redis-cli:
redis 127.0.0.1:6379> get 'django_sessions:ery3j462ezmmgebbpwjajlxjxmvt5adu' 'ZmUxOTY0ZTFkMmNmODA2OWQ5ZjE4MjNhZmQxNDM0MDBiNmQzNzM2Zjp7ImNvdW50IjoxfQ=='
Se mitä näemme täällä on hyvin pitkä, Base64-koodattu merkkijono. Ymmärtääksemme sen tarkoituksen, meidän on tarkasteltava Djangon SessionBase
luokassa nähdäksesi, miten sitä käsitellään:
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 {}
Koodausmenetelmä sarjoittaa tiedot ensin nykyisellä rekisteröidyllä sarjaliitännällä. Toisin sanoen se muuntaa istunnon merkkijonoksi, jonka se voi myöhemmin muuntaa takaisin istunnoksi (katso lisätietoja SESSION_SERIALIZER-ohjeista). Sitten se hajauttaa sarjoitetut tiedot ja käyttää tätä hajautusta myöhemmin allekirjoituksena istunnon tietojen eheyden tarkistamiseksi. Lopuksi se palauttaa kyseisen tietoparin käyttäjälle Base64-koodatuksi merkkijonoksi.
Muuten: ennen versiota 1.6 Django epäonnistui käyttämään suolakurkkua istuntotietojen sarjoitukseen. Johdosta turvallisuuskysymykset , oletussarjamenetelmä on nyt django.contrib.sessions.serializers.JSONSerializer
.
Katsotaanpa istunnonhallintaprosessi toiminnassa. Tässä istuntosanakirjamme on yksinkertaisesti laskenta ja jokin kokonaisluku, mutta voit kuvitella, kuinka tämä yleistyy monimutkaisemmille käyttäjäistunnoille.
>>> store.encode({'count': 1}) u'ZmUxOTY0ZTFkMmNmODA2OWQ5ZjE4MjNhZmQxNDM0MDBiNmQzNzM2Zjp7ImNvdW50IjoxfQ==' >>> base64.b64decode(encoded) 'fe1964e1d2cf8069d9f1823afd143400b6d3736f:{'count':1}'
Store-menetelmän (u’ZmUxOTY… == ’) tulos on koodattu merkkijono, joka sisältää sarjoitetun käyttäjäistunnon ja sen hash. Kun puramme sen, palautamme todellakin sekä hashin (’fe1964e…’) että istunnon ({'count':1}
).
Huomaa, että dekoodausmenetelmä varmistaa, että hash on oikea kyseiselle istunnolle, mikä takaa tietojen eheyden, kun käytämme sitä Pullossa. Meidän tapauksessamme emme ole liian huolissamme siitä, että istuntoa muutetaan asiakkaan puolella, koska:
Emme käytä evästepohjaisia istuntoja eli emme lähetä kaikki käyttäjätiedot asiakkaalle.
Pullossa tarvitsemme vain luku -muodon SessionStore
joka kertoo meille, onko annettu avain olemassa vai ei, ja palauttaa tallennetut tiedot.
Seuraavaksi luodaan yksinkertaistettu versio Redis-istunnon moottorista (tietokanta), jotta se toimisi pullon kanssa. Käytämme samaa SessionStore
(määritelty edellä) perusluokkana, mutta meidän on poistettava osa sen toiminnoista, esimerkiksi tarkistamalla virheelliset allekirjoitukset tai muokkaamalla istuntoja. Olemme kiinnostuneita vain luku -muodosta SessionStore
joka lataa Djangolta tallennetut istuntotiedot. Katsotaanpa, miten se tulee yhteen:
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 {}
Tarvitsemme vain load
menetelmä, koska se on vain luku -tallennus. Tämä tarkoittaa, että et voi kirjautua ulos suoraan pullosta. sen sijaan haluat ehkä ohjata tämän tehtävän Djangoon. Muista, että tavoitteena on hallita näiden kahden Python-kehyksen välisiä istuntoja, jotta saat enemmän joustavuutta.
Pullon mikrokehys tukee evästepohjaisia istuntoja, mikä tarkoittaa, että kaikki istunnon tiedot lähetetään asiakkaalle, Base64-koodattu ja kryptografisesti allekirjoitettu. Mutta itse asiassa emme ole kovin kiinnostuneita Flaskin istuntotuesta.
Tarvitsemme on saada Djangon luoma istuntotunnus ja tarkistaa se Redis-taustaa vasten, jotta voimme olla varmoja, että pyyntö kuuluu ennalta allekirjoitetulle käyttäjälle. Yhteenvetona voidaan todeta, että ihanteellinen prosessi olisi (tämä synkronoidaan yllä olevan kaavion kanssa):
On kätevää, jos sisustaja tarkistaa nämä tiedot ja asettaa nykyisen user_id
osaksi g
muuttuja pullossa:
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
Yllä olevassa esimerkissä käytämme edelleen SessionStore
määritimme aiemmin hakemaan Django-tiedot Redikseltä. Jos istunnossa on _auth_user_id
, palautamme sisällön näkymäfunktiosta; muuten käyttäjä ohjataan kirjautumissivulle, aivan kuten halusimme.
Evästeiden jakamiseksi minusta on kätevää aloittaa Django ja Flask a WSGI ja liimaa ne yhteen. Tässä esimerkissä olen käyttänyt KirsikkaPy :
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)
Sen avulla Django palvelee '/': ssä ja Flask '/ backend' -päätteissä.
Sen sijaan, että tutkisin Djangoa tai pulloa tai kannustan sinua vain oppimaan Pullon mikrokehyksen, olen hitsannut Djangon ja Pullon yhteen ja saanut heidät jakamaan samat istunnon tiedot todennusta varten delegoimalla tehtävän Djangolle. Kun Djangolla on runsaasti moduuleja käyttäjien rekisteröinnin, kirjautumisen ja uloskirjautumisen ratkaisemiseksi (vain muutamia mainitakseni), näiden kahden kehyksen yhdistäminen säästää arvokasta aikaa ja tarjoaa sinulle mahdollisuuden hakkeroida hallittavissa oleviin mikrokehyksiin, kuten Pullo.